From 5c8088e80872281e1f535252e2d705e820ebe4f8 Mon Sep 17 00:00:00 2001 From: chendaofei <857448963@qq.com> Date: Thu, 25 Sep 2025 10:57:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=8D=A2git=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E5=90=8E=E7=9A=84=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- carry_capacity/pom.xml | 175 ++ .../product/CarryCapacityApplication.java | 33 + .../config/AuthorizationServerConfig.java | 234 +++ .../auth/config/WebSecurityConfig.java | 110 ++ .../auth/controller/AuthController.java | 222 +++ .../auth/controller/JudgeThirdToken.java | 121 ++ .../auth/controller/KaptchaController.java | 80 + .../auth/controller/UserController.java | 203 ++ .../auth/exception/AuthExceptionHandler.java | 93 + .../GlobalBusinessExceptionHandler.java | 256 +++ .../product/auth/filter/AuthGlobalFilter.java | 207 ++ ...mClientCredentialsTokenEndpointFilter.java | 42 + .../product/auth/mapper/AuthClientMapper.java | 16 + .../njcn/product/auth/mapper/RoleMapper.java | 22 + .../njcn/product/auth/mapper/UserMapper.java | 23 + .../product/auth/mapper/UserRoleMapper.java | 29 + .../product/auth/mapper/UserSetMapper.java | 17 + .../auth/mapper/UserStrategyMapper.java | 16 + .../auth/mapper/mapping/UserRoleMapper.xml | 16 + .../product/auth/pojo/bo/BusinessUser.java | 96 + .../product/auth/pojo/constant/DeptState.java | 15 + .../product/auth/pojo/constant/DeptType.java | 16 + .../auth/pojo/constant/FunctionState.java | 38 + .../auth/pojo/constant/HomePageState.java | 19 + .../product/auth/pojo/constant/RoleType.java | 18 + .../pojo/constant/UserDefaultPassword.java | 13 + .../product/auth/pojo/constant/UserState.java | 41 + .../product/auth/pojo/constant/UserType.java | 30 + .../auth/pojo/constant/UserValidMessage.java | 73 + .../njcn/product/auth/pojo/dto/UserDTO.java | 48 + .../auth/pojo/enums/UserResponseEnum.java | 125 ++ .../auth/pojo/enums/UserStatusEnum.java | 32 + .../product/auth/pojo/param/UserParam.java | 136 ++ .../njcn/product/auth/pojo/po/AuthClient.java | 81 + .../com/njcn/product/auth/pojo/po/Role.java | 49 + .../com/njcn/product/auth/pojo/po/User.java | 148 ++ .../njcn/product/auth/pojo/po/UserRole.java | 28 + .../njcn/product/auth/pojo/po/UserSet.java | 38 + .../product/auth/pojo/po/UserStrategy.java | 72 + .../com/njcn/product/auth/pojo/vo/UserVO.java | 64 + .../ClientDetailsServiceImpl.java | 58 + .../security/granter/CaptchaTokenGranter.java | 108 + .../PreAuthenticatedUserDetailsService.java | 56 + .../security/granter/SmsTokenGranter.java | 98 + .../AbstractSmsAuthenticationProvider.java | 341 ++++ .../provider/Sm4AuthenticationProvider.java | 92 + .../provider/SmsAuthenticationProvider.java | 74 + .../token/SmsCodeAuthenticationToken.java | 62 + .../service/CustomUserDetailsService.java | 27 + .../auth/service/IAuthClientService.java | 22 + .../product/auth/service/IRoleService.java | 26 + .../auth/service/IUserRoleService.java | 30 + .../product/auth/service/IUserService.java | 61 + .../product/auth/service/IUserSetService.java | 18 + .../auth/service/IUserStrategyService.java | 22 + .../auth/service/UserDetailsServiceImpl.java | 64 + .../auth/service/UserTokenService.java | 128 ++ .../service/impl/AuthClientServiceImpl.java | 32 + .../auth/service/impl/RoleServiceImpl.java | 57 + .../service/impl/UserRoleServiceImpl.java | 39 + .../auth/service/impl/UserServiceImpl.java | 309 +++ .../auth/service/impl/UserSetServiceImpl.java | 29 + .../service/impl/UserStrategyServiceImpl.java | 37 + .../njcn/product/auth/utils/AuthPubUtil.java | 41 + .../controller/CarryCapacityController.java | 133 ++ .../CarryCapacityDevController.java | 85 + .../CarryCapacityResultController.java | 69 + .../CarryCapacityStrategyController.java | 100 + .../CarryCapacityUserController.java | 90 + .../enums/CarryCapacityResponseEnum.java | 103 + .../enums/CarryingCapacityEnum.java | 164 ++ .../mapper/CarryCapacityDataPOMapper.java | 15 + .../mapper/CarryCapacityDevicePOMapper.java | 15 + .../mapper/CarryCapacityResultPOMapper.java | 14 + .../CarryCapacityStrategyDhlPOMapper.java | 15 + .../mapper/CarryCapacityStrategyPOMapper.java | 14 + .../mapper/CarryCapacityUserPOMapper.java | 15 + .../mapping/CarryCapacityDataPOMapper.xml | 5 + .../mapping/CarryCapacityDevicePOMapper.xml | 5 + .../mapping/CarryCapacityResultPOMapper.xml | 5 + .../CarryCapacityStrategyDhlPOMapper.xml | 5 + .../mapping/CarryCapacityStrategyPOMapper.xml | 5 + .../mapping/CarryCapacityUserPOMapper.xml | 5 + .../pojo/excel/CarryCapcityDataEexcel.java | 292 +++ .../pojo/excel/CarryCapcityDataIEexcel.java | 424 ++++ .../pojo/excel/CarryCapcityDataPEexcel.java | 65 + .../pojo/excel/CarryCapcityDataQEexcel.java | 67 + .../pojo/excel/CarryCapcityDataVEexcel.java | 65 + .../pojo/excel/ExcelDataDTO.java | 24 + .../pojo/param/CarryCapacityCalParam.java | 54 + .../pojo/param/CarryCapacityDeviceParam.java | 51 + .../param/CarryCapacityEvaluateParam.java | 51 + .../param/CarryCapacityQueryDataParam.java | 42 + .../pojo/param/CarryCapacityResultParam.java | 52 + .../param/CarryCapacityStrategyParam.java | 53 + .../pojo/param/CarryCapacityUserParam.java | 103 + .../pojo/param/ExcelDataParam.java | 29 + .../pojo/po/CarryCapacityDataPO.java | 51 + .../pojo/po/CarryCapacityDevicePO.java | 50 + .../pojo/po/CarryCapacityResultPO.java | 129 ++ .../pojo/po/CarryCapacityStrategyDhlPO.java | 98 + .../pojo/po/CarryCapacityStrategyPO.java | 61 + .../pojo/po/CarryCapacityUserPO.java | 83 + .../pojo/vo/CarryCapacityDResultVO.java | 107 + .../pojo/vo/CarryCapacityDataIVO.java | 25 + .../pojo/vo/CarryCapacityDataQVO.java | 26 + .../pojo/vo/CarryCapacityDataVO.java | 37 + .../pojo/vo/CarryCapacityStrategyDhlVO.java | 76 + .../pojo/vo/CarryCapacityStrategyVO.java | 59 + .../pojo/vo/CarryCapacityUserVO.java | 53 + .../service/CarryCapacityDataPOService.java | 17 + .../service/CarryCapacityDevicePOService.java | 21 + .../service/CarryCapacityResultPOService.java | 25 + .../service/CarryCapacityService.java | 39 + .../CarryCapacityStrategyDhlPOService.java | 24 + .../CarryCapacityStrategyPOService.java | 29 + .../service/CarryCapacityUserPOService.java | 25 + .../impl/CarryCapacityDataPOServiceImpl.java | 20 + .../CarryCapacityDevicePOServiceImpl.java | 85 + .../CarryCapacityResultPOServiceImpl.java | 105 + .../impl/CarryCapacityServiceImpl.java | 1272 ++++++++++++ ...CarryCapacityStrategyDhlPOServiceImpl.java | 62 + .../CarryCapacityStrategyPOServiceImpl.java | 139 ++ .../impl/CarryCapacityUserPOServiceImpl.java | 123 ++ .../carrycapacity/util/CarryCapacityUtil.java | 323 +++ .../carrycapacity/util/CheckStringUtil.java | 29 + .../util/EasyExcelDefaultListener.java | 88 + .../carrycapacity/util/EasyExcelUtil.java | 428 ++++ .../util/EasyExcelWriteTool.java | 68 + .../product/carrycapacity/util/FileUtils.java | 83 + .../product/carrycapacity/util/PubUtils.java | 554 ++++++ .../product/carrycapacity/util/Utils.java | 151 ++ .../ledger/controller/LineController.java | 69 + .../controller/TerminalTreeController.java | 62 + .../device/ledger/mapper/DeptLineMapper.java | 47 + .../device/ledger/mapper/DeviceMapper.java | 24 + .../ledger/mapper/LineDetailMapper.java | 24 + .../device/ledger/mapper/LineMapper.java | 103 + .../device/ledger/mapper/OverlimitMapper.java | 17 + .../device/ledger/mapper/TreeMapper.java | 45 + .../device/ledger/mapper/VoltageMapper.java | 23 + .../ledger/mapper/mapping/DeptLineMapper.xml | 7 + .../ledger/mapper/mapping/DeviceMapper.xml | 8 + .../mapper/mapping/LineDetailMapper.xml | 6 + .../ledger/mapper/mapping/LineMapper.xml | 261 +++ .../ledger/mapper/mapping/OverlimitMapper.xml | 5 + .../ledger/mapper/mapping/TreeMapper.xml | 413 ++++ .../ledger/mapper/mapping/VoltageMapper.xml | 8 + .../device/ledger/pojo/dto/DeviceType.java | 42 + .../ledger/pojo/dto/GeneralDeviceDTO.java | 67 + .../device/ledger/pojo/dto/TerminalTree.java | 80 + .../ledger/pojo/enums/LineBaseEnum.java | 63 + .../ledger/pojo/enums/LineFlagEnum.java | 35 + .../ledger/pojo/enums/PowerFlagEnum.java | 52 + .../ledger/pojo/enums/StatisticsEnum.java | 49 + .../ledger/pojo/param/DeviceInfoParam.java | 219 ++ .../device/ledger/pojo/po/DeptLine.java | 31 + .../product/device/ledger/pojo/po/Device.java | 165 ++ .../product/device/ledger/pojo/po/Line.java | 66 + .../device/ledger/pojo/po/LineDetail.java | 219 ++ .../device/ledger/pojo/po/Voltage.java | 42 + .../ledger/pojo/vo/LineDetailDataVO.java | 130 ++ .../device/ledger/pojo/vo/LineDetailVO.java | 109 + .../ledger/pojo/vo/LineOverLimitVO.java | 120 ++ .../ledger/service/DeptLineService.java | 64 + .../device/ledger/service/LineService.java | 33 + .../ledger/service/TerminalBaseService.java | 119 ++ .../ledger/service/TerminalTreeService.java | 30 + .../service/impl/DeptLineServiceImpl.java | 91 + .../service/impl/GeneralDeviceService.java | 434 ++++ .../ledger/service/impl/LineServiceImpl.java | 134 ++ .../service/impl/TerminalBaseServiceImpl.java | 135 ++ .../service/impl/TerminalTreeServiceImpl.java | 158 ++ .../device/overlimit/pojo/Overlimit.java | 952 +++++++++ .../device/overlimit/util/COverlimitUtil.java | 382 ++++ .../dept/controller/AreaController.java | 244 +++ .../dept/controller/DeptController.java | 69 + .../system/dept/mapper/AreaMapper.java | 69 + .../system/dept/mapper/DeptMapper.java | 35 + .../system/dept/mapper/mapping/AreaMapper.xml | 91 + .../system/dept/mapper/mapping/DeptMapper.xml | 87 + .../system/dept/pojo/dto/AreaTreeDTO.java | 20 + .../product/system/dept/pojo/dto/DeptDTO.java | 46 + .../system/dept/pojo/param/AreaParam.java | 88 + .../product/system/dept/pojo/po/Area.java | 73 + .../product/system/dept/pojo/po/Dept.java | 75 + .../system/dept/pojo/vo/AreaTreeVO.java | 41 + .../system/dept/pojo/vo/DeptTreeVO.java | 47 + .../system/dept/service/IAreaService.java | 104 + .../system/dept/service/IDeptService.java | 34 + .../dept/service/impl/AreaServiceImpl.java | 304 +++ .../dept/service/impl/DeptServiceImpl.java | 70 + .../dict/controller/DictDataController.java | 245 +++ .../dict/controller/DictTypeController.java | 153 ++ .../system/dict/enums/DicDataEnum.java | 677 +++++++ .../system/dict/enums/DicDataTypeEnum.java | 163 ++ .../system/dict/enums/SystemResponseEnum.java | 77 + .../system/dict/mapper/DictDataMapper.java | 61 + .../system/dict/mapper/DictTypeMapper.java | 25 + .../dict/mapper/mapping/DictDataMapper.xml | 88 + .../dict/mapper/mapping/DictTypeMapper.xml | 25 + .../system/dict/pojo/param/DictDataParam.java | 95 + .../system/dict/pojo/param/DictTreeParam.java | 84 + .../system/dict/pojo/param/DictTypeParam.java | 82 + .../product/system/dict/pojo/po/DictData.java | 65 + .../product/system/dict/pojo/po/DictType.java | 62 + .../system/dict/pojo/vo/DictDataCache.java | 33 + .../system/dict/pojo/vo/DictDataVO.java | 63 + .../system/dict/service/IDictDataService.java | 123 ++ .../system/dict/service/IDictTypeService.java | 67 + .../service/impl/DictDataServiceImpl.java | 203 ++ .../service/impl/DictTypeServiceImpl.java | 159 ++ .../theme/controller/ConfigController.java | 216 ++ .../theme/controller/FunctionController.java | 149 ++ .../theme/controller/ThemeController.java | 55 + .../system/theme/mapper/ConfigMapper.java | 21 + .../system/theme/mapper/FunctionMapper.java | 29 + .../system/theme/mapper/HomePageMapper.java | 16 + .../theme/mapper/RoleFunctionMapper.java | 25 + .../system/theme/mapper/ThemeMapper.java | 16 + .../theme/mapper/mapping/ConfigMapper.xml | 18 + .../theme/mapper/mapping/FunctionMapper.xml | 83 + .../mapper/mapping/RoleFunctionMapper.xml | 13 + .../system/theme/pojo/param/ConfigParam.java | 78 + .../theme/pojo/param/FunctionParam.java | 77 + .../theme/pojo/param/HomePageParam.java | 62 + .../system/theme/pojo/param/RoleParam.java | 85 + .../product/system/theme/pojo/po/Config.java | 53 + .../system/theme/pojo/po/Function.java | 80 + .../system/theme/pojo/po/HomePage.java | 60 + .../system/theme/pojo/po/RoleFunction.java | 28 + .../product/system/theme/pojo/po/Theme.java | 119 ++ .../system/theme/pojo/vo/FunctionVO.java | 55 + .../system/theme/service/IConfigService.java | 32 + .../theme/service/IFunctionService.java | 123 ++ .../theme/service/IHomePageService.java | 81 + .../theme/service/IRoleFunctionService.java | 30 + .../system/theme/service/IThemeService.java | 33 + .../theme/service/impl/ConfigServiceImpl.java | 86 + .../service/impl/FunctionServiceImpl.java | 354 ++++ .../service/impl/HomePageServiceImpl.java | 99 + .../service/impl/RoleFunctionServiceImpl.java | 51 + .../theme/service/impl/ThemeServiceImpl.java | 39 + .../src/main/resources/application.yml | 96 + carry_capacity/src/main/resources/njcn.jks | Bin 0 -> 2242 bytes .../target/carry_capacity-1.0.0.jar | 0 .../target/carry_capacity-1.0.0.jar.original | 0 carry_capacity/target/classes/application.yml | 96 + .../auth/mapper/mapping/UserRoleMapper.xml | 0 .../mapping/CarryCapacityDataPOMapper.xml | 0 .../mapping/CarryCapacityDevicePOMapper.xml | 0 .../mapping/CarryCapacityResultPOMapper.xml | 0 .../CarryCapacityStrategyDhlPOMapper.xml | 0 .../mapping/CarryCapacityStrategyPOMapper.xml | 0 .../mapping/CarryCapacityUserPOMapper.xml | 0 .../ledger/mapper/mapping/DeptLineMapper.xml | 0 .../ledger/mapper/mapping/DeviceMapper.xml | 0 .../mapper/mapping/LineDetailMapper.xml | 0 .../ledger/mapper/mapping/LineMapper.xml | 0 .../ledger/mapper/mapping/OverlimitMapper.xml | 0 .../ledger/mapper/mapping/TreeMapper.xml | 0 .../ledger/mapper/mapping/VoltageMapper.xml | 0 .../system/dept/mapper/mapping/AreaMapper.xml | 0 .../system/dept/mapper/mapping/DeptMapper.xml | 0 .../dict/mapper/mapping/DictDataMapper.xml | 0 .../dict/mapper/mapping/DictTypeMapper.xml | 0 .../theme/mapper/mapping/ConfigMapper.xml | 0 .../theme/mapper/mapping/FunctionMapper.xml | 0 .../mapper/mapping/RoleFunctionMapper.xml | 0 carry_capacity/target/classes/njcn.jks | 0 .../target/maven-archiver/pom.properties | 0 .../compile/default-compile/createdFiles.lst | 0 .../compile/default-compile/inputFiles.lst | 0 cn-advance/.gitignore | 33 + cn-advance/pom.xml | 148 ++ .../EventRelevantAnalysisController.java | 80 + .../eventSource/mapper/RelevantLogMapper.java | 24 + .../mapper/RmpEventAdvanceMapper.java | 26 + .../mapper/RmpEventDetailAssMapper.java | 21 + .../mapper/mapping/RelevanceMapper.xml | 31 + .../mapping/RmpEventDetailAssMapper.xml | 24 + .../pojo/constant/HarmonicValidMessage.java | 10 + .../dto/eventAggregate/EntityGroupData.java | 43 + .../eventAggregate/EntityGroupEvtData.java | 120 ++ .../pojo/dto/eventAggregate/EntityLogic.java | 21 + .../pojo/dto/eventAggregate/EntityMtrans.java | 70 + .../pojo/dto/eventAggregate/EventAssObj.java | 122 ++ .../pojo/dto/eventAggregate/FinalData.java | 25 + .../pojo/dto/eventAggregate/PlantInfo.java | 32 + .../pojo/dto/eventAggregate/SagEvent.java | 420 ++++ .../pojo/enums/AdvanceResponseEnum.java | 107 + .../eventSource/pojo/po/PqsRelevanceLog.java | 39 + .../pojo/po/RmpEventDetailAssPO.java | 52 + .../service/EventRelevantAnalysisService.java | 35 + .../service/HistoryHarmonicService.java | 25 + .../service/RmpEventDetailAssService.java | 7 + .../EventRelevantAnalysisServiceImpl.java | 565 ++++++ .../impl/HistoryHarmonicServiceImpl.java | 344 ++++ .../impl/RmpEventDetailAssServiceImpl.java | 18 + .../eventSource/utils/UtilNormalization.java | 347 ++++ .../controller/HarmonicUpController.java | 91 + .../harmonicUp/imapper/DataIUpToMapper.java | 16 + .../harmonicUp/imapper/DataVUpToMapper.java | 18 + .../mapper/UpHarmonicDetailMapper.java | 22 + .../harmonicUp/pojo/param/HistoryParam.java | 45 + .../advance/harmonicUp/pojo/po/DataIUp.java | 194 ++ .../advance/harmonicUp/pojo/po/DataVUp.java | 200 ++ .../harmonicUp/pojo/po/UpHarmonicDetail.java | 110 ++ .../pojo/vo/HistoryDataResultVO.java | 55 + .../pojo/vo/QueryResultLimitVO.java | 30 + .../harmonicUp/pojo/vo/UpTableInfo.java | 32 + .../harmonicUp/service/HarmonicUpService.java | 28 + .../service/HistoryResultService.java | 27 + .../service/impl/HarmonicUpServiceImpl.java | 719 +++++++ .../impl/HistoryResultServiceImpl.java | 777 ++++++++ .../CanonicalCorrelationAnalysis.java | 382 ++++ .../calculator/HarmonicCalculationEngine.java | 425 ++++ .../calculator/ResponsibilityCalculator.java | 429 ++++ .../controller/HistoryHarmonicController.java | 68 + .../controller/ResponsibilityController.java | 114 ++ .../controller/UserDataController.java | 114 ++ .../responsility/imapper/DataHarmP.java | 30 + .../responsility/mapper/RespDataMapper.java | 26 + .../mapper/RespDataResultMapper.java | 16 + .../mapper/RespUserDataIntegrityMapper.java | 20 + .../mapper/RespUserDataMapper.java | 24 + .../mapper/mapping/RespDataMapper.xml | 26 + .../mapper/mapping/RespDataResultMapper.xml | 5 + .../mapping/RespUserDataIntegrityMapper.xml | 11 + .../mapper/mapping/RespUserDataMapper.xml | 24 + .../responsility/model/CacheQvvrData.java | 47 + .../responsility/model/HIKSDKStructure.java | 33 + .../responsility/model/HKDataStruct.java | 37 + .../responsility/model/HarmonicData.java | 287 +++ .../responsility/model/PDataStruct.java | 55 + .../responsility/model/QvvrDataEntity.java | 54 + .../responsility/model/QvvrStruct.java | 205 ++ .../responsility/pojo/bo/DealDataResult.java | 37 + .../pojo/bo/DealUserDataResult.java | 31 + .../responsility/pojo/bo/RespCommon.java | 25 + .../responsility/pojo/bo/RespHarmData.java | 25 + .../responsility/pojo/bo/UserDataExcel.java | 46 + .../pojo/constant/CalculationMode.java | 46 + .../pojo/constant/CalculationStatus.java | 49 + .../pojo/constant/HarmonicConstants.java | 60 + .../responsility/pojo/dto/CustomerData.java | 27 + .../pojo/dto/CustomerResponsibility.java | 34 + .../responsility/pojo/dto/RespDataDTO.java | 29 + .../pojo/dto/ResponsibilityResult.java | 53 + .../pojo/param/HistoryHarmParam.java | 56 + .../pojo/param/PHistoryHarmParam.java | 34 + .../pojo/param/RespBaseParam.java | 18 + .../param/ResponsibilityCalculateParam.java | 57 + .../param/ResponsibilitySecondCalParam.java | 44 + .../pojo/param/UserDataIntegrityParam.java | 19 + .../responsility/pojo/po/RespData.java | 55 + .../responsility/pojo/po/RespDataResult.java | 80 + .../responsility/pojo/po/RespUserData.java | 57 + .../pojo/po/RespUserDataIntegrity.java | 63 + .../IHarmonicResponsibilityService.java | 62 + .../service/IRespDataResultService.java | 20 + .../service/IRespDataService.java | 38 + .../IRespUserDataIntegrityService.java | 20 + .../service/IRespUserDataService.java | 40 + .../HarmonicResponsibilityServiceImpl.java | 163 ++ .../impl/RespDataResultServiceImpl.java | 88 + .../service/impl/RespDataServiceImpl.java | 1756 +++++++++++++++++ .../RespUserDataIntegrityServiceImpl.java | 33 + .../service/impl/RespUserDataServiceImpl.java | 486 +++++ .../advance/responsility/utils/MathUtils.java | 314 +++ .../utils/ResponsibilityAlgorithm.java | 766 +++++++ .../advance/CnAdvanceApplicationTests.java | 13 + cn-begin/.gitignore | 33 + cn-begin/pom.xml | 84 + .../product/begin/CnBeginApplication.java | 20 + .../main/resources/application-wuxi_dev.yml | 99 + .../main/resources/application-wuxi_prod.yml | 73 + cn-begin/src/main/resources/application.yml | 84 + cn-begin/src/main/resources/logback.xml | 142 ++ .../begin/CnBeginApplicationTests.java | 13 + cn-diagram/.gitignore | 33 + cn-diagram/pom.xml | 105 + .../controller/LedgerScaleController.java | 171 ++ .../LedgerScale/pojo/dto/EventSourceDTO.java | 32 + .../LedgerScale/pojo/dto/LedgerScaleDTO.java | 25 + .../LedgerScale/pojo/vo/EventDetailVO.java | 82 + .../LedgerScale/pojo/vo/EventLedgerVO.java | 27 + .../service/LedgerScaleService.java | 47 + .../service/impl/LedgerScaleServiceImpl.java | 500 +++++ .../njcn/product/diagram/job/CustomJob.java | 39 + .../diagram/websocket/WebSocketConfig.java | 41 + .../diagram/websocket/WebSocketServer.java | 176 ++ cn-terminal/.gitignore | 33 + cn-terminal/pom.xml | 90 + .../device/mapper/PqDeviceDetailMapper.java | 13 + .../device/mapper/PqDeviceMapper.java | 30 + .../device/mapper/PqGdCompanyMapper.java | 16 + .../terminal/device/mapper/PqLineMapper.java | 28 + .../device/mapper/PqLinedetailMapper.java | 9 + .../device/mapper/PqSubstationMapper.java | 21 + .../device/mapper/PqsDeptslineMapper.java | 17 + .../device/mapper/PqsStationMapMapper.java | 18 + .../device/mapper/mapping/PqDeviceMapper.xml | 135 ++ .../device/mapper/mapping/PqLineMapper.xml | 108 + .../mapper/mapping/PqSubstationMapper.xml | 37 + .../terminal/device/pojo/dto/DeviceDTO.java | 45 + .../device/pojo/dto/DeviceDeptDTO.java | 18 + .../device/pojo/dto/LedgerBaseInfoDTO.java | 39 + .../device/pojo/dto/OracleLedgerTreeDTO.java | 25 + .../terminal/device/pojo/dto/PqsDeptDTO.java | 70 + .../device/pojo/dto/SubstationDTO.java | 22 + .../terminal/device/pojo/po/PqDevice.java | 127 ++ .../device/pojo/po/PqDeviceDetail.java | 69 + .../terminal/device/pojo/po/PqGdCompany.java | 26 + .../terminal/device/pojo/po/PqLine.java | 132 ++ .../terminal/device/pojo/po/PqLinedetail.java | 52 + .../terminal/device/pojo/po/PqSubstation.java | 45 + .../terminal/device/pojo/po/PqsDeptsline.java | 30 + .../device/pojo/po/PqsStationMap.java | 57 + .../device/service/LedgerTreeService.java | 10 + .../device/service/PqDeviceService.java | 27 + .../device/service/PqLineService.java | 25 + .../device/service/PqSubstationService.java | 21 + .../device/service/PqsDeptslineService.java | 17 + .../service/impl/LedgerTreeServiceImpl.java | 85 + .../service/impl/PqDeviceServiceImpl.java | 40 + .../service/impl/PqLineServiceImpl.java | 70 + .../service/impl/PqSubstationServiceImpl.java | 26 + .../service/impl/PqsDeptslineServiceImpl.java | 20 + .../event/controller/EventGateController.java | 45 + .../pojo/param/MonitorTerminalParam.java | 23 + .../event/service/EventGateService.java | 15 + .../service/impl/EventGateServiceImpl.java | 80 + .../controller/LedgerTreeController.java | 72 + .../controller/LineController.java | 46 + .../mysqlTerminal/mapper/DeptLineMapper.java | 28 + .../mysqlTerminal/mapper/DeviceMapper.java | 24 + .../mapper/LedgerScaleMapper.java | 19 + .../mapper/LineDetailMapper.java | 86 + .../mysqlTerminal/mapper/LineMapper.java | 114 ++ .../mysqlTerminal/mapper/OverlimitMapper.java | 17 + .../mapper/RmpEventDetailMapper.java | 20 + .../mysqlTerminal/mapper/TreeMapper.java | 45 + .../mapper/UserReportPOMapper.java | 20 + .../mysqlTerminal/mapper/VoltageMapper.java | 37 + .../mapper/mapping/DeptLineMapper.xml | 83 + .../mapper/mapping/DeptMapper.xml | 82 + .../mapper/mapping/LedgerScaleMapper.xml | 85 + .../mapper/mapping/LineMapper.xml | 400 ++++ .../mapper/mapping/TreeMapper.xml | 413 ++++ .../pojo/constant/ValidMessage.java | 77 + .../mysqlTerminal/pojo/dto/DeptGetBase.java | 41 + .../pojo/dto/DeptGetChildrenMoreDTO.java | 24 + .../mysqlTerminal/pojo/dto/DeviceType.java | 42 + .../pojo/dto/GeneralDeviceDTO.java | 67 + .../pojo/dto/LedgerBaseInfo.java | 75 + .../mysqlTerminal/pojo/dto/LedgerTreeDTO.java | 27 + .../mysqlTerminal/pojo/dto/LineDevGetDTO.java | 123 ++ .../mysqlTerminal/pojo/dto/LineInfo.java | 30 + .../pojo/enums/LineBaseEnum.java | 63 + .../pojo/enums/LineFlagEnum.java | 35 + .../pojo/enums/PowerFlagEnum.java | 52 + .../mysqlTerminal/pojo/enums/RunFlagEnum.java | 34 + .../pojo/enums/StatisticsEnum.java | 49 + .../pojo/param/DeptGetLineParam.java | 51 + .../pojo/param/DeviceInfoParam.java | 218 ++ .../pojo/param/LargeScreenCountParam.java | 41 + .../pojo/param/UserReportParam.java | 215 ++ .../mysqlTerminal/pojo/po/DeptLine.java | 31 + .../mysqlTerminal/pojo/po/Device.java | 165 ++ .../terminal/mysqlTerminal/pojo/po/Line.java | 66 + .../mysqlTerminal/pojo/po/LineDetail.java | 219 ++ .../mysqlTerminal/pojo/po/Overlimit.java | 876 ++++++++ .../mysqlTerminal/pojo/po/PqsDeviceUnit.java | 120 ++ .../mysqlTerminal/pojo/po/PqsTflgass.java | 46 + .../mysqlTerminal/pojo/po/PqsTflgploy.java | 55 + .../mysqlTerminal/pojo/po/PqsTflgployass.java | 37 + .../pojo/po/RmpEventDetailPO.java | 127 ++ .../pojo/po/UserReportNormalPO.java | 53 + .../mysqlTerminal/pojo/po/UserReportPO.java | 184 ++ .../pojo/po/UserReportProjectPO.java | 96 + .../pojo/po/UserReportRenewalPO.java | 67 + .../pojo/po/UserReportSensitivePO.java | 191 ++ .../pojo/po/UserReportSubstationPO.java | 142 ++ .../mysqlTerminal/pojo/po/Voltage.java | 42 + .../pojo/vo/AdvanceEventDetailVO.java | 116 ++ .../mysqlTerminal/pojo/vo/LineDataVO.java | 52 + .../pojo/vo/LineDetailDataVO.java | 135 ++ .../mysqlTerminal/pojo/vo/LineDetailVO.java | 109 + .../mysqlTerminal/pojo/vo/TerminalShowVO.java | 23 + .../mysqlTerminal/pojo/vo/TerminalTree.java | 80 + .../mysqlTerminal/pojo/vo/UserLedgerVO.java | 24 + .../service/CommGeneralService.java | 518 +++++ .../service/CommTerminalService.java | 33 + .../service/DeptLineService.java | 38 + .../mysqlTerminal/service/LineService.java | 39 + .../service/TerminalBaseService.java | 130 ++ .../service/UserReportPOService.java | 25 + .../service/impl/CommTerminalServiceImpl.java | 130 ++ .../service/impl/DeptLineServiceImpl.java | 48 + .../service/impl/LineServiceImpl.java | 336 ++++ .../service/impl/TerminalBaseServiceImpl.java | 196 ++ .../service/impl/UserReportPOServiceImpl.java | 54 + .../product/terminal/utils/TerminalUtils.java | 79 + cn-user/.gitignore | 33 + cn-user/pom.xml | 45 + .../user/controller/AuthController.java | 146 ++ .../user/controller/DeptController.java | 70 + .../controller/SysFunctionController.java | 168 ++ .../user/controller/SysRoleController.java | 104 + .../user/controller/SysUserController.java | 127 ++ .../cnuser/user/mapper/DeptMapper.java | 36 + .../cnuser/user/mapper/SysFunctionMapper.java | 30 + .../user/mapper/SysRoleFunctionMapper.java | 24 + .../cnuser/user/mapper/SysRoleMapper.java | 13 + .../cnuser/user/mapper/SysUserMapper.java | 13 + .../cnuser/user/mapper/SysUserRoleMapper.java | 23 + .../user/mapper/mapping/SysFunctionMapper.xml | 31 + .../mapper/mapping/SysRoleFunctionMapper.xml | 13 + .../user/mapper/mapping/SysRoleMapper.xml | 7 + .../user/mapper/mapping/SysUserMapper.xml | 7 + .../user/mapper/mapping/SysUserRoleMapper.xml | 14 + .../user/pojo/constant/FunctionConst.java | 33 + .../cnuser/user/pojo/constant/RoleConst.java | 28 + .../cnuser/user/pojo/constant/UserConst.java | 16 + .../user/pojo/constant/UserValidMessage.java | 49 + .../product/cnuser/user/pojo/dto/DeptDTO.java | 46 + .../product/cnuser/user/pojo/dto/UserDTO.java | 48 + .../user/pojo/enums/UserResponseEnum.java | 37 + .../user/pojo/param/SysFunctionParam.java | 74 + .../cnuser/user/pojo/param/SysRoleParam.java | 83 + .../cnuser/user/pojo/param/SysUserParam.java | 104 + .../product/cnuser/user/pojo/po/Dept.java | 75 + .../cnuser/user/pojo/po/SysFunction.java | 88 + .../product/cnuser/user/pojo/po/SysRole.java | 50 + .../cnuser/user/pojo/po/SysRoleFunction.java | 27 + .../product/cnuser/user/pojo/po/SysUser.java | 97 + .../cnuser/user/pojo/po/SysUserRole.java | 27 + .../product/cnuser/user/pojo/po/Token.java | 16 + .../cnuser/user/pojo/vo/DeptAllTreeVO.java | 32 + .../product/cnuser/user/pojo/vo/MenuVO.java | 13 + .../product/cnuser/user/pojo/vo/MetaVO.java | 49 + .../cnuser/user/service/IDeptService.java | 29 + .../user/service/ISysFunctionService.java | 71 + .../user/service/ISysRoleFunctionService.java | 47 + .../cnuser/user/service/ISysRoleService.java | 53 + .../user/service/ISysUserRoleService.java | 56 + .../cnuser/user/service/ISysUserService.java | 106 + .../user/service/impl/DeptServiceImpl.java | 75 + .../service/impl/SysFunctionServiceImpl.java | 234 +++ .../impl/SysRoleFunctionServiceImpl.java | 68 + .../user/service/impl/SysRoleServiceImpl.java | 128 ++ .../service/impl/SysUserRoleServiceImpl.java | 79 + .../user/service/impl/SysUserServiceImpl.java | 212 ++ cn-zutai/.gitignore | 33 + cn-zutai/pom.xml | 60 + .../controller/CsConfigurationController.java | 110 ++ .../zutai/controller/CsPagePOController.java | 69 + .../zutai/controller/ElementController.java | 68 + .../controller/RealTimeDataController.java | 43 + .../zutai/mapper/CsConfigurationMapper.java | 19 + .../cnzutai/zutai/mapper/CsElementMapper.java | 16 + .../cnzutai/zutai/mapper/CsPagePOMapper.java | 15 + .../mapper/mapping/CsConfigurationMapper.xml | 42 + .../zutai/mapper/mapping/CsPagePOMapper.xml | 21 + .../zutai/pojo/dto/AskRealTimeDataDTO.java | 19 + .../zutai/pojo/dto/RealTimeDataDTO.java | 25 + .../cnzutai/zutai/pojo/dto/ZuTaiDTO.java | 50 + .../pojo/enums/CsSystemResponseEnum.java | 35 + .../zutai/pojo/param/CsConfigurationParm.java | 64 + .../cnzutai/zutai/pojo/param/CsPageParm.java | 53 + .../zutai/pojo/param/ElementParam.java | 45 + .../zutai/pojo/po/CsConfigurationPO.java | 72 + .../cnzutai/zutai/pojo/po/CsElement.java | 70 + .../cnzutai/zutai/pojo/po/CsPagePO.java | 77 + .../zutai/pojo/vo/CsConfigurationVO.java | 54 + .../cnzutai/zutai/pojo/vo/CsPageVO.java | 71 + .../cnzutai/zutai/pojo/vo/CsRtDataVO.java | 52 + .../zutai/pojo/vo/DataArrayTreeVO.java | 27 + .../cnzutai/zutai/pojo/vo/ElementsVO.java | 21 + .../cnzutai/zutai/pojo/vo/RealTimeDataVo.java | 51 + .../zutai/service/CsConfigurationService.java | 33 + .../zutai/service/CsPagePOService.java | 34 + .../zutai/service/IElementService.java | 36 + .../zutai/service/ILineTargetService.java | 28 + .../impl/CsConfigurationServiceImpl.java | 225 +++ .../service/impl/CsElementServiceImpl.java | 65 + .../service/impl/CsPagePOServiceImpl.java | 151 ++ .../service/impl/LineTargetServiceImpl.java | 471 +++++ event_smart/.gitignore | 33 + event_smart/pom.xml | 166 ++ .../product/event/EventSmartApplication.java | 18 + .../event/devcie/config/PqlineCache.java | 81 + .../event/devcie/job/LineCacheJob.java | 79 + .../devcie/mapper/PqDeviceDetailMapper.java | 13 + .../event/devcie/mapper/PqDeviceMapper.java | 29 + .../devcie/mapper/PqGdCompanyMapper.java | 16 + .../event/devcie/mapper/PqLineMapper.java | 28 + .../devcie/mapper/PqLinedetailMapper.java | 9 + .../devcie/mapper/PqSubstationMapper.java | 20 + .../devcie/mapper/PqsStationMapMapper.java | 17 + .../devcie/mapper/mapping/PqDeviceMapper.xml | 181 ++ .../devcie/mapper/mapping/PqLineMapper.xml | 109 + .../mapper/mapping/PqSubstationMapper.xml | 37 + .../event/devcie/pojo/dto/DeviceDTO.java | 45 + .../event/devcie/pojo/dto/DeviceDeptDTO.java | 18 + .../devcie/pojo/dto/LedgerBaseInfoDTO.java | 41 + .../event/devcie/pojo/dto/PqsDeptDTO.java | 70 + .../event/devcie/pojo/dto/SubstationDTO.java | 22 + .../event/devcie/pojo/po/PqDevice.java | 127 ++ .../event/devcie/pojo/po/PqDeviceDetail.java | 70 + .../event/devcie/pojo/po/PqGdCompany.java | 27 + .../product/event/devcie/pojo/po/PqLine.java | 132 ++ .../event/devcie/pojo/po/PqLinedetail.java | 52 + .../event/devcie/pojo/po/PqSubstation.java | 45 + .../event/devcie/pojo/po/PqsDeptsline.java | 31 + .../event/devcie/pojo/po/PqsStationMap.java | 58 + .../event/devcie/service/PqDeviceService.java | 26 + .../event/devcie/service/PqLineService.java | 24 + .../devcie/service/PqSubstationService.java | 20 + .../devcie/service/PqsDeptslineService.java | 16 + .../service/impl/PqDeviceServiceImpl.java | 38 + .../service/impl/PqLineServiceImpl.java | 69 + .../service/impl/PqSubstationServiceImpl.java | 26 + .../service/impl/PqsDeptslineServiceImpl.java | 19 + .../EasyPoiWordExportController.java | 49 + .../report/pojo/dto/BjCustomReportDTO.java | 46 + .../report/pojo/param/ReportExportParam.java | 30 + .../service/EasyPoiWordExportService.java | 16 + .../impl/EasyPoiWordExportServiceImpl.java | 253 +++ .../event/report/utils/WordTemplate.java | 119 ++ .../controller/EventGateController.java | 365 ++++ .../controller/EventRightController.java | 130 ++ .../LargeScreenCountController.java | 265 +++ .../controller/MsgEventConfigController.java | 53 + .../controller/PqUserLedgerController.java | 54 + .../controller/PqsDicTreeController.java | 46 + .../transientes/filter/JwtRequestFilter.java | 82 + .../transientes/handler/ControllerUtil.java | 37 + .../GlobalBusinessExceptionHandler.java | 252 +++ .../handler/SqlExecuteTimeInterceptor.java | 57 + .../mapper/MessageEventFeedbackMapper.java | 13 + .../mapper/MsgEventConfigMapper.java | 12 + .../mapper/MsgEventInfoMapper.java | 9 + .../mapper/PqDevicedetailMapper.java | 15 + .../mapper/PqUserLedgerMapper.java | 14 + .../mapper/PqUserLineAssMapper.java | 9 + .../transientes/mapper/PqsDeptsMapper.java | 22 + .../mapper/PqsDeptslineMapper.java | 20 + .../transientes/mapper/PqsDicDataMapper.java | 9 + .../transientes/mapper/PqsDicTreeMapper.java | 16 + .../transientes/mapper/PqsDicTypeMapper.java | 10 + .../mapper/PqsEventdetailMapper.java | 15 + .../mapper/PqsIntegrityMapper.java | 15 + .../mapper/PqsOnlinerateMapper.java | 15 + .../transientes/mapper/PqsUserMapper.java | 9 + .../transientes/mapper/PqsUserSetMapper.java | 13 + .../mapper/mapping/PqDevicedetailMapper.xml | 28 + .../mapper/mapping/PqUserLedger.xml | 17 + .../mapper/mapping/PqsDeptsMapper.xml | 56 + .../mapper/mapping/PqsDeptslineMapper.xml | 22 + .../mapper/mapping/PqsEventdetailMapper.xml | 57 + .../mapper/mapping/PqsOnlinerateMapper.xml | 16 + .../event/transientes/pojo/DicTreeEnum.java | 24 + .../pojo/constant/RedisConstant.java | 13 + .../transientes/pojo/enums/DicTypeEnum.java | 33 + .../pojo/param/LargeScreenCountParam.java | 65 + .../pojo/param/MessageEventFeedbackParam.java | 38 + .../pojo/param/MonitorTerminalParam.java | 30 + .../pojo/param/MsgEventConfigParam.java | 49 + .../pojo/param/PqUserLedgerParam.java | 44 + .../pojo/param/SimulationMsgParam.java | 21 + .../pojo/po/MessageEventFeedback.java | 35 + .../transientes/pojo/po/MsgEventConfig.java | 84 + .../transientes/pojo/po/MsgEventInfo.java | 66 + .../pojo/po/PqDevicedetailaaa.java | 94 + .../transientes/pojo/po/PqUserLedgerPO.java | 108 + .../transientes/pojo/po/PqUserLineAssPO.java | 25 + .../event/transientes/pojo/po/PqsDepts.java | 79 + .../event/transientes/pojo/po/PqsDicData.java | 49 + .../transientes/pojo/po/PqsDicTreePO.java | 48 + .../event/transientes/pojo/po/PqsDicType.java | 39 + .../transientes/pojo/po/PqsEventdetail.java | 107 + .../transientes/pojo/po/PqsIntegrity.java | 34 + .../transientes/pojo/po/PqsOnlinerate.java | 32 + .../event/transientes/pojo/po/PqsUser.java | 58 + .../event/transientes/pojo/po/PqsUserSet.java | 49 + .../transientes/pojo/vo/AlarmAnalysisVO.java | 41 + .../transientes/pojo/vo/DeviceCountVO.java | 18 + .../transientes/pojo/vo/EventDetailVO.java | 52 + .../transientes/pojo/vo/EventMsgDetailVO.java | 35 + .../transientes/pojo/vo/EventTrendVO.java | 18 + .../transientes/pojo/vo/LedgerCountVO.java | 31 + .../event/transientes/pojo/vo/MapCountVO.java | 28 + .../transientes/pojo/vo/PqsDicTreeVO.java | 36 + .../transientes/pojo/vo/RegionDevCountVO.java | 28 + .../pojo/vo/SubStationCountVO.java | 63 + .../pojo/vo/UserLedgerStatisticVO.java | 63 + .../transientes/security/AuthController.java | 102 + .../transientes/security/AuthResponse.java | 20 + .../transientes/security/MyUserDetails.java | 64 + .../security/MyUserDetailsService.java | 66 + .../transientes/security/SecurityConfig.java | 57 + .../service/CommGeneralService.java | 52 + .../transientes/service/EventGateService.java | 15 + .../service/EventRightService.java | 48 + .../service/LargeScreenCountService.java | 64 + .../service/MessageEventFeedbackService.java | 7 + .../service/MsgEventConfigService.java | 20 + .../service/MsgEventInfoService.java | 11 + .../service/PqDevicedetailService.java | 16 + .../service/PqUserLedgerService.java | 26 + .../transientes/service/PqsDeptsService.java | 24 + .../service/PqsDicTreeService.java | 14 + .../service/PqsEventdetailService.java | 17 + .../service/PqsOnlinerateService.java | 16 + .../transientes/service/PqsUserService.java | 14 + .../service/PqsUsersetService.java | 18 + .../service/impl/EventGateServiceImpl.java | 76 + .../service/impl/EventRightServiceImpl.java | 983 +++++++++ .../impl/LargeScreenCountServiceImpl.java | 1539 +++++++++++++++ .../impl/MessageEventFeedbackServiceImpl.java | 16 + .../impl/MsgEventConfigServiceImpl.java | 104 + .../service/impl/MsgEventInfoServiceImpl.java | 39 + .../impl/PqDevicedetailServiceImpl.java | 19 + .../service/impl/PqUserLedgerServiceImpl.java | 65 + .../service/impl/PqsDeptsServiceImpl.java | 32 + .../service/impl/PqsDicTreeServiceImpl.java | 31 + .../impl/PqsEventdetailServiceImpl.java | 22 + .../impl/PqsOnlinerateServiceImpl.java | 19 + .../service/impl/PqsUserServiceImpl.java | 19 + .../service/impl/PqsUsersetServiceImpl.java | 21 + .../event/transientes/utils/JwtUtil.java | 85 + .../websocket/WebSocketConfig.java | 41 + .../websocket/WebSocketServer.java | 147 ++ .../src/main/resources/application-dev.yml | 53 + .../src/main/resources/application-prod.yml | 55 + .../src/main/resources/application.yml | 72 + event_smart/src/main/resources/logback.xml | 146 ++ .../src/main/resources/template/test.docx | Bin 0 -> 1023599 bytes .../event/EventSmartApplicationTests.java | 13 + 741 files changed, 62243 insertions(+) create mode 100644 carry_capacity/pom.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/CarryCapacityApplication.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/config/AuthorizationServerConfig.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/config/WebSecurityConfig.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/controller/AuthController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/controller/JudgeThirdToken.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/controller/KaptchaController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/controller/UserController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/exception/AuthExceptionHandler.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/exception/GlobalBusinessExceptionHandler.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/filter/AuthGlobalFilter.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/filter/CustomClientCredentialsTokenEndpointFilter.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/mapper/AuthClientMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/mapper/RoleMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserRoleMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserSetMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserStrategyMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/mapper/mapping/UserRoleMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/bo/BusinessUser.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/DeptState.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/DeptType.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/FunctionState.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/HomePageState.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/RoleType.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserDefaultPassword.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserState.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserType.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserValidMessage.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/dto/UserDTO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/enums/UserResponseEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/enums/UserStatusEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/param/UserParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/AuthClient.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/Role.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/User.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserRole.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserSet.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserStrategy.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/pojo/vo/UserVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/security/clientdetails/ClientDetailsServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/security/granter/CaptchaTokenGranter.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/security/granter/PreAuthenticatedUserDetailsService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/security/granter/SmsTokenGranter.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/security/provider/AbstractSmsAuthenticationProvider.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/security/provider/Sm4AuthenticationProvider.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/security/provider/SmsAuthenticationProvider.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/security/token/SmsCodeAuthenticationToken.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/CustomUserDetailsService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/IAuthClientService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/IRoleService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/IUserRoleService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/IUserService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/IUserSetService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/IUserStrategyService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/UserDetailsServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/UserTokenService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/impl/AuthClientServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/impl/RoleServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserRoleServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserSetServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserStrategyServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/auth/utils/AuthPubUtil.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityDevController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityResultController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityStrategyController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityUserController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/enums/CarryCapacityResponseEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/enums/CarryingCapacityEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityDataPOMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityDevicePOMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityResultPOMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityStrategyDhlPOMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityStrategyPOMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityUserPOMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDataPOMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDevicePOMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityResultPOMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyDhlPOMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyPOMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityUserPOMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataEexcel.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataIEexcel.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataPEexcel.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataQEexcel.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataVEexcel.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/ExcelDataDTO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityCalParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityDeviceParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityEvaluateParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityQueryDataParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityResultParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityStrategyParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityUserParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/ExcelDataParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityDataPO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityDevicePO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityResultPO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityStrategyDhlPO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityStrategyPO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityUserPO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDResultVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataIVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataQVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityStrategyDhlVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityStrategyVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityUserVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityDataPOService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityDevicePOService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityResultPOService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityStrategyDhlPOService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityStrategyPOService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityUserPOService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityDataPOServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityDevicePOServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityResultPOServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityStrategyDhlPOServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityStrategyPOServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityUserPOServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/CarryCapacityUtil.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/CheckStringUtil.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelDefaultListener.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelUtil.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelWriteTool.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/FileUtils.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/PubUtils.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/Utils.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/controller/LineController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/controller/TerminalTreeController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/DeptLineMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/DeviceMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/LineDetailMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/LineMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/OverlimitMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/TreeMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/VoltageMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/DeptLineMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/DeviceMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/LineDetailMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/LineMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/OverlimitMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/TreeMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/VoltageMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/DeviceType.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/GeneralDeviceDTO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/TerminalTree.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/LineBaseEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/LineFlagEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/PowerFlagEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/StatisticsEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/param/DeviceInfoParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/DeptLine.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Device.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Line.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/LineDetail.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Voltage.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineDetailDataVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineDetailVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineOverLimitVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/service/DeptLineService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/service/LineService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/service/TerminalBaseService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/service/TerminalTreeService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/DeptLineServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/GeneralDeviceService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/LineServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/TerminalBaseServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/TerminalTreeServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/overlimit/pojo/Overlimit.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/device/overlimit/util/COverlimitUtil.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/controller/AreaController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/controller/DeptController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/AreaMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/DeptMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/mapping/AreaMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/mapping/DeptMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/dto/AreaTreeDTO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/dto/DeptDTO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/param/AreaParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/po/Area.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/po/Dept.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/vo/AreaTreeVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/vo/DeptTreeVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/service/IAreaService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/service/IDeptService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/service/impl/AreaServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dept/service/impl/DeptServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/controller/DictDataController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/controller/DictTypeController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/enums/DicDataEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/enums/DicDataTypeEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/enums/SystemResponseEnum.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/DictDataMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/DictTypeMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/mapping/DictDataMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/mapping/DictTypeMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictDataParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictTreeParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictTypeParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/po/DictData.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/po/DictType.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/vo/DictDataCache.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/vo/DictDataVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/service/IDictDataService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/service/IDictTypeService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/service/impl/DictDataServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/dict/service/impl/DictTypeServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/controller/ConfigController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/controller/FunctionController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/controller/ThemeController.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/ConfigMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/FunctionMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/HomePageMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/RoleFunctionMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/ThemeMapper.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/ConfigMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/FunctionMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/RoleFunctionMapper.xml create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/ConfigParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/FunctionParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/HomePageParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/RoleParam.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Config.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Function.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/HomePage.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/RoleFunction.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Theme.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/vo/FunctionVO.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/service/IConfigService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/service/IFunctionService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/service/IHomePageService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/service/IRoleFunctionService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/service/IThemeService.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/ConfigServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/FunctionServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/HomePageServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/RoleFunctionServiceImpl.java create mode 100644 carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/ThemeServiceImpl.java create mode 100644 carry_capacity/src/main/resources/application.yml create mode 100644 carry_capacity/src/main/resources/njcn.jks create mode 100644 carry_capacity/target/carry_capacity-1.0.0.jar create mode 100644 carry_capacity/target/carry_capacity-1.0.0.jar.original create mode 100644 carry_capacity/target/classes/application.yml create mode 100644 carry_capacity/target/classes/com/njcn/product/auth/mapper/mapping/UserRoleMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDataPOMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDevicePOMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityResultPOMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyDhlPOMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyPOMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityUserPOMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/DeptLineMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/DeviceMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/LineDetailMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/LineMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/OverlimitMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/TreeMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/VoltageMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/system/dept/mapper/mapping/AreaMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/system/dept/mapper/mapping/DeptMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/system/dict/mapper/mapping/DictDataMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/system/dict/mapper/mapping/DictTypeMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/system/theme/mapper/mapping/ConfigMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/system/theme/mapper/mapping/FunctionMapper.xml create mode 100644 carry_capacity/target/classes/com/njcn/product/system/theme/mapper/mapping/RoleFunctionMapper.xml create mode 100644 carry_capacity/target/classes/njcn.jks create mode 100644 carry_capacity/target/maven-archiver/pom.properties create mode 100644 carry_capacity/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 carry_capacity/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 cn-advance/.gitignore create mode 100644 cn-advance/pom.xml create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/controller/EventRelevantAnalysisController.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RelevantLogMapper.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RmpEventAdvanceMapper.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RmpEventDetailAssMapper.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/mapping/RelevanceMapper.xml create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/mapping/RmpEventDetailAssMapper.xml create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/constant/HarmonicValidMessage.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityGroupData.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityGroupEvtData.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityLogic.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityMtrans.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EventAssObj.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/FinalData.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/PlantInfo.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/SagEvent.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/enums/AdvanceResponseEnum.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/po/PqsRelevanceLog.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/po/RmpEventDetailAssPO.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/EventRelevantAnalysisService.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/HistoryHarmonicService.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/RmpEventDetailAssService.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/EventRelevantAnalysisServiceImpl.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/HistoryHarmonicServiceImpl.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/RmpEventDetailAssServiceImpl.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/eventSource/utils/UtilNormalization.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/controller/HarmonicUpController.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/imapper/DataIUpToMapper.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/imapper/DataVUpToMapper.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/mapper/UpHarmonicDetailMapper.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/param/HistoryParam.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/DataIUp.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/DataVUp.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/UpHarmonicDetail.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/HistoryDataResultVO.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/QueryResultLimitVO.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/UpTableInfo.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/HarmonicUpService.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/HistoryResultService.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/impl/HarmonicUpServiceImpl.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/impl/HistoryResultServiceImpl.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/analysis/CanonicalCorrelationAnalysis.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/calculator/HarmonicCalculationEngine.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/calculator/ResponsibilityCalculator.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/HistoryHarmonicController.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/ResponsibilityController.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/UserDataController.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/imapper/DataHarmP.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespDataMapper.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespDataResultMapper.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespUserDataIntegrityMapper.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespUserDataMapper.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespDataMapper.xml create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespDataResultMapper.xml create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespUserDataIntegrityMapper.xml create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespUserDataMapper.xml create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/model/CacheQvvrData.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HIKSDKStructure.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HKDataStruct.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HarmonicData.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/model/PDataStruct.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/model/QvvrDataEntity.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/model/QvvrStruct.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/DealDataResult.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/DealUserDataResult.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/RespCommon.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/RespHarmData.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/UserDataExcel.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/CalculationMode.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/CalculationStatus.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/HarmonicConstants.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/CustomerData.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/CustomerResponsibility.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/RespDataDTO.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/ResponsibilityResult.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/HistoryHarmParam.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/PHistoryHarmParam.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/RespBaseParam.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/ResponsibilityCalculateParam.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/ResponsibilitySecondCalParam.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/UserDataIntegrityParam.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespData.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespDataResult.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespUserData.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespUserDataIntegrity.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IHarmonicResponsibilityService.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespDataResultService.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespDataService.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespUserDataIntegrityService.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespUserDataService.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/HarmonicResponsibilityServiceImpl.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespDataResultServiceImpl.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespDataServiceImpl.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespUserDataIntegrityServiceImpl.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespUserDataServiceImpl.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/utils/MathUtils.java create mode 100644 cn-advance/src/main/java/com/njcn/product/advance/responsility/utils/ResponsibilityAlgorithm.java create mode 100644 cn-advance/src/test/java/com/njcn/product/advance/CnAdvanceApplicationTests.java create mode 100644 cn-begin/.gitignore create mode 100644 cn-begin/pom.xml create mode 100644 cn-begin/src/main/java/com/njcn/product/begin/CnBeginApplication.java create mode 100644 cn-begin/src/main/resources/application-wuxi_dev.yml create mode 100644 cn-begin/src/main/resources/application-wuxi_prod.yml create mode 100644 cn-begin/src/main/resources/application.yml create mode 100644 cn-begin/src/main/resources/logback.xml create mode 100644 cn-begin/src/test/java/com/njcn/product/begin/CnBeginApplicationTests.java create mode 100644 cn-diagram/.gitignore create mode 100644 cn-diagram/pom.xml create mode 100644 cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/controller/LedgerScaleController.java create mode 100644 cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/dto/EventSourceDTO.java create mode 100644 cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/dto/LedgerScaleDTO.java create mode 100644 cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/vo/EventDetailVO.java create mode 100644 cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/vo/EventLedgerVO.java create mode 100644 cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/service/LedgerScaleService.java create mode 100644 cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/service/impl/LedgerScaleServiceImpl.java create mode 100644 cn-diagram/src/main/java/com/njcn/product/diagram/job/CustomJob.java create mode 100644 cn-diagram/src/main/java/com/njcn/product/diagram/websocket/WebSocketConfig.java create mode 100644 cn-diagram/src/main/java/com/njcn/product/diagram/websocket/WebSocketServer.java create mode 100644 cn-terminal/.gitignore create mode 100644 cn-terminal/pom.xml create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqDeviceDetailMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqDeviceMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqGdCompanyMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqLineMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqLinedetailMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqSubstationMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqsDeptslineMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqsStationMapMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqDeviceMapper.xml create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqLineMapper.xml create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqSubstationMapper.xml create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/DeviceDTO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/DeviceDeptDTO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/LedgerBaseInfoDTO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/OracleLedgerTreeDTO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/PqsDeptDTO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/SubstationDTO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqDevice.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqDeviceDetail.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqGdCompany.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqLine.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqLinedetail.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqSubstation.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqsDeptsline.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqsStationMap.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/service/LedgerTreeService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqDeviceService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqLineService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqSubstationService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqsDeptslineService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/LedgerTreeServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqDeviceServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqLineServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqSubstationServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqsDeptslineServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/event/controller/EventGateController.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/event/pojo/param/MonitorTerminalParam.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/event/service/EventGateService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/event/service/impl/EventGateServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/controller/LedgerTreeController.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/controller/LineController.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/DeptLineMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/DeviceMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LedgerScaleMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LineDetailMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LineMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/OverlimitMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/RmpEventDetailMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/TreeMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/UserReportPOMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/VoltageMapper.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/DeptLineMapper.xml create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/DeptMapper.xml create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/LedgerScaleMapper.xml create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/LineMapper.xml create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/TreeMapper.xml create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/constant/ValidMessage.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeptGetBase.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeptGetChildrenMoreDTO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeviceType.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/GeneralDeviceDTO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LedgerBaseInfo.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LedgerTreeDTO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LineDevGetDTO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LineInfo.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/LineBaseEnum.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/LineFlagEnum.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/PowerFlagEnum.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/RunFlagEnum.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/StatisticsEnum.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/DeptGetLineParam.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/DeviceInfoParam.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/LargeScreenCountParam.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/UserReportParam.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/DeptLine.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Device.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Line.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/LineDetail.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Overlimit.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsDeviceUnit.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgass.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgploy.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgployass.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/RmpEventDetailPO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportNormalPO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportPO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportProjectPO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportRenewalPO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportSensitivePO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportSubstationPO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Voltage.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/AdvanceEventDetailVO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDataVO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDetailDataVO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDetailVO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/TerminalShowVO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/TerminalTree.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/UserLedgerVO.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/CommGeneralService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/CommTerminalService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/DeptLineService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/LineService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/TerminalBaseService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/UserReportPOService.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/CommTerminalServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/DeptLineServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/LineServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/TerminalBaseServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/UserReportPOServiceImpl.java create mode 100644 cn-terminal/src/main/java/com/njcn/product/terminal/utils/TerminalUtils.java create mode 100644 cn-user/.gitignore create mode 100644 cn-user/pom.xml create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/controller/AuthController.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/controller/DeptController.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysFunctionController.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysRoleController.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysUserController.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/DeptMapper.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysFunctionMapper.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysRoleFunctionMapper.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysRoleMapper.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysUserMapper.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysUserRoleMapper.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysFunctionMapper.xml create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysRoleFunctionMapper.xml create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysRoleMapper.xml create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysUserMapper.xml create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysUserRoleMapper.xml create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/FunctionConst.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/RoleConst.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/UserConst.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/UserValidMessage.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/dto/DeptDTO.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/dto/UserDTO.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/enums/UserResponseEnum.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysFunctionParam.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysRoleParam.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysUserParam.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/Dept.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysFunction.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysRole.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysRoleFunction.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysUser.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysUserRole.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/Token.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/DeptAllTreeVO.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/MenuVO.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/MetaVO.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/IDeptService.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysFunctionService.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysRoleFunctionService.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysRoleService.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysUserRoleService.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysUserService.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/DeptServiceImpl.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysFunctionServiceImpl.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysRoleFunctionServiceImpl.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysRoleServiceImpl.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysUserRoleServiceImpl.java create mode 100644 cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysUserServiceImpl.java create mode 100644 cn-zutai/.gitignore create mode 100644 cn-zutai/pom.xml create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/CsConfigurationController.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/CsPagePOController.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/ElementController.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/RealTimeDataController.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsConfigurationMapper.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsElementMapper.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsPagePOMapper.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/mapping/CsConfigurationMapper.xml create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/mapping/CsPagePOMapper.xml create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/AskRealTimeDataDTO.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/RealTimeDataDTO.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/ZuTaiDTO.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/enums/CsSystemResponseEnum.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/CsConfigurationParm.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/CsPageParm.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/ElementParam.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsConfigurationPO.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsElement.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsPagePO.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsConfigurationVO.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsPageVO.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsRtDataVO.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/DataArrayTreeVO.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/ElementsVO.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/RealTimeDataVo.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/CsConfigurationService.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/CsPagePOService.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/IElementService.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/ILineTargetService.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsConfigurationServiceImpl.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsElementServiceImpl.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsPagePOServiceImpl.java create mode 100644 cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/LineTargetServiceImpl.java create mode 100644 event_smart/.gitignore create mode 100644 event_smart/pom.xml create mode 100644 event_smart/src/main/java/com/njcn/product/event/EventSmartApplication.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/config/PqlineCache.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/job/LineCacheJob.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqDeviceDetailMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqDeviceMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqGdCompanyMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqLineMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqLinedetailMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqSubstationMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqsStationMapMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqDeviceMapper.xml create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqLineMapper.xml create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqSubstationMapper.xml create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/DeviceDTO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/DeviceDeptDTO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/LedgerBaseInfoDTO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/PqsDeptDTO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/SubstationDTO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqDevice.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqDeviceDetail.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqGdCompany.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqLine.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqLinedetail.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqSubstation.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqsDeptsline.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqsStationMap.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/service/PqDeviceService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/service/PqLineService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/service/PqSubstationService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/service/PqsDeptslineService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqDeviceServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqLineServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqSubstationServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqsDeptslineServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/report/controller/EasyPoiWordExportController.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/report/pojo/dto/BjCustomReportDTO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/report/pojo/param/ReportExportParam.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/report/service/EasyPoiWordExportService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/report/service/impl/EasyPoiWordExportServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/report/utils/WordTemplate.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/controller/EventGateController.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/controller/EventRightController.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/controller/LargeScreenCountController.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/controller/MsgEventConfigController.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/controller/PqUserLedgerController.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/controller/PqsDicTreeController.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/filter/JwtRequestFilter.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/handler/ControllerUtil.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/handler/GlobalBusinessExceptionHandler.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/handler/SqlExecuteTimeInterceptor.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MessageEventFeedbackMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MsgEventConfigMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MsgEventInfoMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqDevicedetailMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqUserLedgerMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqUserLineAssMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDeptsMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDeptslineMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicDataMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicTreeMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicTypeMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsEventdetailMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsIntegrityMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsOnlinerateMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsUserMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsUserSetMapper.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqDevicedetailMapper.xml create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqUserLedger.xml create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsDeptsMapper.xml create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsDeptslineMapper.xml create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsEventdetailMapper.xml create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsOnlinerateMapper.xml create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/DicTreeEnum.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/constant/RedisConstant.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/enums/DicTypeEnum.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/LargeScreenCountParam.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MessageEventFeedbackParam.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MonitorTerminalParam.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MsgEventConfigParam.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/PqUserLedgerParam.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/SimulationMsgParam.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MessageEventFeedback.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MsgEventConfig.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MsgEventInfo.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqDevicedetailaaa.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqUserLedgerPO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqUserLineAssPO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDepts.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicData.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicTreePO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicType.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsEventdetail.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsIntegrity.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsOnlinerate.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsUser.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsUserSet.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/AlarmAnalysisVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/DeviceCountVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventDetailVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventMsgDetailVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventTrendVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/LedgerCountVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/MapCountVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/PqsDicTreeVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/RegionDevCountVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/SubStationCountVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/UserLedgerStatisticVO.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/security/AuthController.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/security/AuthResponse.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/security/MyUserDetails.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/security/MyUserDetailsService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/security/SecurityConfig.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/CommGeneralService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/EventGateService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/EventRightService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/LargeScreenCountService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/MessageEventFeedbackService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/MsgEventConfigService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/MsgEventInfoService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/PqDevicedetailService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/PqUserLedgerService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsDeptsService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsDicTreeService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsEventdetailService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsOnlinerateService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsUserService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsUsersetService.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/EventGateServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/EventRightServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/LargeScreenCountServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MessageEventFeedbackServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MsgEventConfigServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MsgEventInfoServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqDevicedetailServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqUserLedgerServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsDeptsServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsDicTreeServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsEventdetailServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsOnlinerateServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsUserServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsUsersetServiceImpl.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/utils/JwtUtil.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/websocket/WebSocketConfig.java create mode 100644 event_smart/src/main/java/com/njcn/product/event/transientes/websocket/WebSocketServer.java create mode 100644 event_smart/src/main/resources/application-dev.yml create mode 100644 event_smart/src/main/resources/application-prod.yml create mode 100644 event_smart/src/main/resources/application.yml create mode 100644 event_smart/src/main/resources/logback.xml create mode 100644 event_smart/src/main/resources/template/test.docx create mode 100644 event_smart/src/test/java/com/njcn/product/event/EventSmartApplicationTests.java diff --git a/carry_capacity/pom.xml b/carry_capacity/pom.xml new file mode 100644 index 0000000..742cb4e --- /dev/null +++ b/carry_capacity/pom.xml @@ -0,0 +1,175 @@ + + 4.0.0 + + com.njcn.product + CN_Product + 1.0.0 + + com.njcn + carry_capacity + + + + + + + + + + + + + + + + + + + com.njcn + common-redis + 1.0.0 + + + com.njcn + common-core + + + + + + com.alibaba + fastjson + 1.2.83 + + + + cn.afterturn + easypoi-spring-boot-starter + 4.4.0 + + + + + + + + com.njcn + common-db + ${project.version} + + + + com.njcn + pqs-influx + ${project.version} + + + com.alibaba + easyexcel + 3.0.5 + + + + com.google.guava + guava + 32.1.3-jre + + + + + org.springframework.security.oauth.boot + spring-security-oauth2-autoconfigure + 2.1.2.RELEASE + + + org.springframework.security + spring-security-oauth2-jose + + + + + + com.github.penggle + kaptcha + 2.3.2 + + + org.springframework.boot + spring-boot-starter-validation + 2.3.12.RELEASE + + + + + com.njcn + common-web + ${project.version} + + + common-microservice + com.njcn + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + package + + repackage + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + + + src/main/resources + true + + + **/*.jks + **/*.keystore + **/*.p12 + + + + + src/main/resources + false + + **/*.jks + **/*.keystore + **/*.p12 + + + + + src/main/java + + **/*.xml + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/CarryCapacityApplication.java b/carry_capacity/src/main/java/com/njcn/product/CarryCapacityApplication.java new file mode 100644 index 0000000..4cc42c5 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/CarryCapacityApplication.java @@ -0,0 +1,33 @@ +package com.njcn.product; + +import com.njcn.web.advice.ResponseAdvice; +import com.njcn.web.config.FeignConfig; +import com.njcn.web.exception.GlobalBusinessExceptionHandler; +import com.njcn.web.service.ILogService; +import com.njcn.web.service.impl.LogServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.FilterType; + +@Slf4j +@DependsOn("proxyMapperRegister") +@SpringBootApplication(scanBasePackages = "com.njcn") +@MapperScan("com.njcn.**.mapper") +@ComponentScan( + basePackages = "com.njcn", + excludeFilters = @ComponentScan.Filter( + type = FilterType.ASSIGNABLE_TYPE, + classes ={ FeignConfig.class, ILogService.class, GlobalBusinessExceptionHandler.class, ResponseAdvice.class} + ) +) +public class CarryCapacityApplication { + + public static void main(String[] args) { + SpringApplication.run(CarryCapacityApplication.class, args); + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/config/AuthorizationServerConfig.java b/carry_capacity/src/main/java/com/njcn/product/auth/config/AuthorizationServerConfig.java new file mode 100644 index 0000000..39b7230 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/config/AuthorizationServerConfig.java @@ -0,0 +1,234 @@ +package com.njcn.product.auth.config; + + +import cn.hutool.core.util.StrUtil; + +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.enums.auth.ClientEnum; +import com.njcn.product.auth.filter.CustomClientCredentialsTokenEndpointFilter; +import com.njcn.product.auth.pojo.bo.BusinessUser; +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.product.auth.security.clientdetails.ClientDetailsServiceImpl; +import com.njcn.product.auth.security.granter.CaptchaTokenGranter; +import com.njcn.product.auth.security.granter.PreAuthenticatedUserDetailsService; +import com.njcn.product.auth.security.granter.SmsTokenGranter; +import com.njcn.product.auth.service.UserDetailsServiceImpl; +import com.njcn.redis.utils.RedisUtil; +import com.njcn.web.utils.WebUtil; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.CompositeTokenGranter; +import org.springframework.security.oauth2.provider.TokenGranter; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.TokenEnhancer; +import org.springframework.security.oauth2.provider.token.TokenEnhancerChain; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; + +import java.security.KeyPair; +import java.util.*; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年05月11日 13:16 + */ +@Configuration +@AllArgsConstructor +@EnableAuthorizationServer +public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + + private final AuthenticationManager authenticationManager; + + private final ClientDetailsServiceImpl clientDetailsService; + + private final UserDetailsServiceImpl userDetailsService; + + private final PasswordEncoder passwordEncoder; + + private final RedisUtil redisUtil; + + + /** + * 客户端信息配置 + */ + @Override + @SneakyThrows + public void configure(ClientDetailsServiceConfigurer clients) { + clients.withClientDetails(clientDetailsService); + } + + /** + * 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services) + */ + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) { + TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); + List tokenEnhancers = new ArrayList<>(); + tokenEnhancers.add(tokenEnhancer()); + tokenEnhancers.add(jwtAccessTokenConverter()); + tokenEnhancerChain.setTokenEnhancers(tokenEnhancers); + // 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者 + List granterList = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter())); + + // 添加验证码授权模式授权者 + granterList.add(new CaptchaTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), + endpoints.getOAuth2RequestFactory(), authenticationManager, redisUtil + )); + // 添加短信授权模式授权者 + granterList.add(new SmsTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), + endpoints.getOAuth2RequestFactory(), authenticationManager, redisUtil + )); + //todo... 后续可以扩展更多授权模式,比如:微信小程序、移动app + CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList); + endpoints.authenticationManager(authenticationManager) + .accessTokenConverter(jwtAccessTokenConverter()) + //设置grant_type类型集合 + .tokenEnhancer(tokenEnhancerChain) + .tokenGranter(compositeTokenGranter) + /* + * refresh_token有两种使用方式:重复使用(true)、非重复使用(false),默认为true + * 1.重复使用:access_token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准 + * 2.非重复使用:access_token过期刷新时, refresh_token过期时间延续,在refresh_token有效期内刷新而无需失效再次登录 + */ + .reuseRefreshTokens(true) + .tokenServices(tokenServices(endpoints)); + } + + + + public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) { + TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); + List tokenEnhancers = new ArrayList<>(); + tokenEnhancers.add(tokenEnhancer()); + tokenEnhancers.add(jwtAccessTokenConverter()); + tokenEnhancerChain.setTokenEnhancers(tokenEnhancers); + + DefaultTokenServices tokenServices = new DefaultTokenServices(); + tokenServices.setTokenStore(endpoints.getTokenStore()); + tokenServices.setSupportRefreshToken(true); + tokenServices.setClientDetailsService(clientDetailsService); + tokenServices.setTokenEnhancer(tokenEnhancerChain); + + // 多用户体系下,刷新token再次认证客户端ID和 UserDetailService 的映射Map + Map clientUserDetailsServiceMap = new HashMap<>(16); + + // 系统管理客户端 + clientUserDetailsServiceMap.put(ClientEnum.WEB_CLIENT.getClientId(), userDetailsService); + clientUserDetailsServiceMap.put(ClientEnum.WEB_CLIENT_TEST.getClientId(), userDetailsService); + clientUserDetailsServiceMap.put(ClientEnum.APP_CLIENT.getClientId(), userDetailsService); + clientUserDetailsServiceMap.put(ClientEnum.SCREEN_CLIENT.getClientId(), userDetailsService); + clientUserDetailsServiceMap.put(ClientEnum.WE_CHAT_APP_CLIENT.getClientId(), userDetailsService); + + //todo .. 后面扩展微信小程序、app实现服务 + // 刷新token模式下,重写预认证提供者替换其AuthenticationManager,可自定义根据客户端ID和认证方式区分用户体系获取认证用户信息 + PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider(); + provider.setPreAuthenticatedUserDetailsService(new PreAuthenticatedUserDetailsService<>(clientUserDetailsServiceMap)); + tokenServices.setAuthenticationManager(new ProviderManager(Collections.singletonList(provider))); + return tokenServices; + + } + + /** + * 使用非对称加密算法对token签名 + */ + @Bean + public JwtAccessTokenConverter jwtAccessTokenConverter() { + JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); + converter.setKeyPair(keyPair()); + return converter; + } + + /** + * 从classpath下的密钥库中获取密钥对(公钥+私钥) + */ + @Bean + public KeyPair keyPair() { + KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("njcn.jks"), "njcnpqs".toCharArray()); + return factory.getKeyPair("njcn", "njcnpqs".toCharArray()); + } + + + + + /** + * 自定义认证异常响应数据 + */ + @Bean + public AuthenticationEntryPoint authenticationEntryPoint() { + return (request, response, e) -> { + WebUtil.responseInfo(response, UserResponseEnum.CLIENT_AUTHENTICATION_FAILED.getCode(), UserResponseEnum.CLIENT_AUTHENTICATION_FAILED.getMessage()); + }; + } + + + /** + * JWT内容增强 + */ + @Bean + public TokenEnhancer tokenEnhancer() { + return (accessToken, authentication) -> { + String clientId = authentication.getOAuth2Request().getClientId(); + BusinessUser user = (BusinessUser) authentication.getUserAuthentication().getPrincipal(); + Map map = new HashMap<>(8); + map.put(SecurityConstants.USER_INDEX_KEY, user.getUserIndex()); + map.put(SecurityConstants.USER_TYPE, user.getType()); + map.put(SecurityConstants.USER_NICKNAME_KEY, user.getNickName()); + map.put(SecurityConstants.CLIENT_ID_KEY, clientId); + map.put(SecurityConstants.DEPT_INDEX_KEY, user.getDeptIndex()); + map.put(SecurityConstants.USER_HEAD_KEY, user.getHeadSculpture()); + if (StrUtil.isNotBlank(user.getAuthenticationMethod())) { + map.put(SecurityConstants.AUTHENTICATION_METHOD, user.getAuthenticationMethod()); + } + ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map); + return accessToken; + }; + } + + + /** + * 配置自定义密码认证过滤器 + * @param security . + */ + @Override + public void configure(AuthorizationServerSecurityConfigurer security) { + CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security); + endpointFilter.afterPropertiesSet(); + endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint()); + security.addTokenEndpointAuthenticationFilter(endpointFilter); + + security + .authenticationEntryPoint(authenticationEntryPoint()) + /* .allowFormAuthenticationForClients()*/ //如果使用表单认证则需要加上 + .tokenKeyAccess("permitAll()") + .checkTokenAccess("isAuthenticated()"); + } + + + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setHideUserNotFoundExceptions(false); + provider.setUserDetailsService(userDetailsService); + provider.setPasswordEncoder(passwordEncoder); + return provider; + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/config/WebSecurityConfig.java b/carry_capacity/src/main/java/com/njcn/product/auth/config/WebSecurityConfig.java new file mode 100644 index 0000000..4ce2e43 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/config/WebSecurityConfig.java @@ -0,0 +1,110 @@ +package com.njcn.product.auth.config; + + +import com.njcn.product.auth.filter.AuthGlobalFilter; +import com.njcn.product.auth.security.provider.Sm4AuthenticationProvider; +import com.njcn.product.auth.security.provider.SmsAuthenticationProvider; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +/** + * @author hongawen + */ +@Slf4j +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final UserDetailsService sysUserDetailsService; + + private final Sm4AuthenticationProvider sm4AuthenticationProvider; + + private final SmsAuthenticationProvider smsAuthenticationProvider; + private final AuthGlobalFilter authGlobalFilter; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/user/generateSm2Key","/oauth/token","/theme/getTheme").permitAll() + .antMatchers("/webjars/**","/doc.html","/swagger-resources/**","/v2/api-docs").permitAll() + .anyRequest().authenticated() + .and() + .csrf().disable(); + http.addFilterAfter(authGlobalFilter, UsernamePasswordAuthenticationFilter.class); + + } + + /** + * 认证管理对象 + * + * @throws Exception . + * @return . + */ + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + + + @Override + public void configure(AuthenticationManagerBuilder auth) { + auth.authenticationProvider(daoAuthenticationProvider()); + } + + + + /** + * 重写父类自定义AuthenticationManager 将provider注入进去 + * 当然我们也可以考虑不重写 在父类的manager里面注入provider + */ + @Bean + @Override + protected AuthenticationManager authenticationManager(){ + return new ProviderManager(sm4AuthenticationProvider,smsAuthenticationProvider); + } + + + + /** + * 用户名密码认证授权提供者 + */ + @Bean + public DaoAuthenticationProvider daoAuthenticationProvider() { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(sysUserDetailsService); + provider.setPasswordEncoder(passwordEncoder()); + // 是否隐藏用户不存在异常,默认:true-隐藏;false-抛出异常; + provider.setHideUserNotFoundExceptions(false); + return provider; + } + + /** + * 密码编码器 + *

+ * 委托方式,根据密码的前缀选择对应的encoder,例如:{bcypt}前缀->标识BCYPT算法加密;{noop}->标识不使用任何加密即明文的方式 + * 密码判读 DaoAuthenticationProvider#additionalAuthenticationChecks + */ + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/controller/AuthController.java b/carry_capacity/src/main/java/com/njcn/product/auth/controller/AuthController.java new file mode 100644 index 0000000..277fc40 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/controller/AuthController.java @@ -0,0 +1,222 @@ +package com.njcn.product.auth.controller; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.LogInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.dto.UserTokenInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.common.utils.LogUtil; +import com.njcn.common.utils.sm.DesUtils; +import com.njcn.common.utils.sm.Sm2; +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.product.auth.service.IUserService; +import com.njcn.product.auth.service.UserTokenService; +import com.njcn.product.auth.utils.AuthPubUtil; +import com.njcn.redis.utils.RedisUtil; + +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.RequestUtil; +import com.njcn.web.utils.RestTemplateUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.UriComponentsBuilder; +import springfox.documentation.annotations.ApiIgnore; + +import javax.servlet.http.HttpServletRequest; +import java.net.URI; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.interfaces.RSAPublicKey; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author hongawen + */ +@Api(tags = "认证中心") +@Slf4j +@RestController +@RequestMapping("/oauth") +@AllArgsConstructor +public class AuthController extends BaseController { + + + private final TokenEndpoint tokenEndpoint; + + private final KeyPair keyPair; + + private final RedisUtil redisUtil; + + private final IUserService userService; + + + private final UserTokenService userTokenService; + + + @ApiIgnore + @OperateInfo(info = LogEnum.SYSTEM_SERIOUS, operateType = OperateType.AUTHENTICATE) + @ApiOperation("登录认证") + @ApiImplicitParams({ + @ApiImplicitParam(name = SecurityConstants.GRANT_TYPE, defaultValue = "password", value = "授权模式", required = true), + @ApiImplicitParam(name = SecurityConstants.CLIENT_ID, defaultValue = "njcn", value = "Oauth2客户端ID", required = true), + @ApiImplicitParam(name = SecurityConstants.CLIENT_SECRET, defaultValue = "njcnpqs", value = "Oauth2客户端秘钥", required = true), + @ApiImplicitParam(name = SecurityConstants.REFRESH_TOKEN, value = "刷新token"), + @ApiImplicitParam(name = SecurityConstants.USERNAME, value = "登录用户名"), + @ApiImplicitParam(name = SecurityConstants.PASSWORD, value = "登录密码"), + @ApiImplicitParam(name = SecurityConstants.IMAGE_CODE, value = "图形验证码"), + @ApiImplicitParam(name = SecurityConstants.PHONE, value = "手机号"), + @ApiImplicitParam(name = SecurityConstants.SMS_CODE, value = "短信验证码"), + }) + @PostMapping("/token") + public Object postAccessToken(@ApiIgnore Principal principal, @RequestParam @ApiIgnore Map parameters) throws HttpRequestMethodNotSupportedException { + String methodDescribe = getMethodDescribe("postAccessToken"); + String username = parameters.get(SecurityConstants.USERNAME); + + + String grantType = parameters.get(SecurityConstants.GRANT_TYPE); + if (grantType.equalsIgnoreCase(SecurityConstants.GRANT_CAPTCHA) || grantType.equalsIgnoreCase(SecurityConstants.REFRESH_TOKEN_KEY)) { + username = DesUtils.aesDecrypt(username); + } else if (grantType.equalsIgnoreCase(SecurityConstants.GRANT_SMS_CODE)) { + //短信方式登录,将手机号赋值为用户名 + username = parameters.get(SecurityConstants.PHONE); + } + + + + if (grantType.equalsIgnoreCase(SecurityConstants.REFRESH_TOKEN_KEY)) { + //如果是刷新token,需要去黑名单校验 + userTokenService.judgeRefreshToken(parameters.get(SecurityConstants.REFRESH_TOKEN_KEY)); + } + RequestUtil.saveLoginName(username); + OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody(); + //用户的登录名&密码校验成功后,判断当前该用户是否可以正常使用系统 + if (!grantType.equalsIgnoreCase(SecurityConstants.GRANT_SMS_CODE)) { + userService.judgeUserStatus(username); + } + //登录成功后,记录token信息,并处理踢人效果 + userTokenService.recordUserInfo(oAuth2AccessToken, RequestUtil.getRealIp()); + if (!grantType.equalsIgnoreCase(SecurityConstants.PASSWORD)) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, oAuth2AccessToken, methodDescribe); + } else { + return oAuth2AccessToken; + } + } + + @OperateInfo(info = LogEnum.SYSTEM_SERIOUS, operateType = OperateType.LOGOUT) + @ApiOperation("用户登出系统") + @DeleteMapping("/logout") + public HttpResult logout() { + String methodDescribe = getMethodDescribe("logout"); + String userIndex = RequestUtil.getUserIndex(); + String username = RequestUtil.getUsername(); + LogUtil.njcnDebug(log, "{},用户名为:{}", methodDescribe, username); + String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex; + String onlineUserKey = SecurityConstants.TOKEN_ONLINE_PREFIX + userIndex; + Object onlineTokenInfoOld = redisUtil.getObjectByKey(onlineUserKey); + List blackUsers = (List) redisUtil.getObjectByKey(blackUserKey); + UserTokenInfo userTokenInfo; + if (!Objects.isNull(onlineTokenInfoOld)) { + //清除在线token信息 + redisUtil.delete(onlineUserKey); + userTokenInfo = (UserTokenInfo) onlineTokenInfoOld; + if (CollectionUtils.isEmpty(blackUsers)) { + blackUsers = new ArrayList<>(); + } + blackUsers.add(userTokenInfo); + LocalDateTime refreshTokenExpire = userTokenInfo.getRefreshTokenExpire(); + long lifeTime = Math.abs(refreshTokenExpire.plusMinutes(5L).toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"))); + redisUtil.saveByKeyWithExpire(blackUserKey, blackUsers, lifeTime); + } + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + + + /** + * 文档隐藏该接口 + */ + @ApiIgnore + @ApiOperation("RSA公钥获取接口") + @GetMapping("/getPublicKey") + public Map getPublicKey() { + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAKey key = new RSAKey.Builder(publicKey).build(); + return new JWKSet(key).toJSONObject(); + } + + /** + * 自动登录 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.AUTHENTICATE) + @ApiOperation("自动登录") + @PostMapping("/autoLogin") + @ApiImplicitParam(name = "phone", value = "手机号", required = true, paramType = "query") + @ApiIgnore + public HttpResult autoLogin(@RequestParam String phone) { + String methodDescribe = getMethodDescribe("autoLogin"); + String userUrl = "http://127.0.0.1:10214/oauth/token"; + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(userUrl) + .queryParam("grant_type", "sms_code") + .queryParam("client_id", "njcnapp") + .queryParam("client_secret", "njcnpqs") + .queryParam("phone", phone) + .queryParam("smsCode", "123456789"); + URI uri = builder.build().encode().toUri(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, Objects.requireNonNull(RestTemplateUtil.post(uri, HttpResult.class).getBody()).getData(), methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/generateSm2Key") + @ApiOperation("根据登录名获取公钥") + @ApiImplicitParam(name = "loginName", value = "登录名", required = true) + public HttpResult generateSm2Key(String loginName, @ApiIgnore HttpServletRequest request) { + System.out.println("request1==:" + request); + if (StrUtil.isBlankIfStr(loginName)) { + RequestUtil.saveLoginName(LogInfo.UNKNOWN_USER); + throw new BusinessException(UserResponseEnum.LOGIN_USERNAME_INVALID); + } + String methodDescribe = getMethodDescribe("generateSm2Key"); + loginName = DesUtils.aesDecrypt(loginName); + RequestUtil.saveLoginName(loginName); + Map keyMap; + try { + keyMap = Sm2.getSm2CipHer(); + } catch (NoSuchAlgorithmException e) { + throw new BusinessException(CommonResponseEnum.SM2_CIPHER_ERROR); + } + String publicKey = keyMap.get("publicKey"); + String privateKey = keyMap.get("privateKey"); + String ip = request.getHeader(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP); + + if (redisUtil.hasKey(loginName + ip)) { + //秘钥先删除再添加 + redisUtil.delete(loginName + ip); + } + // 保存私钥到 redis + redisUtil.saveByKeyWithExpire(loginName + ip, privateKey, 5 * 60L); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, publicKey, methodDescribe); + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/controller/JudgeThirdToken.java b/carry_capacity/src/main/java/com/njcn/product/auth/controller/JudgeThirdToken.java new file mode 100644 index 0000000..644f1a2 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/controller/JudgeThirdToken.java @@ -0,0 +1,121 @@ +package com.njcn.product.auth.controller; + +import cn.hutool.json.JSONObject; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.common.utils.LogUtil; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +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.client.RestTemplate; + +import java.util.Objects; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年04月27日 11:22 + */ +@Slf4j +@RestController +@AllArgsConstructor +@Api(tags = "校验第三方token") +@RequestMapping("/judgeToken") +public class JudgeThirdToken extends BaseController { + + /** + * 校验广州超高压token有效性 + * + * @param token token数据 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/guangZhou") + @ApiOperation("校验广州超高压token有效性") + @ApiImplicitParam(name = "token", required = true) + public HttpResult guangZhou(String token) { + RestTemplate restTemplate = new RestTemplate(); + String methodDescribe = getMethodDescribe("guangZhou"); + LogUtil.njcnDebug(log, "{},token:{}", methodDescribe, token); + + // 请求地址 + String url = "http://10.121.17.9:9080/ehv/auth_valid"; + + // 请求头设置,x-www-form-urlencoded格式的数据 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + //提交参数设置 + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("token", token); + + // 组装请求体 + HttpEntity> request = + new HttpEntity<>(map, headers); + + // 发送post请求,并打印结果,以String类型接收响应结果JSON字符串 + String result = restTemplate.postForObject(url, request, String.class); + JSONObject resultJson = new JSONObject(result); + if (Objects.equals(resultJson.getInt("status"), DataStateEnum.ENABLE.getCode())) { + //成功 + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/heBei") + @ApiOperation("校验河北token有效性") + @ApiImplicitParam(name = "token", required = true) + public HttpResult heBei(String token) { + RestTemplate restTemplate = new RestTemplate(); + String methodDescribe = getMethodDescribe("heBei"); + LogUtil.njcnDebug(log, "{},token:{}", methodDescribe, token); + + /* // 请求地址 + String url = "http://dwzyywzt-test.com/baseCenter/oauth2/user/token"; + + // 请求头设置,x-www-form-urlencoded格式的数据 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + //提交参数设置 + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("token", token); + + // 组装请求体 + HttpEntity> request = + new HttpEntity<>(map, headers); + + // 发送post请求,并打印结果,以String类型接收响应结果JSON字符串 + String result = restTemplate.postForObject(url, request, String.class); + JSONObject resultJson = new JSONObject(result); + if (Objects.equals(resultJson.getInt("status"), DataStateEnum.ENABLE.getCode())) { + //成功 + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + }*/ + + + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, "HE_DNZLJCYW", methodDescribe); + + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/controller/KaptchaController.java b/carry_capacity/src/main/java/com/njcn/product/auth/controller/KaptchaController.java new file mode 100644 index 0000000..e0ae932 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/controller/KaptchaController.java @@ -0,0 +1,80 @@ +package com.njcn.product.auth.controller; + +import cn.hutool.core.io.IoUtil; +import com.google.code.kaptcha.Producer; +import com.google.code.kaptcha.util.Config; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.product.auth.utils.AuthPubUtil; +import com.njcn.redis.utils.RedisUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import springfox.documentation.annotations.ApiIgnore; + +import javax.imageio.ImageIO; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.Properties; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年06月04日 15:25 + */ +@Api(tags = "认证中心") +@Slf4j +@Controller +@RequestMapping("/auth") +@AllArgsConstructor +public class KaptchaController { + + private final RedisUtil redisUtil; + + @ApiIgnore + @ApiOperation("获取图形验证码") + @GetMapping("/getImgCode") + public void getImgCode(@ApiIgnore HttpServletResponse resp, @ApiIgnore HttpServletRequest request) { + ServletOutputStream out = null; + try { + out = resp.getOutputStream(); +// resp.setContentType("image/jpeg");"/pqs-auth/auth/getImgCode", + if (null != out) { + Properties props = new Properties(); + Producer kaptchaProducer; + ImageIO.setUseCache(false); + props.put("kaptcha.border", "no"); + props.put("kaptcha.textproducer.font.color", "black"); + /*props.put("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.ShadowGimpy");*/ + /*props.put("kaptcha.noise.impl", "com.sso.utils.ComplexNoise");*/ + props.put("kaptcha.textproducer.char.space", "5"); + props.put("kaptcha.textproducer.char.length", "4"); + Config config = new Config(props); + kaptchaProducer = config.getProducerImpl(); + //此处需要固定采用字母和数字混合 + String capText = AuthPubUtil.getKaptchaText(4); + String userAgent = request.getHeader(HttpHeaders.USER_AGENT); + String ip = request.getHeader(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP); + String key = userAgent + ip; + redisUtil.delete(key); + redisUtil.saveByKeyWithExpire(key, capText, 30*60L); + BufferedImage bi = kaptchaProducer.createImage(capText); + ImageIO.write(bi, "jpg", out); + out.flush(); + } + } catch (IOException ioException) { + log.error("获取图形验证码异常,异常为:{}", ioException.toString()); + } finally { + IoUtil.close(out); + } + + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/controller/UserController.java b/carry_capacity/src/main/java/com/njcn/product/auth/controller/UserController.java new file mode 100644 index 0000000..fa4c4ca --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/controller/UserController.java @@ -0,0 +1,203 @@ +package com.njcn.product.auth.controller; + + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.LogInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.common.utils.LogUtil; +import com.njcn.common.utils.sm.DesUtils; +import com.njcn.common.utils.sm.Sm2; + +import com.njcn.product.auth.pojo.dto.UserDTO; +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.product.auth.pojo.param.UserParam; +import com.njcn.product.auth.pojo.po.User; +import com.njcn.product.auth.pojo.vo.UserVO; +import com.njcn.product.auth.service.IUserService; +import com.njcn.redis.utils.RedisUtil; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.RequestUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import springfox.documentation.annotations.ApiIgnore; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + *

+ * 前端控制器 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Validated +@Slf4j +@RestController +@RequestMapping("/user") +@Api(tags = "用户信息管理") +@AllArgsConstructor +public class UserController extends BaseController { + + private final IUserService userService; + + private final RedisUtil redisUtil; + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getUserByName/{loginName}") + @ApiOperation("根据登录名查询用户信息") + @ApiImplicitParam(name = "loginName", value = "登录名", required = true) + public HttpResult getUserByName(@PathVariable String loginName) { + RequestUtil.saveLoginName(loginName); + String methodDescribe = getMethodDescribe("getUserByName"); + LogUtil.njcnDebug(log, "{},登录名为:{}", methodDescribe, loginName); + UserDTO user = userService.getUserByName(loginName); + if (Objects.isNull(user)) { + throw new BusinessException(UserResponseEnum.LOGIN_WRONG_PWD); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, user, methodDescribe); + } + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @ApiIgnore + @GetMapping("/getUserByPhone/{phone}") + @ApiOperation("根据手机号查询用户信息") + @ApiImplicitParam(name = "phone", value = "手机号", required = true) + public HttpResult getUserByPhone(@PathVariable String phone) { + RequestUtil.saveLoginName(phone); + String methodDescribe = getMethodDescribe("getUserByPhone"); + LogUtil.njcnDebug(log, "{},手机号为:{}", methodDescribe, phone); + UserDTO user = userService.loadUserByPhone(phone); + if (Objects.isNull(user)) { + throw new BusinessException(UserResponseEnum.LOGIN_PHONE_NOT_REGISTER); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, user, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/judgeUserStatus/{loginName}") + @ApiOperation("认证后根据用户名判断用户状态") + @ApiImplicitParam(name = "loginName", value = "登录名", required = true) + public HttpResult judgeUserStatus(@PathVariable String loginName) { + RequestUtil.saveLoginName(loginName); + String methodDescribe = getMethodDescribe("judgeUserStatus"); + LogUtil.njcnDebug(log, "{},登录名为:{}", methodDescribe, loginName); + userService.judgeUserStatus(loginName); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } + + + + + + + /** + * 根据用户id获取用户详情 + * + * @param id 用户id + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getUserById") + @ApiOperation("用户详情") + @ApiImplicitParam(name = "id", value = "用户id", required = true) + public HttpResult getUserById(@RequestParam @Validated String id) { + String methodDescribe = getMethodDescribe("getUserById"); + LogUtil.njcnDebug(log, "{},用户id为:{}", methodDescribe, id); + UserVO userVO = userService.getUserById(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, userVO, methodDescribe); + } + + + + + + + + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/generateSm2Key") + @ApiOperation("根据登录名获取公钥") + @ApiImplicitParam(name = "loginName", value = "登录名", required = true) + public HttpResult generateSm2Key(String loginName, @ApiIgnore HttpServletRequest request) { + System.out.println("request1==:" + request); + if (StrUtil.isBlankIfStr(loginName)) { + RequestUtil.saveLoginName(LogInfo.UNKNOWN_USER); + throw new BusinessException(UserResponseEnum.LOGIN_USERNAME_INVALID); + } + String methodDescribe = getMethodDescribe("generateSm2Key"); + loginName = DesUtils.aesDecrypt(loginName); + RequestUtil.saveLoginName(loginName); + Map keyMap; + try { + keyMap = Sm2.getSm2CipHer(); + } catch (NoSuchAlgorithmException e) { + throw new BusinessException(CommonResponseEnum.SM2_CIPHER_ERROR); + } + String publicKey = keyMap.get("publicKey"); + String privateKey = keyMap.get("privateKey"); + String ip = request.getHeader(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP); + + if (redisUtil.hasKey(loginName + ip)) { + //秘钥先删除再添加 + redisUtil.delete(loginName + ip); + } + // 保存私钥到 redis + redisUtil.saveByKeyWithExpire(loginName + ip, privateKey, 5 * 60L); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, publicKey, methodDescribe); + } + + @OperateInfo(operateType = OperateType.UPDATE, info = LogEnum.SYSTEM_SERIOUS) + @PutMapping("/updateUserLoginErrorTimes/{loginName}") + @ApiOperation("更新用户登录认证密码错误次数") + @ApiImplicitParam(name = "loginName", value = "登录名", required = true) + public HttpResult updateUserLoginErrorTimes(@PathVariable String loginName) { + RequestUtil.saveLoginName(loginName); + String methodDescribe = getMethodDescribe("updateUserLoginErrorTimes"); + LogUtil.njcnDebug(log, "{},登录名为:{}", methodDescribe, loginName); + String result = userService.updateUserLoginErrorTimes(loginName); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + + /** + * 查询所有用户包含管理员 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getAllUserSimpleList") + @ApiOperation("查询所有用户作为下拉框") + public HttpResult getAllUserSimpleList() { + String methodDescribe = getMethodDescribe("getAllUserSimpleList"); + List result = userService.simpleList(true); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + +} + diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/exception/AuthExceptionHandler.java b/carry_capacity/src/main/java/com/njcn/product/auth/exception/AuthExceptionHandler.java new file mode 100644 index 0000000..5f05878 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/exception/AuthExceptionHandler.java @@ -0,0 +1,93 @@ +package com.njcn.product.auth.exception; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.nimbusds.jose.JWSObject; +import com.njcn.common.pojo.constant.LogInfo; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; + +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.product.auth.service.IUserService; +import com.njcn.web.utils.RequestUtil; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年05月17日 12:46 + */ +@Slf4j +@RestControllerAdvice +@RequiredArgsConstructor +@Order(Ordered.HIGHEST_PRECEDENCE) +public class AuthExceptionHandler { + + private final IUserService userService; + +// private final ILogService logService; + + /** + * 用户名和密码非法 + */ + @ExceptionHandler(InvalidGrantException.class) + public HttpResult handleInvalidGrantException(HttpServletRequest httpServletRequest, InvalidGrantException invalidGrantException) { + String loginName = invalidGrantException.getMessage(); + log.error("捕获用户名密码错误"); + String result = userService.updateUserLoginErrorTimes(loginName); + if (result.equals(UserResponseEnum.LOGIN_USER_LOCKED.getMessage())) { +// logService.recodeAuthExceptionLog(invalidGrantException, httpServletRequest, UserResponseEnum.LOGIN_USER_LOCKED.getMessage(), loginName); + return HttpResultUtil.assembleResult(UserResponseEnum.LOGIN_USER_LOCKED.getCode(), null, UserResponseEnum.LOGIN_USER_LOCKED.getMessage()); + } else { +// logService.recodeAuthExceptionLog(invalidGrantException, httpServletRequest, UserResponseEnum.LOGIN_WRONG_PWD.getMessage(), loginName); + return HttpResultUtil.assembleResult(UserResponseEnum.LOGIN_WRONG_PWD.getCode(), null, UserResponseEnum.LOGIN_WRONG_PWD.getMessage()); + } + } + + + /** + * 不支持的认证方式 + *

+ * 不支持的认证方式 目前支持:用户名密码:password、刷新token:refresh-token + */ + @ExceptionHandler(UnsupportedGrantTypeException.class) + public HttpResult unsupportedGrantTypeExceptionException(HttpServletRequest httpServletRequest, UnsupportedGrantTypeException unsupportedGrantTypeException) { + String loginName = RequestUtil.getLoginName(httpServletRequest); +// logService.recodeAuthExceptionLog(unsupportedGrantTypeException, httpServletRequest, UserResponseEnum.UNSUPPORTED_GRANT_TYPE.getMessage(), loginName); + return HttpResultUtil.assembleResult(UserResponseEnum.UNSUPPORTED_GRANT_TYPE.getCode(), null, UserResponseEnum.UNSUPPORTED_GRANT_TYPE.getMessage()); + } + + /** + * oAuth2中token校验异常 + */ + @SneakyThrows + @ExceptionHandler(InvalidTokenException.class) + public HttpResult invalidTokenExceptionException(HttpServletRequest httpServletRequest, InvalidTokenException invalidTokenException) { + final String EXPIRED_KEY = "Invalid refresh token (expired):"; + if (invalidTokenException.getMessage().startsWith(EXPIRED_KEY)) { + String message = invalidTokenException.getMessage(); + message = message.substring(EXPIRED_KEY.length()); + JWSObject jwsObject = JWSObject.parse(message); + String payload = jwsObject.getPayload().toString(); + JSONObject jsonObject = JSONUtil.parseObj(payload); +// logService.recodeAuthExceptionLog(invalidTokenException, httpServletRequest, UserResponseEnum.REFRESH_TOKEN_EXPIRE_JWT.getMessage(), jsonObject.getStr(SecurityConstants.USER_NAME_KEY)); + return HttpResultUtil.assembleResult(UserResponseEnum.REFRESH_TOKEN_EXPIRE_JWT.getCode(), null, UserResponseEnum.REFRESH_TOKEN_EXPIRE_JWT.getMessage()); + } +// logService.recodeAuthExceptionLog(invalidTokenException, httpServletRequest, UserResponseEnum.PARSE_TOKEN_FORBIDDEN_JWT.getMessage(), LogInfo.UNKNOWN_USER); + return HttpResultUtil.assembleResult(UserResponseEnum.PARSE_TOKEN_FORBIDDEN_JWT.getCode(), null, UserResponseEnum.PARSE_TOKEN_FORBIDDEN_JWT.getMessage()); + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/exception/GlobalBusinessExceptionHandler.java b/carry_capacity/src/main/java/com/njcn/product/auth/exception/GlobalBusinessExceptionHandler.java new file mode 100644 index 0000000..3d5abd4 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/exception/GlobalBusinessExceptionHandler.java @@ -0,0 +1,256 @@ +package com.njcn.product.auth.exception; + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.common.utils.LogUtil; +import com.njcn.common.utils.ReflectCommonUtil; +import com.njcn.web.utils.ControllerUtil; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.json.JSONException; +import org.springframework.validation.ObjectError; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.util.NestedServletException; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 全局通用业务异常处理器 + * + * @author hongawen + * @version 1.0.0 + * @date 2021年04月20日 18:04 + */ +@Slf4j +@AllArgsConstructor +@RestControllerAdvice +public class GlobalBusinessExceptionHandler { + + + + private final ThreadPoolExecutor executor = new ThreadPoolExecutor( + 4, 8, 30, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(100), + // 队列满时由主线程执行 + new ThreadPoolExecutor.CallerRunsPolicy() + ); + + + /** + * 捕获业务功能异常,通常为业务数据抛出的异常 + * + * @param businessException 业务异常 + */ + @ExceptionHandler(BusinessException.class) + public HttpResult handleBusinessException(BusinessException businessException) { + String operate = ReflectCommonUtil.getMethodDescribeByException(businessException); + // recodeBusinessExceptionLog(businessException, businessException.getMessage()); + return HttpResultUtil.assembleBusinessExceptionResult(businessException, null, operate); + } + + + /** + * 空指针异常捕捉 + * + * @param nullPointerException 空指针异常 + */ + @ExceptionHandler(NullPointerException.class) + public HttpResult handleNullPointerException(NullPointerException nullPointerException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.NULL_POINTER_EXCEPTION.getMessage(), nullPointerException); + //recodeBusinessExceptionLog(nullPointerException, CommonResponseEnum.NULL_POINTER_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.NULL_POINTER_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(nullPointerException)); + } + + /** + * 算数运算异常 + * + * @param arithmeticException 算数运算异常,由于除数为0引起的异常 + */ + @ExceptionHandler(ArithmeticException.class) + public HttpResult handleArithmeticException(ArithmeticException arithmeticException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.ARITHMETIC_EXCEPTION.getMessage(), arithmeticException); + // recodeBusinessExceptionLog(arithmeticException, CommonResponseEnum.ARITHMETIC_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.ARITHMETIC_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(arithmeticException)); + } + + /** + * 类型转换异常捕捉 + * + * @param classCastException 类型转换异常 + */ + @ExceptionHandler(ClassCastException.class) + public HttpResult handleClassCastException(ClassCastException classCastException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.CLASS_CAST_EXCEPTION.getMessage(), classCastException); + // recodeBusinessExceptionLog(classCastException, CommonResponseEnum.CLASS_CAST_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.CLASS_CAST_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(classCastException)); + } + + + /** + * 索引下标越界异常捕捉 + * + * @param indexOutOfBoundsException 索引下标越界异常 + */ + @ExceptionHandler(IndexOutOfBoundsException.class) + public HttpResult handleIndexOutOfBoundsException(IndexOutOfBoundsException indexOutOfBoundsException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION.getMessage(), indexOutOfBoundsException); + // recodeBusinessExceptionLog(indexOutOfBoundsException, CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(indexOutOfBoundsException)); + } + + /** + * 前端请求后端,请求中参数的媒体方式不支持异常 + * + * @param httpMediaTypeNotSupportedException 请求中参数的媒体方式不支持异常 + */ + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + public HttpResult httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException httpMediaTypeNotSupportedException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION.getMessage(), httpMediaTypeNotSupportedException); + // 然后提取错误提示信息进行返回 + // recodeBusinessExceptionLog(httpMediaTypeNotSupportedException, CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(httpMediaTypeNotSupportedException)); + } + + /** + * 前端请求后端,参数校验异常捕捉 + * RequestBody注解参数异常 + * + * @param methodArgumentNotValidException 参数校验异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public HttpResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException methodArgumentNotValidException) { + // 从异常对象中拿到allErrors数据 + String messages = methodArgumentNotValidException.getBindingResult().getAllErrors() + .stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(";")); + // 然后提取错误提示信息进行返回 + LogUtil.njcnDebug(log, "参数校验异常,异常为:{}", messages); + // recodeBusinessExceptionLog(methodArgumentNotValidException, CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages, ControllerUtil.getMethodArgumentNotValidException(methodArgumentNotValidException)); + } + + /** + * 前端请求后端,参数校验异常捕捉 + * PathVariable注解、RequestParam注解参数异常 + * + * @param constraintViolationException 参数校验异常 + */ + @ExceptionHandler(ConstraintViolationException.class) + public HttpResult constraintViolationExceptionExceptionHandler(ConstraintViolationException constraintViolationException) { + String exceptionMessage = constraintViolationException.getMessage(); + StringBuilder messages = new StringBuilder(); + if (exceptionMessage.indexOf(StrUtil.COMMA) > 0) { + String[] tempMessage = exceptionMessage.split(StrUtil.COMMA); + Stream.of(tempMessage).forEach(message -> { + messages.append(message.substring(message.indexOf(StrUtil.COLON) + 2)).append(';'); + }); + } else { + messages.append(exceptionMessage.substring(exceptionMessage.indexOf(StrUtil.COLON) + 2)); + } + // 然后提取错误提示信息进行返回 + LogUtil.njcnDebug(log, "参数校验异常,异常为:{}", messages); + // recodeBusinessExceptionLog(constraintViolationException, CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION.getMessage()); + List> constraintViolationList = new ArrayList<>(constraintViolationException.getConstraintViolations()); + ConstraintViolation constraintViolation = constraintViolationList.get(0); + Class rootBeanClass = constraintViolation.getRootBeanClass(); + //判断校验参数异常捕获的根源是controller还是service处 + if (rootBeanClass.getName().endsWith("Controller")) { + String methodName = constraintViolation.getPropertyPath().toString().substring(0, constraintViolation.getPropertyPath().toString().indexOf(StrUtil.DOT)); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages.toString(), ReflectCommonUtil.getMethodDescribeByClassAndMethodName(rootBeanClass, methodName)); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages.toString(), ReflectCommonUtil.getMethodDescribeByException(constraintViolationException)); + } + + } + + + /** + * 索引下标越界异常捕捉 + * + * @param illegalArgumentException 参数校验异常 + */ + @ExceptionHandler(IllegalArgumentException.class) + public HttpResult handleIndexOutOfBoundsException(IllegalArgumentException illegalArgumentException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION.getMessage(), illegalArgumentException); + // recodeBusinessExceptionLog(illegalArgumentException, CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION, illegalArgumentException.getMessage(), ReflectCommonUtil.getMethodDescribeByException(illegalArgumentException)); + } + + + /** + * 未声明异常捕捉 + * + * @param exception 未声明异常 + */ + @ExceptionHandler(Exception.class) + public HttpResult handleException(Exception exception) { + //针对fallbackFactory降级异常特殊处理 + Exception tempException = exception; + String exceptionCause = CommonResponseEnum.UN_DECLARE.getMessage(); + String code = CommonResponseEnum.UN_DECLARE.getCode(); + if (exception instanceof NestedServletException) { + Throwable cause = exception.getCause(); + if (cause instanceof AssertionError) { + if (cause.getCause() instanceof BusinessException) { + tempException = (BusinessException) cause.getCause(); + BusinessException tempBusinessException = (BusinessException) cause.getCause(); + exceptionCause = tempBusinessException.getMessage(); + code = tempBusinessException.getCode(); + } + } + } + LogUtil.logExceptionStackInfo(exceptionCause, tempException); + // recodeBusinessExceptionLog(exception, exceptionCause); + //判断方法上是否有自定义注解,做特殊处理 +// Method method = ReflectCommonUtil.getMethod(exception); +// if (!Objects.isNull(method)){ +// if(method.isAnnotationPresent(ReturnMsg.class)){ +// return HttpResultUtil.assembleResult(code, null, StrFormatter.format("{}",exceptionCause)); +// } +// } + return HttpResultUtil.assembleResult(code, null, StrFormatter.format("{}{}{}", ReflectCommonUtil.getMethodDescribeByException(tempException), StrUtil.C_COMMA, exceptionCause)); + } + + + /** + * json解析异常 + * + * @param jsonException json参数 + */ + @ExceptionHandler(JSONException.class) + public HttpResult handleIndexOutOfBoundsException(JSONException jsonException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.JSON_CONVERT_EXCEPTION.getMessage(), jsonException); + // recodeBusinessExceptionLog(jsonException, CommonResponseEnum.JSON_CONVERT_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.JSON_CONVERT_EXCEPTION, jsonException.getMessage(), ReflectCommonUtil.getMethodDescribeByException(jsonException)); + } +/* + private void recodeBusinessExceptionLog(Exception businessException, String methodDescribe) { + HttpServletRequest httpServletRequest = HttpServletUtil.getRequest(); + Future future = executor.submit(() -> { + HttpServletUtil.setRequest(httpServletRequest); + sysLogAuditService.recodeBusinessExceptionLog(businessException, methodDescribe); + }); + try { + // 抛出 ExecutionException + future.get(); + } catch (ExecutionException | InterruptedException e) { + log.error("保存审计日志异常,异常为:" + e.getMessage()); + } + }*/ + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/filter/AuthGlobalFilter.java b/carry_capacity/src/main/java/com/njcn/product/auth/filter/AuthGlobalFilter.java new file mode 100644 index 0000000..a66ecfc --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/filter/AuthGlobalFilter.java @@ -0,0 +1,207 @@ +package com.njcn.product.auth.filter; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.nimbusds.jose.JWSObject; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.dto.UserTokenInfo; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; + +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.redis.utils.RedisUtil; +import com.njcn.web.utils.IpUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.util.Strings; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.time.LocalDateTime; +import java.util.*; + +/** + * 全局过滤器 + * + * @author hongawen + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class AuthGlobalFilter extends OncePerRequestFilter { + + private final static List REPALCEURL = Arrays.asList("/user-boot","/system-boot","/device-boot","/advance-boot"); + + private final RedisUtil redisUtil; + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + ServerHttpRequest serverHttpRequest = new ServletServerHttpRequest(request); + + Map customHeaders = new HashMap<>(); + String realIp = IpUtils.getRealIpAddress(serverHttpRequest); + String token = request.getHeader(SecurityConstants.AUTHORIZATION_KEY); + String path = request.getRequestURI(); + for (String s : REPALCEURL) { + if(path.contains(s)){ + path = path.replace(s,""); + } + } + if (StrUtil.isBlank(token) || !token.startsWith(SecurityConstants.AUTHORIZATION_PREFIX)) { + customHeaders.put(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP, realIp); + chain.doFilter(new ModifiedHttpServletRequest(request, path, customHeaders), response); + return; + + } + token = token.replace(SecurityConstants.AUTHORIZATION_PREFIX, Strings.EMPTY); + JWSObject jwsObject = null; + try { + jwsObject = JWSObject.parse(token); + } catch (ParseException e) { + sendErrorResponse(response,CommonResponseEnum.PARSE_TOKEN_ERROR); + return; + } + String payload = jwsObject.getPayload().toString(); + JSONObject jsonObject = JSONUtil.parseObj(payload); + String userIndex = jsonObject.getStr(SecurityConstants.USER_INDEX_KEY); + String jti = jsonObject.getStr(SecurityConstants.JWT_JTI); + String exp = jsonObject.getStr(SecurityConstants.JWT_EXP); + LocalDateTime expTime = LocalDateTimeUtil.of(Long.parseLong(exp + "000")); + if(expTime.isBefore(LocalDateTimeUtil.now())){ + sendErrorResponse(response,CommonResponseEnum.TOKEN_EXPIRE_JWT); + return; + } + String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex; + List blackUsers = (List) redisUtil.getObjectByKey(blackUserKey); + if (CollectionUtils.isNotEmpty(blackUsers)) { + for (UserTokenInfo blackUser : blackUsers) { + //存在当前的刷新token,则抛出业务异常 + if(blackUser.getAccessTokenJti().equalsIgnoreCase(jti)){ + sendErrorResponse(response,CommonResponseEnum.TOKEN_EXPIRE_JWT); + return; + } + } + } + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(request.getUserPrincipal(),null,null); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + + customHeaders.put(SecurityConstants.JWT_PAYLOAD_KEY, URLEncoder.encode(payload, StandardCharsets.UTF_8.toString())); + customHeaders.put(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP, realIp); + chain.doFilter(new ModifiedHttpServletRequest(request, path, customHeaders), response); + + } + + private void sendErrorResponse(HttpServletResponse response, CommonResponseEnum error) throws IOException { + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("application/json;charset=UTF-8"); + response.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Cache-Control", "no-cache"); + HttpResult httpResult = new HttpResult<>(); + httpResult.setCode(error.getCode()); + httpResult.setMessage(error.getMessage()); + + response.getWriter().write(new JSONObject(httpResult, false).toString()); + } + + // 自定义HttpServletRequest包装器 + private static class ModifiedHttpServletRequest extends HttpServletRequestWrapper { + private final String modifiedUri; + + public ModifiedHttpServletRequest(HttpServletRequest request, String modifiedUri, Map customHeaders) { + super(request); + this.modifiedUri = modifiedUri; + this.customHeaders = customHeaders; + } + + @Override + public String getRequestURI() { + return modifiedUri; + } + + @Override + public String getServletPath() { + return modifiedUri; + } + + private final Map customHeaders; + + + // 添加/修改Header + public void putHeader(String name, String value) { + this.customHeaders.put(name, value); + } + + @Override + public String getHeader(String name) { + String customValue = customHeaders.get(name); + return (customValue != null) ? customValue : super.getHeader(name); + } + + @Override + public Enumeration getHeaders(String name) { + if (customHeaders.containsKey(name)) { + return Collections.enumeration(Collections.singletonList(customHeaders.get(name))); + } + return super.getHeaders(name); + } + + @Override + public Enumeration getHeaderNames() { + Set names = new HashSet<>(customHeaders.keySet()); + Enumeration originalNames = super.getHeaderNames(); + while (originalNames.hasMoreElements()) { + names.add(originalNames.nextElement()); + } + return Collections.enumeration(names); + } + } + + public static String getClientIp(HttpServletRequest request) { + // 1. 优先从代理头获取 + String[] headerNames = { + "X-Forwarded-For", // 标准代理头 + "Proxy-Client-IP", // Apache 代理 + "WL-Proxy-Client-IP", // WebLogic 代理 + "HTTP_X_FORWARDED_FOR", + "HTTP_CLIENT_IP" + }; + + for (String header : headerNames) { + String ip = request.getHeader(header); + if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { + // X-Forwarded-For 可能有多个IP(client, proxy1, proxy2) + if (ip.contains(",")) { + ip = ip.split(",")[0].trim(); // 取第一个IP + } + return ip; + } + } + + // 2. 没有代理头时直接获取 + return request.getRemoteAddr(); + } + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/filter/CustomClientCredentialsTokenEndpointFilter.java b/carry_capacity/src/main/java/com/njcn/product/auth/filter/CustomClientCredentialsTokenEndpointFilter.java new file mode 100644 index 0000000..a6c7f53 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/filter/CustomClientCredentialsTokenEndpointFilter.java @@ -0,0 +1,42 @@ +package com.njcn.product.auth.filter; + + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter; +import org.springframework.security.web.AuthenticationEntryPoint; + +/** + * @author hongawen + * @version 1.0.0 + * @createTime 2021年05月24日 15:39 + */ +public class CustomClientCredentialsTokenEndpointFilter extends ClientCredentialsTokenEndpointFilter { + + private final AuthorizationServerSecurityConfigurer configurer; + + private AuthenticationEntryPoint authenticationEntryPoint; + + + public CustomClientCredentialsTokenEndpointFilter(AuthorizationServerSecurityConfigurer configurer) { + this.configurer = configurer; + } + + @Override + public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { + super.setAuthenticationEntryPoint(null); + this.authenticationEntryPoint = authenticationEntryPoint; + } + + @Override + protected AuthenticationManager getAuthenticationManager() { + return configurer.and().getSharedObject(AuthenticationManager.class); + } + + @Override + public void afterPropertiesSet() { + setAuthenticationFailureHandler((request, response, e) -> authenticationEntryPoint.commence(request, response, e)); + setAuthenticationSuccessHandler((request, response, authentication) -> { + }); + } +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/mapper/AuthClientMapper.java b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/AuthClientMapper.java new file mode 100644 index 0000000..bd908e7 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/AuthClientMapper.java @@ -0,0 +1,16 @@ +package com.njcn.product.auth.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.auth.pojo.po.AuthClient; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-15 + */ +public interface AuthClientMapper extends BaseMapper { + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/mapper/RoleMapper.java b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/RoleMapper.java new file mode 100644 index 0000000..06a9b40 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/RoleMapper.java @@ -0,0 +1,22 @@ +package com.njcn.product.auth.mapper; + + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.auth.pojo.po.Role; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface RoleMapper extends BaseMapper { + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserMapper.java b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserMapper.java new file mode 100644 index 0000000..cf59d98 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserMapper.java @@ -0,0 +1,23 @@ +package com.njcn.product.auth.mapper; + + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.auth.pojo.po.User; +import com.njcn.product.auth.pojo.vo.UserVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface UserMapper extends BaseMapper { + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserRoleMapper.java b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserRoleMapper.java new file mode 100644 index 0000000..940521c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserRoleMapper.java @@ -0,0 +1,29 @@ +package com.njcn.product.auth.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.auth.pojo.po.Role; +import com.njcn.product.auth.pojo.po.UserRole; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface UserRoleMapper extends BaseMapper { + + /** + * 根据用户id获取角色详情 + * @param userId 用户id + * @author cdf + * @date 2022/9/8 + * @return 角色结果集 + */ + List getRoleListByUserId(String userId); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserSetMapper.java b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserSetMapper.java new file mode 100644 index 0000000..8aba0a1 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserSetMapper.java @@ -0,0 +1,17 @@ +package com.njcn.product.auth.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.auth.pojo.po.UserSet; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface UserSetMapper extends BaseMapper { + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserStrategyMapper.java b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserStrategyMapper.java new file mode 100644 index 0000000..464bef7 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/UserStrategyMapper.java @@ -0,0 +1,16 @@ +package com.njcn.product.auth.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.auth.pojo.po.UserStrategy; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface UserStrategyMapper extends BaseMapper { + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/mapper/mapping/UserRoleMapper.xml b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/mapping/UserRoleMapper.xml new file mode 100644 index 0000000..7939cda --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/mapper/mapping/UserRoleMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/bo/BusinessUser.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/bo/BusinessUser.java new file mode 100644 index 0000000..dcc0b35 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/bo/BusinessUser.java @@ -0,0 +1,96 @@ +package com.njcn.product.auth.pojo.bo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; + +/** + * @author hongawen + * @version 1.0.0 + * @createTime 2021年04月28日 13:31 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BusinessUser implements UserDetails { + + private String userIndex; + + private String username; + + private String nickName; + + private String password; + + private String clientId; + + private String deptIndex; + + private Collection authorities; + + private boolean accountNonExpired; + + private boolean accountNonLocked; + + private boolean credentialsNonExpired; + + private boolean enabled; + + private String secretKey; + + private String standBy; + + private String authenticationMethod; + + private Integer type; + + private String headSculpture; + + @Override + public String getPassword() { + return this.password; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public Collection getAuthorities(){ + return authorities; + } + + + public BusinessUser(String username, String password, List authorities) { + this.username = username; + this.password = password; + this.authorities =authorities; + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/DeptState.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/DeptState.java new file mode 100644 index 0000000..8a37b27 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/DeptState.java @@ -0,0 +1,15 @@ +package com.njcn.product.auth.pojo.constant; + +/** + * @author denghuajun + * @date 2021/12/28 + * + */ +public interface DeptState { + + /** + * 部门状态 0-删除;1-正常;默认正常 + */ + int DELETE = 0; + int ENABLE = 1; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/DeptType.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/DeptType.java new file mode 100644 index 0000000..5d1e499 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/DeptType.java @@ -0,0 +1,16 @@ +package com.njcn.product.auth.pojo.constant; + +/** + * @author denghuajun + * @date 2021/12/28 + * + */ +public interface DeptType { + + /** + * 部门类型 0-非自定义;1-web自定义;2-App自定义 + */ + int UNCUSTOM = 0; + int WEBCUSTOM = 1; + int APPCUSTOM = 2; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/FunctionState.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/FunctionState.java new file mode 100644 index 0000000..ac5242c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/FunctionState.java @@ -0,0 +1,38 @@ +package com.njcn.product.auth.pojo.constant; + +/** + * @author 徐扬 + */ +public interface FunctionState { + + /** + * 权限资源状态 0-删除;1-正常;默认正常 + */ + int DELETE = 0; + + int ENABLE = 1; + + /** + * 顶层父类的pid + */ + String FATHER_PID = "0"; + + /** + * 驾驶舱父类名称 + */ + String DRIVER_NAME = "/home"; + + /** + * 权限资源类型 0-菜单、1-按钮、2-公共资源、3-服务间调用资源、4-tab页面 + */ + int MENU = 0; + + int BUTTON = 1; + + int PUBLIC = 2; + + int IN_SERVICE = 3; + + int TAB = 4; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/HomePageState.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/HomePageState.java new file mode 100644 index 0000000..c556410 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/HomePageState.java @@ -0,0 +1,19 @@ +package com.njcn.product.auth.pojo.constant; + +/** + * @author 徐扬 + */ +public interface HomePageState { + + /** + * 状态 0-删除;1-正常;默认正常 + */ + int DELETE = 0; + + int ENABLE = 1; + + /** + * 默认首页 用户的id + */ + String DEFAULT_USER_ID = "0"; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/RoleType.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/RoleType.java new file mode 100644 index 0000000..a3349a5 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/RoleType.java @@ -0,0 +1,18 @@ +package com.njcn.product.auth.pojo.constant; + +/** + * @author 徐扬 + */ +public interface RoleType { + + /** + * 角色类型 0:超级管理员;1:管理员;2:用户 3:APP角色 + */ + int SUPER_ADMINISTRATOR = 0; + + int ADMINISTRATOR = 1; + + int USER = 2; + + int APP = 3; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserDefaultPassword.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserDefaultPassword.java new file mode 100644 index 0000000..4ba622d --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserDefaultPassword.java @@ -0,0 +1,13 @@ +package com.njcn.product.auth.pojo.constant; + +/** + * @author 徐扬 + */ +public interface UserDefaultPassword { + + /** + * 新增用户初始密码 + */ + String DEFAULT_PASSWORD = "123456"; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserState.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserState.java new file mode 100644 index 0000000..1a19f62 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserState.java @@ -0,0 +1,41 @@ +package com.njcn.product.auth.pojo.constant; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月15日 15:00 + */ +public interface UserState { + + /** + * 用户状态 0:删除;1:正常;2:锁定;3:待审核;4:休眠;5:密码过期 + */ + int DELETE = 0; + int ENABLE = 1; + int LOCKED = 2; + int UNCHECK = 3; + int SLEEP = 4; + int OVERDUE = 5; + + + + /** + * 数据来源:0-外部推送 1-正常新增 + */ + int OUT_ORIGIN = 0; + int NORMAL_ORIGIN = 1; + + + /** + * 密码状态:0-不需要修改 1-需要修改 + */ + int NEEDLESS = 0; + int NEED = 1; + + + /** + * 初始密码错误次数 + */ + int ERROR_PASSWORD_TIMES = 0; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserType.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserType.java new file mode 100644 index 0000000..1285c9b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserType.java @@ -0,0 +1,30 @@ +package com.njcn.product.auth.pojo.constant; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月15日 19:39 + */ +public interface UserType { + + /** + * 用户类型 0:临时用户;1:正式用户 + */ + int CASUAL = 0; + + int OFFICIAL = 1; + + /** + * 用户权限类型 0:超级管理员;1:管理员;2:用户;3:APP用户 + */ + int SUPER_ADMINISTRATOR = 0; + + int ADMINISTRATOR = 1; + + int USER = 2; + + int APP = 3; + + String SUPER_ADMIN = "root"; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserValidMessage.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserValidMessage.java new file mode 100644 index 0000000..4e1063b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/constant/UserValidMessage.java @@ -0,0 +1,73 @@ +package com.njcn.product.auth.pojo.constant; + +/** + * @author xy + * @date 2021/12/29 15:10 + */ +public interface UserValidMessage { + + String ID_NOT_BLANK = "id不能为空,请检查id参数"; + + String USERNAME_NOT_BLANK = "名称不能为空,请检查name参数"; + + String USERNAME_FORMAT_ERROR = "用户名格式错误,需中英文1-16,请检查name参数"; + + String LOGIN_NAME_NOT_BLANK = "登录名不能为空,请检查loginName参数"; + + String LOGIN_NAME_FORMAT_ERROR = "登录名格式错误,需3-16位的英文字母或数字,请检查loginName参数"; + + String PASSWORD_NOT_BLANK = "密码不能为空,请检查password参数"; + + String PASSWORD_FORMAT_ERROR = "密码格式错误,需包含特殊字符字母数字,长度为8-16,请检查password参数"; + + String DEPT_NOT_BLANK = "部门不能为空,请检查deptId参数"; + + String PHONE_FORMAT_ERROR = "电话号码格式错误,请检查phone参数"; + + String EMAIL_FORMAT_ERROR = "邮箱格式错误,请检查email参数"; + + String LIMIT_IP_START_NOT_BLANK = "起始IP不能为空,请检查limitIpStart参数"; + + String LIMIT_IP_START_FORMAT_ERROR = "起始IP格式错误,请检查limitIpStart参数"; + + String LIMIT_IP_END_NOT_BLANK = "结束IP不能为空,请检查limitIpEnd参数"; + + String LIMIT_IP_END_FORMAT_ERROR = "结束IP格式错误,请检查limitIpEnd参数"; + + String LIMIT_TIME_NOT_BLANK = "时间段不能为空,请检查limitTime参数"; + + String CASUAL_USER_NOT_BLANK = "类型不能为空"; + + String SMS_NOTICE_NOT_BLANK = "短信通知不能为空,请检查smsNotice参数"; + + String EMAIL_NOTICE_NOT_BLANK = "邮件通知不能为空,请检查emailNotice参数"; + + String PARAM_FORMAT_ERROR = "参数值非法"; + + String ROLE_NOT_BLANK = "角色不能为空,请检查role参数"; + + String PID_NOT_BLANK = "父节点不能为空,请检查pid参数"; + + String CODE_NOT_BLANK = "资源标识不能为空,请检查code参数"; + + String PATH_NOT_BLANK = "资源路径不能为空,请检查path参数"; + + String PATH_FORMAT_ERROR = "路径格式错误,请检查path参数"; + + String SORT_NOT_BLANK = "排序不能为空,请检查sort参数"; + + String TYPE_NOT_BLANK = "资源类型不能为空,请检查type参数"; + + String LAYOUT_NOT_BLANK = "模板不能为空,请检查layout参数"; + + String FUNCTION_ID_NOT_BLANK = "资源id不能为空,请检查functionId参数"; + + String FUNCTION_ID_FORMAT_ERROR = "资源id格式错误,请检查functionId参数"; + + String FUNCTION_GROUP_NOT_BLANK = "功能数组不能为空,请检查functionGroup参数"; + + String FUNCTION_GROUP_FORMAT_ERROR = "功能数组格式错误,请检查functionGroup参数"; + + String COMPONENT_CODE_NOT_BLANK = "功能组件表示不能为空,请检查code参数"; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/dto/UserDTO.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/dto/UserDTO.java new file mode 100644 index 0000000..6858f5e --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/dto/UserDTO.java @@ -0,0 +1,48 @@ +package com.njcn.product.auth.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年05月08日 15:12 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserDTO { + + private String userIndex; + + private String username; + + private String nickname; + + private String password; + + /** + * 角色集合 + */ + private List roleName; + + /** + * sm4加密秘钥 + */ + private String secretKey; + + /** + * sm4中间过程校验 + */ + private String standBy; + + private String deptIndex; + + private Integer type; + + private String headSculpture; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/enums/UserResponseEnum.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/enums/UserResponseEnum.java new file mode 100644 index 0000000..3f23a10 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/enums/UserResponseEnum.java @@ -0,0 +1,125 @@ +package com.njcn.product.auth.pojo.enums; + +import lombok.Getter; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年04月13日 10:50 + */ +@Getter +public enum UserResponseEnum { + + /** + * A0100 ~ A0199 用于用户模块的枚举 + *

+ * 大致可以分为: + * 登录失败状态码 A0101 + 各种登录失败的原因 + * 注册失败状态码 A0103 + 各种注册失败的原因 + * 用户权限升级失败(暂时用于app账号不同角色升级) A0103 + 各种升级app权限失败的原因 + * token校验失败 A0104 + 各种token失败的原因 + * client校验失败 A0105 + 各种客户端失败的原因 + */ + LOGIN_USERNAME_NOT_FOUND("A0101", "用户不存在"), + LOGIN_USERNAME_INVALID("A0101", "用户名非法"), + LOGIN_USER_INDEX_INVALID("A0101", "用户索引非法"), + LOGIN_PHONE_NOT_FOUND("A0101", "手机号不存在"), + LOGIN_PHONE_NOT_REGISTER("A0101", "手机号未注册"), + KEY_WRONG("A0101","登录密码/验证码为空"), + LOGIN_WRONG_PWD("A0101", "用户名密码错误"), + LOGIN_WRONG_PHONE_CODE("A0101", "短信验证码错误"), + LOGIN_WRONG_CODE("A0101", "验证码错误"), + CODE_TYPE_ERROR("A0101","验证码类型非法"), + SEND_CODE_FAIL("A0101","验证码发送失败"), + LOGIN_USER_DELETE("A0101", "账号已被注销"), + LOGIN_USER_OVERLIMIT("A0101", "登陆用户数不能大于配置用户并发量"), + LOGIN_USER_LOCKED("A0102", "账号已被锁定"), + LOGIN_USER_UNAUDITED("A0101", "账号未审核"), + NEED_MODIFY_PASSWORD("A0101", "密码需修改"), + LOGIN_USER_SLEEP("A0101", "账号已休眠"), + LOGIN_USER_PASSWORD_EXPIRED("A0101", "账号密码过期"), + LOGIN_ERROR("A0101", "登录失败"), + LOGIN_FIRST_LOGIN("A0101", "账号首次登录"), + NEED_MODIFY_PWD("A0101", "密码失效,请重置"), + PASSWORD_INVALID("A0101", "密码非法"), + PASSWORD_SET_ERROR("A0101", "密码设置错误"), + LACK_USER_STRATEGY("A0101", "缺失用户策略配置"), + UNSUPPORTED_GRANT_TYPE("A0101", "非法认证方式"), + INVALID_IP("A0101", "非法IP访问系统"), + INVALID_TIME("A0101", "用户当前时间段禁止访问"), + PASSWORD_TRANSPORT_ERROR("A0101", "密码传输完整性被破坏"), + SPECIAL_PASSWORD("A0101", "密码需要包含特殊字符字母数字,长度为8-16"), + APP_PASSWORD("A0101", "密码长度为8-16"), + REPEAT_PASSWORD("A0101", "新密码与旧密码不能一致"), + + MESSAGE_SEND_FAIL("A0102", "短信发送失败"), + REGISTER_FAIL("A0102", "注册失败"), + REGISTER_PHONE_FAIL("A0102", "该号码已注册"), + REGISTER_LOGIN_NAME_FAIL("A0102", "该账号已注册"), + REGISTER_PHONE_WRONG("A0102", "手机号非法"), + REGISTER_PHONE_REPEAT("A0102", "手机号已注册"), + REGISTER_PASSWORD_WRONG("A0102", "账号密码非法"), + DEV_CODE_WRONG("A0102","设备码非法"), + REGISTER_LOGIN_NAME_EXIST("A0102", "该登录名已存在,请检查loginName字段"), + REGISTER_HOMEPAGE_NAME_EXIST("A0102", "该驾驶舱名已存在,请检查name字段"), + FUNCTION_PATH_EXIST("A0102", "菜单路径已存在,请检查path字段"), + COMPONENT_NAME_EXIST("A0102", "组件名已存在,请检查name字段"), + + + UPDATE_ROLE_REFERRAL_CODE_ERROR("A0103", "推荐码非法"), + + PARSE_TOKEN_FORBIDDEN_JWT("A0104", "token已被禁止访问"), + REFRESH_TOKEN_EXPIRE_JWT("A0104", "refresh_token已过期"), + + CLIENT_AUTHENTICATION_FAILED("A0105", "客户端认证失败"), + NOT_FOUND_CLIENT("A0105", "客户端不存在"), + + DEPT_MISSING("A0106", "未找到此部门"), + + DEPT_NODATA("A0107", "部门下暂无用户"), + + BIND_USER_DATA("A0108", "已绑定用户,先解绑用户"), + + CHILD_DEPT_DATA("A0109", "已绑定子部门,先解绑部门"), + + BIND_MONITOR_DATA("A0110", "已绑定监测点,先解绑监测点"), + + BINDING_BUTTON("A0110", "已绑定按钮,先删除按钮"), + + NO_MENU_DATA("A0111","未找到菜单"), + + CHILD_DATA("A0112","数据已绑子节点"), + + BIND_ROLE_DATA("A0113","已有角色绑定,请先解绑"), + + NO_ROLE_DATA("A0114","未找到此角色"), + + BIND_FUNCTION_DATA("A0115","已绑定资源,先解绑资源"), + + DEPT_NAME_REPEAT("A0116","部门名称重复"), + + ROLE_NAME_REPEAT("A0117","角色名称重复"), + DEPT_PID_EXCEPTION("A0118","新增部门父节点信息异常"), + + REFERRAL_CODE_LAPSE("A0119","角色推荐码失效,请联系管理员"), + REFERRAL_CODE_ERROR("A0119","角色推荐码错误,请联系管理员"), + ; + + private final String code; + + private final String message; + + UserResponseEnum(String code, String message) { + this.code = code; + this.message = message; + } + + public static String getCodeByMsg(String msg){ + for (UserResponseEnum userCodeEnum : UserResponseEnum.values()) { + if (userCodeEnum.message.equalsIgnoreCase(msg)) { + return userCodeEnum.code; + } + } + return ""; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/enums/UserStatusEnum.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/enums/UserStatusEnum.java new file mode 100644 index 0000000..3cf6e9b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/enums/UserStatusEnum.java @@ -0,0 +1,32 @@ +package com.njcn.product.auth.pojo.enums; + +import lombok.Getter; + +/** + * @author hongawen + * @version 1.0.0 + * @createTime 2021年05月25日 15:40 + */ +@Getter +public enum UserStatusEnum { + + /** + * 用户状态0:删除;1:正常;2:锁定;3:待审核;4:休眠;5:密码过期 + */ + DESTROY(0, "用户已注销"), + NORMAL(1, "正常"), + LOCKED(2, "用户已经被锁定"), + UNCHECK(3, "用户未审核"), + SLEEP(4, "用户已休眠"), + OVERDUE(5, "用户密码已经过期"); + + private final int code; + + private final String message; + + UserStatusEnum(int code, String message) { + this.code=code; + this.message=message; + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/param/UserParam.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/param/UserParam.java new file mode 100644 index 0000000..9016599 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/param/UserParam.java @@ -0,0 +1,136 @@ +package com.njcn.product.auth.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.product.auth.pojo.constant.UserValidMessage; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.util.List; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2021/12/29 14:56 + */ +@Data +public class UserParam { + + @ApiModelProperty("用户名") + @NotBlank(message = UserValidMessage.USERNAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.USERNAME_REGEX, message = UserValidMessage.USERNAME_FORMAT_ERROR) + private String name; + + @ApiModelProperty("部门id") + private String deptId; + + @ApiModelProperty("电话号码") + @Pattern(regexp = PatternRegex.PHONE_REGEX_OR_NULL, message = UserValidMessage.PHONE_FORMAT_ERROR) + private String phone; + + @ApiModelProperty("邮箱") + @Pattern(regexp = PatternRegex.EMAIL_REGEX_OR_NULL, message = UserValidMessage.EMAIL_FORMAT_ERROR) + private String email; + + @ApiModelProperty("起始IP") + @NotBlank(message = UserValidMessage.LIMIT_IP_START_NOT_BLANK) + @Pattern(regexp = PatternRegex.IP_REGEX, message = UserValidMessage.LIMIT_IP_START_FORMAT_ERROR) + private String limitIpStart; + + @ApiModelProperty("结束IP") + @NotBlank(message = UserValidMessage.LIMIT_IP_END_NOT_BLANK) + @Pattern(regexp = PatternRegex.IP_REGEX, message = UserValidMessage.LIMIT_IP_END_FORMAT_ERROR) + private String limitIpEnd; + + @ApiModelProperty("时间段") + @NotBlank(message = UserValidMessage.LIMIT_TIME_NOT_BLANK) + private String limitTime; + + @ApiModelProperty("用户类型") + @NotNull(message = UserValidMessage.CASUAL_USER_NOT_BLANK) + @Range(min = 0, max = 1, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer casualUser; + + @ApiModelProperty("用户权限类型") + @NotNull(message = UserValidMessage.CASUAL_USER_NOT_BLANK) + @Range(min = 0, max = 2, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer type; + + @ApiModelProperty("短信通知") + @NotNull(message = UserValidMessage.SMS_NOTICE_NOT_BLANK) + @Range(min = 0, max = 1, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer smsNotice; + + @ApiModelProperty("邮件通知") + @NotNull(message = UserValidMessage.EMAIL_NOTICE_NOT_BLANK) + @Range(min = 0, max = 1, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer emailNotice; + + @ApiModelProperty("角色") + @NotEmpty(message = UserValidMessage.ROLE_NOT_BLANK) + private List role; + + @ApiModelProperty("手机识别码") + private String devCode; + + @ApiModelProperty("移动端用户头像") + private String headSculpture; + + /** + * 用户新增操作实体 + * + * 需要填写的参数:登录名、密码 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class UserAddParam extends UserParam { + + @ApiModelProperty("登录名") + @NotBlank(message = UserValidMessage.LOGIN_NAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.LOGIN_NAME_REGEX, message = UserValidMessage.LOGIN_NAME_FORMAT_ERROR) + private String loginName; + + @ApiModelProperty("用户表Id") + @NotNull(message = UserValidMessage.ID_NOT_BLANK) + private String id; + } + + + /** + * 用户更新操作实体 + * + * 需要填写的参数:用户的id + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class UserUpdateParam extends UserParam { + + @ApiModelProperty("用户表Id") + @NotBlank(message = UserValidMessage.ID_NOT_BLANK) + private String id; + } + + /** + * 分页查询实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class UserQueryParam extends BaseParam { + + @ApiModelProperty("用户类型") + @NotNull(message = UserValidMessage.CASUAL_USER_NOT_BLANK) + @Range(min = -1, max = 1, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer casualUser; + } + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/AuthClient.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/AuthClient.java new file mode 100644 index 0000000..8680869 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/AuthClient.java @@ -0,0 +1,81 @@ +package com.njcn.product.auth.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @author hongawen + * @since 2021-12-15 + */ +@Data +@TableName("sys_auth_client") +public class AuthClient { + + private static final long serialVersionUID = 1L; + + /** + * 认证客户端Id + */ + private String id; + + /** + * 客户端名称 + */ + private String name; + + /** + * 资源Id列表 + */ + private String resourceIds; + + /** + * 客户端秘钥 + */ + private String clientSecret; + + /** + * 域 + */ + private String scope; + + /** + * 授权方式 + */ + private String authorizedGrantTypes; + + /** + * 回调地址 + */ + private String webServerRedirectUri; + + /** + * 权限列表 + */ + private String authorities; + + /** + * 认证令牌时效 单位:秒 + */ + private Integer accessTokenValidity; + + /** + * 刷新令牌时效 单位:秒 + */ + private Integer refreshTokenValidity; + + /** + * 扩展信息 + */ + private String additionalInformation; + + /** + * 是否自动放行 0-不放行 1-放行 + */ + private Boolean autoApprove; + + /** + * 状态:0-删除 1-正常 + */ + private Integer state; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/Role.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/Role.java new file mode 100644 index 0000000..b2f6ff3 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/Role.java @@ -0,0 +1,49 @@ +package com.njcn.product.auth.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_role") +public class Role extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 角色表Id + */ + private String id; + + /** + * 角色名称 + */ + private String name; + + /** + * 角色代码,有需要用做匹配时候用(关联字典表id) + */ + private String code; + + /** + * 角色描述 + */ + private String remark; + + /** + * 角色状态0-删除;1-正常;默认正常 + */ + private Integer state; + + /** + * 角色类型0-超级管理员 1-其他管理员 2-其他用户 + */ + private Integer type; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/User.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/User.java new file mode 100644 index 0000000..c1d09e2 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/User.java @@ -0,0 +1,148 @@ +package com.njcn.product.auth.pojo.po; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_user") +public class User extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private String id; + + /** + * 用户名 + */ + private String name; + + /** + * 登录名 + */ + private String loginName; + + /** + * 密码 + */ + private String password; + + /** + * 部门Id + */ + private String deptId; + + /** + * 电话号码 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 用户状态0-删除;1-正常;2-锁定;3-待审核;4-休眠;5-密码过期 + */ + private Integer state; + + /** + * 用户类型 0:超级管理员;1:管理员;2:普通用户 + */ + private Integer type; + + /** + * 数据来源:0-外部推送;1-正常新增;默认正常新增 + */ + private Integer origin; + + /** + * 用户类型:0-临时用户 1-正式用户 + */ + private Integer casualUser; + + /** + * 密码状态:0-不需要修改 1-需要修改 + */ + private Integer pwdState; + + /** + * 短信通知(0-不通知;1-通知)默认不通知 + */ + private Integer smsNotice; + + /** + * 邮件通知(0-不通知;1-通知)默认不通知 + */ + private Integer emailNotice; + + /** + * 推荐码 + */ + private String referralCode; + + /** + * 注册时间 + */ + private LocalDateTime registerTime; + + /** + * 密码有效期字段(初始化的时候跟注册时间一样) + */ + private LocalDateTime pwdValidity; + + /** + * 最后一次登录时间 + */ + private LocalDateTime loginTime; + + /** + * 限制登录起始IP + */ + private String limitIpStart; + + /** + * 限制登录结束IP + */ + private String limitIpEnd; + + /** + * 限制登录时间段(用'-'分割) + */ + private String limitTime; + + /** + * 密码错误次数 + */ + private Integer loginErrorTimes; + + /** + * 首次密码错误时间(半个小时错误次数超过N次数则锁定,解锁后密码错误次数、首次密码错误时间重置) + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime firstErrorTime; + + /** + * 用户密码错误锁定时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime lockTime; + + private String devCode; + + private String headSculpture; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserRole.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserRole.java new file mode 100644 index 0000000..9609bf8 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserRole.java @@ -0,0 +1,28 @@ +package com.njcn.product.auth.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@TableName("sys_user_role") +public class UserRole { + + private static final long serialVersionUID = 1L; + + /** + * 用户Id + */ + private String userId; + + /** + * 角色Id + */ + private String roleId; + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserSet.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserSet.java new file mode 100644 index 0000000..e18d4d5 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserSet.java @@ -0,0 +1,38 @@ +package com.njcn.product.auth.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@TableName("sys_user_set") +public class UserSet { + + private static final long serialVersionUID = 1L; + + /** + * 用户配置表Id + */ + private String id; + + /** + * 用户Id + */ + private String userId; + + /** + * 工作秘钥 + */ + private String secretKey; + + /** + * SM4-1值 + */ + private String standBy; + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserStrategy.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserStrategy.java new file mode 100644 index 0000000..993c5b6 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/po/UserStrategy.java @@ -0,0 +1,72 @@ +package com.njcn.product.auth.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_user_strategy") +public class UserStrategy extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 用户策略Id + */ + private String id; + + /** + * 密码有效期(1-6月,默认3个月) + */ + private Integer limitPwdDate; + + /** + * 密码错误次数限定(3-20次,默认5次) + */ + private Integer limitPwdTimes; + + /** + * 验证密码错误次数时间范围(5-60分钟,默认30分钟) + */ + private Integer lockPwdCheck; + + /** + * 用户锁定时间(30-60分钟,默认30分钟) + */ + private Integer lockPwdTime; + + /** + * 用户休眠(1-180天,默认90天,临时用户默认3天) + */ + private Integer sleep; + + /** + * 用户注销(1-360天,正常用户默认180天,临时用户默认7天) + */ + private Integer logout; + + /** + * 最大并发数(10-99,默认50) + */ + private Integer maxNum; + + /** + * 类型:0-临时用户 1-正常用户 + */ + private Integer type; + + /** + * 状态:0-删除 1-正常 + */ + private Integer state; + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/pojo/vo/UserVO.java b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/vo/UserVO.java new file mode 100644 index 0000000..5a31485 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/pojo/vo/UserVO.java @@ -0,0 +1,64 @@ +package com.njcn.product.auth.pojo.vo; + +import com.njcn.product.auth.pojo.param.UserParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2021/12/29 14:31 + */ +@Data +public class UserVO extends UserParam implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("用户Id") + private String id; + + @ApiModelProperty("用户名") + private String name; + + @ApiModelProperty("登录名") + private String loginName; + + @ApiModelProperty("状态") + private Integer state; + + @ApiModelProperty("注册时间") + private String registerTime; + + @ApiModelProperty("登录时间") + private String loginTime; + + @ApiModelProperty("部门编号") + private String deptId; + + @ApiModelProperty("部门名称") + private String deptName; + + @ApiModelProperty("区域id") + private String areaId; + + @ApiModelProperty("区域名称") + private String areaName; + + @ApiModelProperty("部门层级") + private Integer deptLevel; + + @ApiModelProperty("角色id") + private List roleList; + + @ApiModelProperty("头像") + private String headSculpture; + + @ApiModelProperty("角色编码") + private List roleCode; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/security/clientdetails/ClientDetailsServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/auth/security/clientdetails/ClientDetailsServiceImpl.java new file mode 100644 index 0000000..8689cb1 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/security/clientdetails/ClientDetailsServiceImpl.java @@ -0,0 +1,58 @@ +package com.njcn.product.auth.security.clientdetails; + + +import com.njcn.common.pojo.enums.auth.PasswordEncoderTypeEnum; + +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.auth.pojo.po.AuthClient; +import com.njcn.product.auth.service.IAuthClientService; +import lombok.RequiredArgsConstructor; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.NoSuchClientException; +import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.stereotype.Service; + +/** + * OAuth2 客户端信息 + * @author hongawen + */ +@Service +@RequiredArgsConstructor +public class ClientDetailsServiceImpl implements ClientDetailsService { + + private final IAuthClientService authClientService; + + @Override + public ClientDetails loadClientByClientId(String clientName) { + try { + AuthClient authClient = authClientService.getAuthClientByName(clientName); + BaseClientDetails clientDetails = new BaseClientDetails( + authClient.getName(), + authClient.getResourceIds(), + authClient.getScope(), + authClient.getAuthorizedGrantTypes(), + authClient.getAuthorities(), + authClient.getWebServerRedirectUri() + ); + clientDetails.setClientSecret(PasswordEncoderTypeEnum.BCRYPT.getPrefix() + authClient.getClientSecret()); + clientDetails.setAccessTokenValiditySeconds(authClient.getAccessTokenValidity()); + clientDetails.setRefreshTokenValiditySeconds(authClient.getRefreshTokenValidity()); + return clientDetails; + } catch (EmptyResultDataAccessException var4) { + throw new NoSuchClientException("No client with requested id: " + clientName); + } + } + + public static void main(String[] args) { + PasswordEncoder delegatingPasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); + String njcnpqs = delegatingPasswordEncoder.encode("njcnpqs"); + //{bcrypt}$2a$10$xIP3g5Rc11zDdclsKXpQXuOobvZ9gaw2Mix1rkOm1MJN1.hTVY7ci + System.out.println(njcnpqs); + System.out.println(delegatingPasswordEncoder.matches("njcnpqs","{bcrypt}$2a$10$xIP3g5Rc11zDdclsKXpQXuOobvZ9gaw2Mix1rkOm1MJN1.hTVY7ci")); + + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/security/granter/CaptchaTokenGranter.java b/carry_capacity/src/main/java/com/njcn/product/auth/security/granter/CaptchaTokenGranter.java new file mode 100644 index 0000000..ea19e9e --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/security/granter/CaptchaTokenGranter.java @@ -0,0 +1,108 @@ +package com.njcn.product.auth.security.granter; + +import cn.hutool.core.util.StrUtil; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.utils.sm.DesUtils; +import com.njcn.common.utils.sm.Sm2; +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.redis.utils.RedisUtil; +import com.njcn.web.utils.RequestUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; + +import javax.servlet.http.HttpServletRequest; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月15日 14:23 + */ +@Slf4j +public class CaptchaTokenGranter extends AbstractTokenGranter { + + private final AuthenticationManager authenticationManager; + + private final RedisUtil redisUtil; + + public CaptchaTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, + OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager, + RedisUtil redisUtil + ) { + //SecurityConstants.GRANT_CAPTCHA:申明为验证码模式 + super(tokenServices, clientDetailsService, requestFactory, SecurityConstants.GRANT_CAPTCHA); + this.authenticationManager = authenticationManager; + this.redisUtil = redisUtil; + } + + @Override + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { + Map parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters()); + String username = parameters.get(SecurityConstants.USERNAME); + username = DesUtils.aesDecrypt(username); + String verifyCode = parameters.get(SecurityConstants.VERIFY_CODE); + //判断是否需要校验图形验证码,用户错误后,前端要求用户填写验证码 + if(StrUtil.isEmpty(verifyCode)||verifyCode.equals("1")){ + if (!judgeImageCode(parameters.get(SecurityConstants.IMAGE_CODE), RequestUtil.getRequest())) { + throw new BusinessException(UserResponseEnum.LOGIN_WRONG_CODE); + } + } + String password = parameters.get(SecurityConstants.PASSWORD); + String ip = Objects.requireNonNull(RequestUtil.getRequest()).getHeader(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP); + //密码处理 + String privateKey = redisUtil.getStringByKey(username + ip); + //秘钥用完即删 + redisUtil.delete(username + ip); + //对SM2解密面进行验证 + password = Sm2.getPasswordSM2Verify(privateKey, password); + if (StrUtil.isBlankIfStr(password)) { + throw new BusinessException(UserResponseEnum.PASSWORD_TRANSPORT_ERROR); + } + //1、不将密码放入details内,防止密码泄漏 + parameters.remove(SecurityConstants.PASSWORD); + //2、组装用户密码模式的认证信息 + Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password); + ((AbstractAuthenticationToken) userAuth).setDetails(parameters); + try { + //3、认证组装好的信息 + userAuth = authenticationManager.authenticate(userAuth); + } catch (AccountStatusException | BadCredentialsException ase) { + //covers expired, locked, disabled cases + throw new InvalidGrantException(ase.getMessage()); + } + // If the username/password are wrong the spec says we should send 400/invalid grant + if (userAuth == null || !userAuth.isAuthenticated()) { + throw new InvalidGrantException("无法认证用户: " + username); + } + + OAuth2Request storedOauth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); + return new OAuth2Authentication(storedOauth2Request, userAuth); + } + + /** + * @param imageCode 图形验证码 + */ + private boolean judgeImageCode(String imageCode, HttpServletRequest request) { + if (StrUtil.isBlankIfStr(imageCode)) { + return false; + } + String userAgent = request.getHeader(HttpHeaders.USER_AGENT); + String ip = request.getHeader(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP); + String key = userAgent + ip; + String redisImageCode = redisUtil.getStringByKey(key); + if (imageCode.equalsIgnoreCase(redisImageCode)) { + redisUtil.delete(key); + return true; + } + return false; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/security/granter/PreAuthenticatedUserDetailsService.java b/carry_capacity/src/main/java/com/njcn/product/auth/security/granter/PreAuthenticatedUserDetailsService.java new file mode 100644 index 0000000..28d68a3 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/security/granter/PreAuthenticatedUserDetailsService.java @@ -0,0 +1,56 @@ +package com.njcn.product.auth.security.granter; + +import com.njcn.web.utils.RequestUtil; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.util.Assert; + +import java.util.Map; + +/** + * 刷新token再次认证 UserDetailsService + * + * @author hongawen + * @date 2021/10/2 + */ +@NoArgsConstructor +public class PreAuthenticatedUserDetailsService implements AuthenticationUserDetailsService, InitializingBean { + + /** + * 客户端ID和用户服务 UserDetailService 的映射 + * + */ + private Map userDetailsServiceMap; + + public PreAuthenticatedUserDetailsService(Map userDetailsServiceMap) { + Assert.notNull(userDetailsServiceMap, "userDetailsService cannot be null."); + this.userDetailsServiceMap = userDetailsServiceMap; + } + + @Override + public void afterPropertiesSet() { + Assert.notNull(this.userDetailsServiceMap, "UserDetailsService must be set"); + } + + /** + * 重写PreAuthenticatedAuthenticationProvider 的 preAuthenticatedUserDetailsService 属性,可根据客户端和认证方式选择用户服务 UserDetailService 获取用户信息 UserDetail + * + * @param authentication . + * @return . + * @throws UsernameNotFoundException . + */ + @Override + public UserDetails loadUserDetails(T authentication) throws UsernameNotFoundException { + String clientId = RequestUtil.getOAuth2ClientId(); + // 获取认证方式,默认是用户名 username + UserDetailsService userDetailsService = userDetailsServiceMap.get(clientId); + return userDetailsService.loadUserByUsername(authentication.getName()); + + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/security/granter/SmsTokenGranter.java b/carry_capacity/src/main/java/com/njcn/product/auth/security/granter/SmsTokenGranter.java new file mode 100644 index 0000000..d4b15bc --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/security/granter/SmsTokenGranter.java @@ -0,0 +1,98 @@ +package com.njcn.product.auth.security.granter; + +import cn.hutool.core.util.StrUtil; +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.utils.PubUtils; +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.product.auth.security.token.SmsCodeAuthenticationToken; +import com.njcn.redis.pojo.enums.RedisKeyEnum; +import com.njcn.redis.utils.RedisUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月15日 14:23 + */ +@Slf4j +public class SmsTokenGranter extends AbstractTokenGranter { + + private final AuthenticationManager authenticationManager; + + private final RedisUtil redisUtil; + + public SmsTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, + OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager, + RedisUtil redisUtil + ) { + //SecurityConstants.GRANT_CAPTCHA:申明为手机短信模式 + super(tokenServices, clientDetailsService, requestFactory, SecurityConstants.GRANT_SMS_CODE); + this.authenticationManager = authenticationManager; + this.redisUtil = redisUtil; + } + + @Override + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { + Map parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters()); + String phone = parameters.get(SecurityConstants.PHONE); + String smsCode = parameters.get(SecurityConstants.SMS_CODE); + if (StrUtil.isBlank(phone) || !PubUtils.match(PatternRegex.PHONE_REGEX, phone)) { + throw new BusinessException(UserResponseEnum.REGISTER_PHONE_WRONG); + } + if (!judgeSmsCode(phone, smsCode)) { + throw new BusinessException(UserResponseEnum.LOGIN_WRONG_CODE); + } + //2、组装用户手机号认证信息 + Authentication userAuth = new SmsCodeAuthenticationToken(phone, null); + ((AbstractAuthenticationToken) userAuth).setDetails(parameters); + try { + //3、认证组装好的信息 + userAuth = authenticationManager.authenticate(userAuth); + } catch (AccountStatusException | BadCredentialsException ase) { + throw new InvalidGrantException(ase.getMessage()); + } + if (userAuth == null || !userAuth.isAuthenticated()) { + throw new InvalidGrantException("无法认证用户: " + phone); + } + + OAuth2Request storedOauth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); + return new OAuth2Authentication(storedOauth2Request, userAuth); + } + + /** + * 验证用户的短信验证码是否正确 + * + * @param phone 手机号 + * @param smsCode 用户输入的短信验证码 + * @return boolean + * @author hongawen + * @date 2023/6/14 15:25 + */ + private boolean judgeSmsCode(String phone, String smsCode) { + if (StrUtil.isBlankIfStr(smsCode)) { + return false; + } + String key = RedisKeyEnum.SMS_LOGIN_KEY.getKey().concat(phone); + String redisImageCode = redisUtil.getStringByKey(key); + if (smsCode.equalsIgnoreCase(redisImageCode) || Objects.equals(smsCode,"123456789")) { + redisUtil.delete(key); + return true; + } + return false; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/security/provider/AbstractSmsAuthenticationProvider.java b/carry_capacity/src/main/java/com/njcn/product/auth/security/provider/AbstractSmsAuthenticationProvider.java new file mode 100644 index 0000000..e366a9d --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/security/provider/AbstractSmsAuthenticationProvider.java @@ -0,0 +1,341 @@ +package com.njcn.product.auth.security.provider; + +import com.njcn.product.auth.security.token.SmsCodeAuthenticationToken; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceAware; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; +import org.springframework.security.core.userdetails.UserCache; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsChecker; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.core.userdetails.cache.NullUserCache; +import org.springframework.util.Assert; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年06月15日 10:08 + */ +public abstract class AbstractSmsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { + + protected final Log logger = LogFactory.getLog(getClass()); + + protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + private UserCache userCache = new NullUserCache(); + private boolean forcePrincipalAsString = false; + protected boolean hideUserNotFoundExceptions = true; + private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks(); + private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks(); + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + + // ~ Methods + // ======================================================================================================== + + /** + * Allows subclasses to perform any additional checks of a returned (or cached) + * UserDetails for a given authentication request. Generally a subclass + * will at least compare the {@link Authentication#getCredentials()} with a + * {@link UserDetails#getPassword()}. If custom logic is needed to compare additional + * properties of UserDetails and/or + * SmsCodeAuthenticationToken, these should also appear in this + * method. + * + * @param userDetails as retrieved from the + * {@link #retrieveUser(String, SmsCodeAuthenticationToken)} or + * UserCache + * @param authentication the current request that needs to be authenticated + * + * @throws AuthenticationException AuthenticationException if the credentials could + * not be validated (generally a BadCredentialsException, an + * AuthenticationServiceException) + */ + protected abstract void additionalAuthenticationChecks(UserDetails userDetails, + SmsCodeAuthenticationToken authentication) + throws AuthenticationException; + + @Override + public final void afterPropertiesSet() throws Exception { + Assert.notNull(this.userCache, "A user cache must be set"); + Assert.notNull(this.messages, "A message source must be set"); + doAfterPropertiesSet(); + } + + @Override + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + Assert.isInstanceOf(SmsCodeAuthenticationToken.class, authentication, + () -> messages.getMessage( + "AbstractSmsAuthenticationProvider.onlySupports", + "Only SmsCodeAuthenticationToken is supported")); + + // Determine username + String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" + : authentication.getName(); + + boolean cacheWasUsed = true; + UserDetails user = this.userCache.getUserFromCache(username); + + if (user == null) { + cacheWasUsed = false; + + try { + user = retrieveUser(username, + (SmsCodeAuthenticationToken) authentication); + } + catch (UsernameNotFoundException notFound) { + logger.debug("User '" + username + "' not found"); + + if (hideUserNotFoundExceptions) { + throw new BadCredentialsException(messages.getMessage( + "AbstractSmsAuthenticationProvider.badCredentials", + "Bad credentials")); + } + else { + throw notFound; + } + } + + Assert.notNull(user, + "retrieveUser returned null - a violation of the interface contract"); + } + + try { + preAuthenticationChecks.check(user); + additionalAuthenticationChecks(user, + (SmsCodeAuthenticationToken) authentication); + } + catch (AuthenticationException exception) { + if (cacheWasUsed) { + // There was a problem, so try again after checking + // we're using latest data (i.e. not from the cache) + cacheWasUsed = false; + user = retrieveUser(username, + (SmsCodeAuthenticationToken) authentication); + preAuthenticationChecks.check(user); + additionalAuthenticationChecks(user, + (SmsCodeAuthenticationToken) authentication); + } + else { + throw exception; + } + } + + postAuthenticationChecks.check(user); + + if (!cacheWasUsed) { + this.userCache.putUserInCache(user); + } + + Object principalToReturn = user; + + if (forcePrincipalAsString) { + principalToReturn = user.getUsername(); + } + + return createSuccessAuthentication(principalToReturn, authentication, user); + } + + /** + * Creates a successful {@link Authentication} object. + *

+ * Protected so subclasses can override. + *

+ *

+ * Subclasses will usually store the original credentials the user supplied (not + * salted or encoded passwords) in the returned Authentication object. + *

+ * + * @param principal that should be the principal in the returned object (defined by + * the {@link #isForcePrincipalAsString()} method) + * @param authentication that was presented to the provider for validation + * @param user that was loaded by the implementation + * + * @return the successful authentication token + */ + protected Authentication createSuccessAuthentication(Object principal, + Authentication authentication, UserDetails user) { + // Ensure we return the original credentials the user supplied, + // so subsequent attempts are successful even with encoded passwords. + // Also ensure we return the original getDetails(), so that future + // authentication events after cache expiry contain the details + SmsCodeAuthenticationToken result = new SmsCodeAuthenticationToken( + principal, authentication.getCredentials(), + authoritiesMapper.mapAuthorities(user.getAuthorities())); + result.setDetails(authentication.getDetails()); + + return result; + } + + protected void doAfterPropertiesSet() { + } + + public UserCache getUserCache() { + return userCache; + } + + public boolean isForcePrincipalAsString() { + return forcePrincipalAsString; + } + + public boolean isHideUserNotFoundExceptions() { + return hideUserNotFoundExceptions; + } + + /** + * Allows subclasses to actually retrieve the UserDetails from an + * implementation-specific location, with the option of throwing an + * AuthenticationException immediately if the presented credentials are + * incorrect (this is especially useful if it is necessary to bind to a resource as + * the user in order to obtain or generate a UserDetails). + *

+ * Subclasses are not required to perform any caching, as the + * AbstractSmsAuthenticationProvider will by default cache the + * UserDetails. The caching of UserDetails does present + * additional complexity as this means subsequent requests that rely on the cache will + * need to still have their credentials validated, even if the correctness of + * credentials was assured by subclasses adopting a binding-based strategy in this + * method. Accordingly it is important that subclasses either disable caching (if they + * want to ensure that this method is the only method that is capable of + * authenticating a request, as no UserDetails will ever be cached) or + * ensure subclasses implement + * {@link #additionalAuthenticationChecks(UserDetails, SmsCodeAuthenticationToken)} + * to compare the credentials of a cached UserDetails with subsequent + * authentication requests. + *

+ *

+ * Most of the time subclasses will not perform credentials inspection in this method, + * instead performing it in + * {@link #additionalAuthenticationChecks(UserDetails, SmsCodeAuthenticationToken)} + * so that code related to credentials validation need not be duplicated across two + * methods. + *

+ * + * @param username The username to retrieve + * @param authentication The authentication request, which subclasses may + * need to perform a binding-based retrieval of the UserDetails + * + * @return the user information (never null - instead an exception should + * the thrown) + * + * @throws AuthenticationException if the credentials could not be validated + * (generally a BadCredentialsException, an + * AuthenticationServiceException or + * UsernameNotFoundException) + */ + protected abstract UserDetails retrieveUser(String username, + SmsCodeAuthenticationToken authentication) + throws AuthenticationException; + + public void setForcePrincipalAsString(boolean forcePrincipalAsString) { + this.forcePrincipalAsString = forcePrincipalAsString; + } + + /** + * By default the AbstractSmsAuthenticationProvider throws a + * BadCredentialsException if a username is not found or the password is + * incorrect. Setting this property to false will cause + * UsernameNotFoundExceptions to be thrown instead for the former. Note + * this is considered less secure than throwing BadCredentialsException + * for both exceptions. + * + * @param hideUserNotFoundExceptions set to false if you wish + * UsernameNotFoundExceptions to be thrown instead of the non-specific + * BadCredentialsException (defaults to true) + */ + public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) { + this.hideUserNotFoundExceptions = hideUserNotFoundExceptions; + } + + @Override + public void setMessageSource(MessageSource messageSource) { + this.messages = new MessageSourceAccessor(messageSource); + } + + public void setUserCache(UserCache userCache) { + this.userCache = userCache; + } + + @Override + public boolean supports(Class authentication) { + return (SmsCodeAuthenticationToken.class + .isAssignableFrom(authentication)); + } + + protected UserDetailsChecker getPreAuthenticationChecks() { + return preAuthenticationChecks; + } + + /** + * Sets the policy will be used to verify the status of the loaded + * UserDetails before validation of the credentials takes place. + * + * @param preAuthenticationChecks strategy to be invoked prior to authentication. + */ + public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) { + this.preAuthenticationChecks = preAuthenticationChecks; + } + + protected UserDetailsChecker getPostAuthenticationChecks() { + return postAuthenticationChecks; + } + + public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) { + this.postAuthenticationChecks = postAuthenticationChecks; + } + + public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { + this.authoritiesMapper = authoritiesMapper; + } + + private class DefaultPreAuthenticationChecks implements UserDetailsChecker { + @Override + public void check(UserDetails user) { + if (!user.isAccountNonLocked()) { + logger.debug("User account is locked"); + + throw new LockedException(messages.getMessage( + "AbstractSmsAuthenticationProvider.locked", + "User account is locked")); + } + + if (!user.isEnabled()) { + logger.debug("User account is disabled"); + + throw new DisabledException(messages.getMessage( + "AbstractSmsAuthenticationProvider.disabled", + "User is disabled")); + } + + if (!user.isAccountNonExpired()) { + logger.debug("User account is expired"); + + throw new AccountExpiredException(messages.getMessage( + "AbstractSmsAuthenticationProvider.expired", + "User account has expired")); + } + } + } + + private class DefaultPostAuthenticationChecks implements UserDetailsChecker { + @Override + public void check(UserDetails user) { + if (!user.isCredentialsNonExpired()) { + logger.debug("User account credentials have expired"); + + throw new CredentialsExpiredException(messages.getMessage( + "AbstractSmsAuthenticationProvider.credentialsExpired", + "User credentials have expired")); + } + } + } + } + diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/security/provider/Sm4AuthenticationProvider.java b/carry_capacity/src/main/java/com/njcn/product/auth/security/provider/Sm4AuthenticationProvider.java new file mode 100644 index 0000000..6479a42 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/security/provider/Sm4AuthenticationProvider.java @@ -0,0 +1,92 @@ +package com.njcn.product.auth.security.provider; + +import com.njcn.common.utils.sm.Sm4Utils; +import com.njcn.product.auth.pojo.bo.BusinessUser; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.InternalAuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Component; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年06月08日 15:43 + */ +@Slf4j +@Component +@AllArgsConstructor +public class Sm4AuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { + + private final UserDetailsService userDetailsService; + + + /** + * 校验密码有效性. + * + * @param userDetails 用户详细信息 + * @param authentication 用户登录的密码 + * @throws AuthenticationException . + */ + @Override + protected void additionalAuthenticationChecks( + UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) + throws AuthenticationException { + if (authentication.getCredentials() == null) { + logger.debug("Authentication failed: no credentials provided"); + + throw new BadCredentialsException(messages.getMessage( + "AbstractUserDetailsAuthenticationProvider.badCredentials", + "Bad credentials")); + } + + String presentedPassword = authentication.getCredentials().toString(); + BusinessUser businessUser = (BusinessUser)userDetails; + String secretKey = businessUser.getSecretKey(); + Sm4Utils sm4 = new Sm4Utils(secretKey); + //SM4加密密码 + String sm4PwdOnce = sm4.encryptData_ECB(presentedPassword); + //SM4加密(密码+工作秘钥) + String sm4PwdTwice = sm4.encryptData_ECB(sm4PwdOnce + secretKey); + if(!businessUser.getPassword().equalsIgnoreCase(sm4PwdTwice)){ + throw new BadCredentialsException(messages.getMessage( + "AbstractUserDetailsAuthenticationProvider.badCredentials", + businessUser.getUsername())); + } + } + + /** + * 获取用户 + * + * @param username 用户名 + * @param authentication 认证token + * @throws AuthenticationException . + */ + @Override + protected UserDetails retrieveUser( + String username, UsernamePasswordAuthenticationToken authentication) + throws AuthenticationException { + UserDetails loadedUser = userDetailsService.loadUserByUsername(username); + if (loadedUser == null) { + throw new InternalAuthenticationServiceException( + "UserDetailsService returned null, which is an interface contract violation"); + } + return loadedUser; + } + + + /** + * 授权持久化. + */ + @Override + protected Authentication createSuccessAuthentication(Object principal, + Authentication authentication, UserDetails user) { + return super.createSuccessAuthentication(principal, authentication, user); + } +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/security/provider/SmsAuthenticationProvider.java b/carry_capacity/src/main/java/com/njcn/product/auth/security/provider/SmsAuthenticationProvider.java new file mode 100644 index 0000000..59c6cf6 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/security/provider/SmsAuthenticationProvider.java @@ -0,0 +1,74 @@ +package com.njcn.product.auth.security.provider; + + +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.product.auth.security.token.SmsCodeAuthenticationToken; +import com.njcn.product.auth.service.UserDetailsServiceImpl; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +/** + * 手机短信码验证完后,返回用户的 + * @author hongawen + * @version 1.0.0 + * @date 2021年06月08日 15:43 + */ +@Slf4j +@Component +@AllArgsConstructor +public class SmsAuthenticationProvider extends AbstractSmsAuthenticationProvider { + + private final UserDetailsServiceImpl userDetailsService; + + + /** + * 校验密码有效性. + * 因为手机号验证码登录,验证码没问题后,密码无需校验,直接返回该用户的token信息便可以 + * + * @param userDetails 用户详细信息 + * @param authentication 用户登录的密码 + * @throws AuthenticationException . + */ + @Override + protected void additionalAuthenticationChecks( + UserDetails userDetails, SmsCodeAuthenticationToken authentication) + throws AuthenticationException { + + } + + /** + * 获取用户 + * + * @param phone 手机号 + * @param authentication 认证token + * @throws AuthenticationException . + */ + @Override + protected UserDetails retrieveUser( + String phone, SmsCodeAuthenticationToken authentication) + throws AuthenticationException { + //根据手机号获取用户信息 + UserDetails loadedUser = userDetailsService.loadUserByPhone(phone); + if (loadedUser == null) { + throw new BusinessException(UserResponseEnum.LOGIN_PHONE_NOT_REGISTER); + } + return loadedUser; + } + + + /** + * 授权持久化. + */ + @Override + protected Authentication createSuccessAuthentication(Object principal, + Authentication authentication, UserDetails user) { + return super.createSuccessAuthentication(principal, authentication, user); + } + + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/security/token/SmsCodeAuthenticationToken.java b/carry_capacity/src/main/java/com/njcn/product/auth/security/token/SmsCodeAuthenticationToken.java new file mode 100644 index 0000000..a2c2cf0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/security/token/SmsCodeAuthenticationToken.java @@ -0,0 +1,62 @@ +package com.njcn.product.auth.security.token; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.util.Assert; + +import java.util.Collection; + +/** + * UsernamePasswordAuthenticationToken 一样, + * 继承 AbstractAuthenticationToken 抽象类, + * 需要实现 getPrincipal 和 getCredentials 两个方法。 + * 在用户名/密码认证中,principal 表示用户名, + * credentials 表示密码,在此,我们可以让它们指代手机号和验证码。 + * + * @author hongawen + * @version 1.0.0 + * @date 2023年06月14日 16:25 + */ +public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken { + + private final Object principal; + + private Object credentials; + + public SmsCodeAuthenticationToken(Object principal, Object credentials) { + super(null); + this.principal = principal; + this.credentials = credentials; + setAuthenticated(false); + } + public SmsCodeAuthenticationToken(Object principal, Object credentials, + Collection authorities) { + super(authorities); + this.principal = principal; + this.credentials = credentials; + super.setAuthenticated(true); + } + + @Override + public Object getCredentials() { + return this.credentials; + } + + @Override + public Object getPrincipal() { + return this.principal; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + Assert.isTrue(!isAuthenticated, + "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); + super.setAuthenticated(false); + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + this.credentials = null; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/CustomUserDetailsService.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/CustomUserDetailsService.java new file mode 100644 index 0000000..2d8846c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/CustomUserDetailsService.java @@ -0,0 +1,27 @@ +package com.njcn.product.auth.service; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年06月15日 10:26 + */ +public interface CustomUserDetailsService extends UserDetailsService { + + /** + * @param username 用户名 + * @return 用户信息 + */ + @Override + UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; + + /** + * @param phone 手机号 + * @return 用户信息 + */ + UserDetails loadUserByPhone(String phone) throws UsernameNotFoundException; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/IAuthClientService.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/IAuthClientService.java new file mode 100644 index 0000000..dddb13f --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/IAuthClientService.java @@ -0,0 +1,22 @@ +package com.njcn.product.auth.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.auth.pojo.po.AuthClient; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-15 + */ +public interface IAuthClientService extends IService { + + /** + * 根据客户端名称获取客户端 + * @param clientName 客户端名称 + * @return . + */ + AuthClient getAuthClientByName(String clientName); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/IRoleService.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/IRoleService.java new file mode 100644 index 0000000..db4ac71 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/IRoleService.java @@ -0,0 +1,26 @@ +package com.njcn.product.auth.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.auth.pojo.po.Role; + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IRoleService extends IService { + + /** + * 根据用户id获取角色名 + * @param id 用户id + * @return 角色名集合 + */ + List getRoleNameByUserId(String id); + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserRoleService.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserRoleService.java new file mode 100644 index 0000000..797d658 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserRoleService.java @@ -0,0 +1,30 @@ +package com.njcn.product.auth.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.auth.pojo.po.UserRole; + + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IUserRoleService extends IService { + + /** + * 根据用户索引获取 用户--角色关系数据 + * @param id 用户ID + * @return 关系数据 + */ + List getUserRoleByUserId(String id); + + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserService.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserService.java new file mode 100644 index 0000000..bef36d8 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserService.java @@ -0,0 +1,61 @@ +package com.njcn.product.auth.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; + +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.auth.pojo.dto.UserDTO; +import com.njcn.product.auth.pojo.param.UserParam; +import com.njcn.product.auth.pojo.po.User; +import com.njcn.product.auth.pojo.vo.UserVO; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IUserService extends IService { + + /** + * 根据登录名获取用户信息 + * @param loginName 登录名 + * @return 用户信息 + */ + UserDTO getUserByName(String loginName); + + /** + * 认证结束后,判断用户状态是否能正常访问系统 + * @param loginName 登录名 + */ + void judgeUserStatus(String loginName);//used + + + /** + * 功能描述:修改用户登录认证密码错误次数 + * @param loginName 登录名 + * @return boolean + * @author xy + */ + String updateUserLoginErrorTimes(String loginName);//used + + + UserDTO loadUserByPhone(String phone); + + /** + * 功能描述:根据用户id获取用户详情 + * TODO + * + * @param id + * @return com.njcn.user.pojo.vo.UserVO + * @author xy + * @date 2022/1/13 17:10 + */ + UserVO getUserById(String id); + List simpleList(Boolean allUserFlag); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserSetService.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserSetService.java new file mode 100644 index 0000000..1c4d506 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserSetService.java @@ -0,0 +1,18 @@ +package com.njcn.product.auth.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.auth.pojo.po.UserSet; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IUserSetService extends IService { + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserStrategyService.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserStrategyService.java new file mode 100644 index 0000000..4356ee6 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/IUserStrategyService.java @@ -0,0 +1,22 @@ +package com.njcn.product.auth.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.auth.pojo.po.UserStrategy; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IUserStrategyService extends IService { + + /** + * 查询用户策略数据 + * @return 用户策略信息 + * @param casualUser 是否为 casual用户 + */ + UserStrategy getUserStrategy(Integer casualUser); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/UserDetailsServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..a0ec652 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/UserDetailsServiceImpl.java @@ -0,0 +1,64 @@ +package com.njcn.product.auth.service; + +import cn.hutool.core.bean.BeanUtil; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.LogUtil; +import com.njcn.product.auth.pojo.bo.BusinessUser; +import com.njcn.product.auth.pojo.dto.UserDTO; +import com.njcn.web.utils.RequestUtil; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + + +/** + * @author hongawen + *

+ * 自定义用户认证和授权 + */ +@Slf4j +@Service +@AllArgsConstructor +public class UserDetailsServiceImpl implements CustomUserDetailsService { + + private final IUserService userService; + + + @SneakyThrows + @Override + public UserDetails loadUserByUsername(String loginName) throws UsernameNotFoundException { + String clientId = RequestUtil.getOAuth2ClientId(); + BusinessUser businessUser = new BusinessUser(loginName, null, null); + businessUser.setClientId(clientId); + UserDTO userDTO = userService.getUserByName(loginName); + LogUtil.njcnDebug(log, "用户认证时,用户名:{}获取用户信息:{}", loginName, userDTO.toString()); + //成功获取用户信息 + BeanUtil.copyProperties(userDTO, businessUser, true); + //处理头像 +// dealHead(businessUser); + businessUser.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", userDTO.getRoleName()))); + return businessUser; + } + + + @Override + public UserDetails loadUserByPhone(String phone) throws UsernameNotFoundException { + String clientId = RequestUtil.getOAuth2ClientId(); + BusinessUser businessUser = new BusinessUser(phone, null, null); + businessUser.setClientId(clientId); + UserDTO userDTO = userService.loadUserByPhone(phone); + LogUtil.njcnDebug(log, "用户验证码认证时,用户名:{}获取用户信息:{}", phone, userDTO.toString()); + //成功获取用户信息 + BeanUtil.copyProperties(userDTO, businessUser, true); +// dealHead(businessUser); + businessUser.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", userDTO.getRoleName()))); + return businessUser; + } + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/UserTokenService.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/UserTokenService.java new file mode 100644 index 0000000..ad2d6e6 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/UserTokenService.java @@ -0,0 +1,128 @@ +package com.njcn.product.auth.service; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.nimbusds.jose.JWSObject; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.dto.UserTokenInfo; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.redis.utils.RedisUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.scheduling.annotation.Async; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; +import org.springframework.stereotype.Service; + +import java.text.ParseException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年03月11日 10:34 + */ +@Slf4j +@Service +@AllArgsConstructor +public class UserTokenService { + + private final RedisUtil redisUtil; + + /** + * 记录用户token信息,并经过处理后达到最新登录的使用者,将之前的token信息置为黑名单,过期状态 + * 1、从在线名单中获取该用户的token信息,key为:TOKEN_ONLINE_PREFIX+userid,value为userTokenInfo的json对象 + * 1.1 有,则表示有人使用该账户登录过 + * 1.1.1 将在线名单的用户信息添加到黑名单,并清除黑名单中已经过期的token信息 + * ,重新赋值黑名单信息,key为:TOKEN_BLACKLIST_PREFIX+userid,value为userTokenInfo的集合 + * 1.2 没有,该账号当前只有本人在登录,将当前token等信息保存到白名单 + * + * @param oAuth2AccessToken 认证后的最新token信息 + */ + @Async("asyncExecutor") + public void recordUserInfo(OAuth2AccessToken oAuth2AccessToken, String ip) { + UserTokenInfo userTokenInfo = new UserTokenInfo(); + String accessTokenValue = oAuth2AccessToken.getValue(); + JWSObject accessJwsObject; + try { + accessJwsObject = JWSObject.parse(accessTokenValue); + } catch (ParseException e) { + throw new BusinessException(CommonResponseEnum.PARSE_TOKEN_ERROR); + } + JSONObject accessJson = JSONUtil.parseObj(accessJwsObject.getPayload().toString()); + String userIndex = accessJson.getStr(SecurityConstants.USER_INDEX_KEY); + // String nickName = accessJson.getStr(SecurityConstants.USER_NICKNAME_KEY); + // String loginName = accessJson.getStr(SecurityConstants.USER_NAME_KEY); + //查询是否有在线的当前用户 + String onlineUserKey = SecurityConstants.TOKEN_ONLINE_PREFIX + userIndex; + Object onlineTokenInfoOld = redisUtil.getObjectByKey(onlineUserKey); + if (!Objects.isNull(onlineTokenInfoOld)) { + //存在在线用户,将在线用户添加到黑名单列表 + String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex; + List blackUsers = (List) redisUtil.getObjectByKey(blackUserKey); + if (CollectionUtils.isEmpty(blackUsers)) { + blackUsers = new ArrayList<>(); + } + blackUsers.add((UserTokenInfo) onlineTokenInfoOld); + //筛选黑名单中是否存在过期的token信息 + blackUsers.removeIf(userTokenInfoTemp -> userTokenInfoTemp.getRefreshTokenExpire().isBefore(LocalDateTime.now())); + //将黑名单集合重新缓存,此处根据最新的黑名单计算当前这个key的生命周期,在时间差的基础上增加5分钟的延迟时间 + LocalDateTime refreshTokenExpire = ((UserTokenInfo) onlineTokenInfoOld).getRefreshTokenExpire(); + long lifeTime = Math.abs(refreshTokenExpire.plusMinutes(5L).toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"))); + redisUtil.saveByKeyWithExpire(blackUserKey, blackUsers, lifeTime); + } + String accessJti = accessJson.getStr(SecurityConstants.JWT_JTI); + OAuth2RefreshToken refreshToken = oAuth2AccessToken.getRefreshToken(); + JWSObject refreshJwsObject; + try { + refreshJwsObject = JWSObject.parse(refreshToken.getValue()); + } catch (ParseException e) { + throw new BusinessException(CommonResponseEnum.PARSE_TOKEN_ERROR); + } + JSONObject refreshJson = JSONUtil.parseObj(refreshJwsObject.getPayload().toString()); + // String refreshJti = refreshJson.getStr(SecurityConstants.JWT_JTI); + Long refreshExpireTime = refreshJson.getLong(SecurityConstants.JWT_EXP); + userTokenInfo.setAccessTokenJti(accessJti); + userTokenInfo.setRefreshToken(refreshToken.getValue()); + LocalDateTime refreshLifeTime = LocalDateTime.ofEpochSecond(refreshExpireTime, 0, ZoneOffset.of("+8")); + userTokenInfo.setRefreshTokenExpire(refreshLifeTime); + //生命周期在refreshToken的基础上,延迟5分钟 + redisUtil.saveByKeyWithExpire(onlineUserKey, userTokenInfo, refreshLifeTime.plusMinutes(5L).toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"))); + + //记录成功登录后的信息 + //LogInfoDTO logInfoDTO = new LogInfoDTO(loginName, nickName, ip, "登录认证", OperateType.AUTHENTICATE, 1, "", 0, 1, generalInfo.getMicroServiceName(), userIndex,LocalDateTime.now()); + //publisher.send("/userLog", PubUtils.obj2json(logInfoDTO), 2, false); + } + + /** + * 校验刷新token是否被加入黑名单 + * + * @param refreshToken 刷新token + */ + public void judgeRefreshToken(String refreshToken) { + JWSObject refreshJwsObject; + try { + refreshJwsObject = JWSObject.parse(refreshToken); + } catch (ParseException e) { + throw new BusinessException(); + } + JSONObject refreshJson = JSONUtil.parseObj(refreshJwsObject.getPayload().toString()); + String userIndex = refreshJson.getStr(SecurityConstants.USER_INDEX_KEY); + String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex; + List blackUsers = (List) redisUtil.getObjectByKey(blackUserKey); + if (CollectionUtils.isNotEmpty(blackUsers)) { + blackUsers.forEach(temp -> { + //存在当前的刷新token,则抛出业务异常 + if (temp.getRefreshToken().equalsIgnoreCase(refreshToken)) { + throw new BusinessException(CommonResponseEnum.TOKEN_EXPIRE_JWT); + } + }); + } + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/AuthClientServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/AuthClientServiceImpl.java new file mode 100644 index 0000000..fdfb047 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/AuthClientServiceImpl.java @@ -0,0 +1,32 @@ +package com.njcn.product.auth.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.common.DataStateEnum; + +import com.njcn.product.auth.mapper.AuthClientMapper; +import com.njcn.product.auth.pojo.po.AuthClient; +import com.njcn.product.auth.service.IAuthClientService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-15 + */ +@Service +@RequiredArgsConstructor +public class AuthClientServiceImpl extends ServiceImpl implements IAuthClientService { + + @Override + public AuthClient getAuthClientByName(String clientName) { + return lambdaQuery() + .eq(AuthClient::getName,clientName) + .eq(AuthClient::getState,DataStateEnum.ENABLE.getCode()) + .one(); + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/RoleServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..638395f --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/RoleServiceImpl.java @@ -0,0 +1,57 @@ +package com.njcn.product.auth.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.common.DataStateEnum; + +import com.njcn.product.auth.mapper.RoleMapper; +import com.njcn.product.auth.pojo.po.Role; +import com.njcn.product.auth.pojo.po.UserRole; +import com.njcn.product.auth.service.IRoleService; +import com.njcn.product.auth.service.IUserRoleService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Service +@RequiredArgsConstructor +public class RoleServiceImpl extends ServiceImpl implements IRoleService { + + private final IUserRoleService iUserRoleService; + + + + @Override + public List getRoleNameByUserId(String id) { + List userRoles = iUserRoleService.getUserRoleByUserId(id); + if (CollectionUtils.isEmpty(userRoles)) { + return new ArrayList<>(); + } + List roles = this.lambdaQuery() + .select(Role::getCode) + .eq(Role::getState, DataStateEnum.ENABLE.getCode()) + .in(Role::getId, userRoles.stream() + .map(UserRole::getRoleId) + .collect(Collectors.toList()) + ).list(); + return roles + .stream() + .map(Role::getCode) + .distinct() + .collect(Collectors.toList()); + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserRoleServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserRoleServiceImpl.java new file mode 100644 index 0000000..0c5f8e3 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserRoleServiceImpl.java @@ -0,0 +1,39 @@ +package com.njcn.product.auth.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.product.auth.mapper.UserRoleMapper; +import com.njcn.product.auth.pojo.po.Role; +import com.njcn.product.auth.pojo.po.UserRole; +import com.njcn.product.auth.service.IUserRoleService; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Service +@AllArgsConstructor +public class UserRoleServiceImpl extends ServiceImpl implements IUserRoleService { + + private final UserRoleMapper userRoleMapper; + + @Override + public List getUserRoleByUserId(String id) { + return this.lambdaQuery().eq(UserRole::getUserId, id).list(); + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..6478708 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserServiceImpl.java @@ -0,0 +1,309 @@ +package com.njcn.product.auth.service.impl; + + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.config.GeneralInfo; +import com.njcn.common.pojo.constant.LogInfo; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.utils.PubUtils; + +import com.njcn.product.auth.pojo.dto.UserDTO; +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.product.auth.pojo.enums.UserStatusEnum; +import com.njcn.product.auth.mapper.UserMapper; +import com.njcn.product.auth.pojo.constant.UserState; + +import com.njcn.product.auth.pojo.po.*; +import com.njcn.product.auth.pojo.vo.UserVO; +import com.njcn.product.auth.service.*; + +import com.njcn.product.system.dept.pojo.po.Dept; +import com.njcn.web.utils.RequestUtil; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Service +@RequiredArgsConstructor +public class UserServiceImpl extends ServiceImpl implements IUserService { + + + + private final IUserStrategyService userStrategyService; + private final IRoleService roleService; + private final IUserSetService userSetService; + + + @Override + public UserDTO getUserByName(String loginName) { + User user = getUserByLoginName(loginName); + if (Objects.isNull(user)) { + return null; + } + List roleNames = roleService.getRoleNameByUserId(user.getId()); + UserSet userSet = userSetService.lambdaQuery().eq(UserSet::getUserId, user.getId()).one(); + return new UserDTO(user.getId(), user.getLoginName(), user.getName(), user.getPassword(), roleNames, userSet.getSecretKey(), userSet.getStandBy(), user.getDeptId(), user.getType(), user.getHeadSculpture()); + } + + + @Override + public void judgeUserStatus(String loginName) { + User user = getUserByLoginName(loginName); + if (Objects.isNull(user)) { + throw new BusinessException(UserResponseEnum.LOGIN_WRONG_PWD); + } + //超级管理员则不做任何逻辑判断 + if (user.getType() == 0) { + //更新用户登录时间,以及错误登录记录的信息归零。 + user.setState(UserState.ENABLE); + user.setLoginErrorTimes(0); + user.setLoginTime(LocalDateTime.now()); + user.setFirstErrorTime(null); + user.setLockTime(null); + this.baseMapper.updateById(user); + return; + } + //根据用户类型获取对应用户策略 + UserStrategy userStrategy = userStrategyService.lambdaQuery() + .eq(UserStrategy::getType, user.getCasualUser()) + .eq(UserStrategy::getState, DataStateEnum.ENABLE.getCode()) + .one(); + switch (user.getState()) { + case UserState.LOCKED: + LocalDateTime lockTime = user.getLockTime(); + lockTime = lockTime.plusMinutes(userStrategy.getLockPwdTime()); + LocalDateTime nowTime = LocalDateTime.now(); + //判断是否满足锁定时间 + if (nowTime.isBefore(lockTime)) { + CommonResponseEnum testEnum = CommonResponseEnum.DYNAMIC_RESPONSE_ENUM; + testEnum.setMessage("账号已被锁定:锁定剩余时长" + ChronoUnit.MINUTES.between(nowTime, lockTime) + "分钟"); + throw new BusinessException(testEnum); + } + break; + case UserState.DELETE: + //用户已注销 + throw new BusinessException(UserResponseEnum.LOGIN_USER_DELETE); + case UserState.UNCHECK: + //用户未审核 + throw new BusinessException(UserResponseEnum.LOGIN_USER_UNAUDITED); + case UserState.SLEEP: + //用户已休眠 + throw new BusinessException(UserResponseEnum.LOGIN_USER_SLEEP); + case UserState.OVERDUE: + //用户密码已过期 + throw new BusinessException(UserResponseEnum.LOGIN_USER_PASSWORD_EXPIRED); + default: + if (user.getPwdState() == 1) { + throw new BusinessException(UserResponseEnum.NEED_MODIFY_PWD); + } + //用户状态正常,判断其他细节 + judgeFirstLogin(user, userStrategy); + } + //所有验证通过后,更新用户登录时间,以及错误登录记录的信息归零。 + user.setState(UserState.ENABLE); + user.setLoginErrorTimes(0); + user.setLoginTime(LocalDateTime.now()); + user.setFirstErrorTime(null); + user.setLockTime(null); + this.baseMapper.updateById(user); + } + + /** + * 根据登录名查询用户 + * + * @param loginName 登录名 + * @return 用户信息 + */ + private User getUserByLoginName(String loginName) { + return lambdaQuery() + .eq(User::getLoginName, loginName) + .one(); + } + /** + * 判断是否需要修改密码 + */ + private void judgeFirstLogin(@NotNull User user, UserStrategy userStrategy) { + if (user.getPwdState() == 1) { + throw new BusinessException(UserResponseEnum.NEED_MODIFY_PWD); + } else { + judgeIp(user, userStrategy); + } + } + + /** + * 判断用户是否在合理的IP内登录 + */ + private void judgeIp(@NotNull User user, UserStrategy userStrategy) { + String ipSection = user.getLimitIpStart() + "-" + user.getLimitIpEnd(); + log.error("用户实际ip:" + RequestUtil.getRealIp()); + log.error("用户限制ip:" + ipSection); + if (RequestUtil.getRealIp().equalsIgnoreCase(LogInfo.UNKNOWN_IP)) { + //feign接口可能获取的IP是空的 + throw new BusinessException(UserResponseEnum.INVALID_IP); + } else if (!PubUtils.ipExistsInRange(RequestUtil.getRealIp(), ipSection)) { + throw new BusinessException(UserResponseEnum.INVALID_IP); + } else { + judgeLimitTime(user, userStrategy); + } + judgeLimitTime(user, userStrategy); + } + + /** + * 判断用户是否在允许的时间段内登录 + */ + private void judgeLimitTime(@NotNull User user, UserStrategy userStrategy) { + int nowHour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); + String[] limitTime = user.getLimitTime().split(StrUtil.DASHED); + if (nowHour >= Integer.parseInt(limitTime[0]) && nowHour < Integer.parseInt(limitTime[1])) { + judgePwdTimeValidity(user, userStrategy); + } else { + throw new BusinessException(UserResponseEnum.INVALID_TIME); + } + } + + /** + * 判断用户密码是否已经过期 + */ + private void judgePwdTimeValidity(@NotNull User user, UserStrategy userStrategy) { + LocalDateTime pwdValidity = user.getPwdValidity(); + pwdValidity = pwdValidity.plusMonths(userStrategy.getLimitPwdDate()); + if (LocalDateTime.now().isBefore(pwdValidity)) { + judgeLeisurePwd(user, userStrategy); + } else { + //将用户状态置为过期 + user.setState(UserState.OVERDUE); + this.baseMapper.updateById(user); + throw new BusinessException(UserResponseEnum.LOGIN_USER_PASSWORD_EXPIRED); + } + + } + + /** + * 判断用户闲置 + */ + private void judgeLeisurePwd(@NotNull User user, UserStrategy userStrategy) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime sleepTime = user.getLoginTime().plusDays(userStrategy.getSleep()); + LocalDateTime logoutTime = user.getLoginTime().plusDays(userStrategy.getLogout()); + if (now.isAfter(sleepTime) && now.isBefore(logoutTime)) { + //将用户状态置为休眠 + user.setState(UserState.SLEEP); + this.baseMapper.updateById(user); + throw new BusinessException(UserResponseEnum.LOGIN_USER_SLEEP); + } else if (now.isAfter(logoutTime)) { + //将用户状态置为注销 + user.setState(UserState.DELETE); + this.baseMapper.updateById(user); + throw new BusinessException(UserResponseEnum.LOGIN_USER_DELETE); + } + } + @Override + public UserDTO loadUserByPhone(String phone) { + User user = getUserByPhone(phone, false, null); + if (Objects.isNull(user)) { + return null; + } + List roleNames = roleService.getRoleNameByUserId(user.getId()); + UserSet userSet = userSetService.lambdaQuery().eq(UserSet::getUserId, user.getId()).one(); + return new UserDTO(user.getId(), user.getLoginName(), user.getName(), user.getPassword(), roleNames, userSet.getSecretKey(), userSet.getStandBy(), user.getDeptId(), user.getType(), user.getHeadSculpture()); + } + + @Override + public UserVO getUserById(String id) { + UserVO userVO = new UserVO(); + User user = lambdaQuery().eq(User::getId, id).one(); + if (Objects.isNull(user)) { + return null; + } + BeanUtil.copyProperties(user, userVO); + + return userVO; + } + + @Override + public List simpleList(Boolean allUserFlag) { + LambdaQueryWrapper userLambdaQueryWrapper = new LambdaQueryWrapper<>(); + userLambdaQueryWrapper.select(User::getId, User::getName,User::getLoginName).eq(User::getState, DataStateEnum.ENABLE.getCode()); + if(!allUserFlag){ + userLambdaQueryWrapper.eq(User::getType,2); + } + return this.baseMapper.selectList(userLambdaQueryWrapper); + } + + + /** + * 根据手机号查询用户 + * + * @param phone 手机号码 + * @return 用户信息 + */ + private User getUserByPhone(String phone, boolean result, String id) { + if (result) { + return lambdaQuery() + .eq(User::getPhone, phone) + .ne(User::getId, id) + .one(); + } else { + return lambdaQuery() + .eq(User::getPhone, phone) + .one(); + } + } + + @Override + public String updateUserLoginErrorTimes(String loginName) { + User user = this.lambdaQuery().eq(User::getLoginName, loginName).one(); + LocalDateTime now = LocalDateTime.now(); + if (Objects.nonNull(user)) { + UserStrategy userStrategy = userStrategyService.getUserStrategy(user.getCasualUser()); + Integer loginErrorTimes = user.getLoginErrorTimes(); + ++loginErrorTimes; + if (Objects.isNull(user.getFirstErrorTime())) { + //首次错误,错误1次、记录第一次错误时间 + user.setLoginErrorTimes(loginErrorTimes); + user.setFirstErrorTime(LocalDateTime.now()); + } else if (loginErrorTimes <= userStrategy.getLimitPwdTimes()) { + //如果次数在策略之内,还未被锁定 + LocalDateTime firstErrorTime = user.getFirstErrorTime(); + firstErrorTime = firstErrorTime.plusMinutes(userStrategy.getLockPwdCheck()); + if (now.isAfter(firstErrorTime)) { + //重置密码错误次数、时间等记录 + user.setLoginErrorTimes(1); + user.setFirstErrorTime(LocalDateTime.now()); + } else { + user.setLoginErrorTimes(loginErrorTimes); + } + } else { + user.setLockTime(LocalDateTime.now()); + user.setState(UserStatusEnum.LOCKED.getCode()); + user.setLoginErrorTimes(loginErrorTimes); + this.baseMapper.updateById(user); + return UserResponseEnum.LOGIN_USER_LOCKED.getMessage(); + } + this.baseMapper.updateById(user); + } + return CommonResponseEnum.SUCCESS.getMessage(); + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserSetServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserSetServiceImpl.java new file mode 100644 index 0000000..cd09301 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserSetServiceImpl.java @@ -0,0 +1,29 @@ +package com.njcn.product.auth.service.impl; + + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.auth.mapper.UserSetMapper; +import com.njcn.product.auth.pojo.po.UserSet; +import com.njcn.product.auth.service.IUserSetService; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Service +@RequiredArgsConstructor +public class UserSetServiceImpl extends ServiceImpl implements IUserSetService { + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserStrategyServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserStrategyServiceImpl.java new file mode 100644 index 0000000..200d6e0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/service/impl/UserStrategyServiceImpl.java @@ -0,0 +1,37 @@ +package com.njcn.product.auth.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; + +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.product.auth.mapper.UserStrategyMapper; +import com.njcn.product.auth.pojo.po.UserStrategy; +import com.njcn.product.auth.service.IUserStrategyService; +import org.springframework.stereotype.Service; + +import java.util.Objects; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Service +public class UserStrategyServiceImpl extends ServiceImpl implements IUserStrategyService { + + @Override + public UserStrategy getUserStrategy(Integer casualUser) { + UserStrategy userStrategy = this.lambdaQuery() + .eq(UserStrategy::getState, DataStateEnum.ENABLE.getCode()) + .eq(UserStrategy::getType, casualUser) + .one(); + if (Objects.isNull(userStrategy)) { + throw new BusinessException(UserResponseEnum.LACK_USER_STRATEGY); + } + return userStrategy; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/auth/utils/AuthPubUtil.java b/carry_capacity/src/main/java/com/njcn/product/auth/utils/AuthPubUtil.java new file mode 100644 index 0000000..e8cb412 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/auth/utils/AuthPubUtil.java @@ -0,0 +1,41 @@ +package com.njcn.product.auth.utils; + +import cn.hutool.core.util.RandomUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.nimbusds.jose.JWSObject; +import lombok.SneakyThrows; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年06月04日 14:00 + */ +public class AuthPubUtil { + + public static String getKaptchaText(int codeLength) { + StringBuilder code = new StringBuilder(); + int letterLength = RandomUtil.randomInt(codeLength - 1) + 1; + code.append(RandomUtil.randomString(RandomUtil.BASE_CHAR, letterLength).toUpperCase(Locale.ROOT)); + int numberLength = codeLength - letterLength; + code.append(RandomUtil.randomString(RandomUtil.BASE_NUMBER, numberLength)); + List textList = Arrays.asList(code.toString().split("")); + //填充完字符后,打乱顺序,返回字符串 + Collections.shuffle(textList); + return String.join("", textList); + } + + + @SneakyThrows + public static JSONObject getLoginByToken(String token){ + JWSObject jwsObject = JWSObject.parse(token); + String payload = jwsObject.getPayload().toString(); + return JSONUtil.parseObj(payload); + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityController.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityController.java new file mode 100644 index 0000000..8bc2c98 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityController.java @@ -0,0 +1,133 @@ +package com.njcn.product.carrycapacity.controller; + + +import com.alibaba.excel.EasyExcel; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; + +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.product.carrycapacity.pojo.excel.CarryCapcityDataEexcel; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityCalParam; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityEvaluateParam; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityQueryDataParam; +import com.njcn.product.carrycapacity.pojo.param.ExcelDataParam; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDResultVO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDataIVO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDataQVO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDataVO; +import com.njcn.product.carrycapacity.service.CarryCapacityService; +import com.njcn.product.carrycapacity.util.EasyExcelUtil; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月21日 10:06 + */ +@RestController +@RequestMapping("carrycapacity") +@Api(tags = "承载能力评估") +@RequiredArgsConstructor +public class CarryCapacityController extends BaseController { + + + private final CarryCapacityService carryCapcityService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queryCarryCapacityData") + @ApiOperation("承载能力评估数据查询-主页面") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult queryCarryCapacityData(@RequestBody @Validated CarryCapacityQueryDataParam queryParam) { + String methodDescribe = getMethodDescribe("queryCarryCapacityData"); + CarryCapacityDataVO carryCapacityDataVO = carryCapcityService.queryCarryCapacityData(queryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, carryCapacityDataVO, methodDescribe); + } + + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queryCarryCapacityQData") + @ApiOperation("承载能力评估数据查询-无功功率") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult queryCarryCapacityQData(@RequestBody @Validated CarryCapacityQueryDataParam queryParam) { + String methodDescribe = getMethodDescribe("queryCarryCapacityQData"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, carryCapcityService.queryCarryCapacityqData(queryParam), methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queryCarryCapacityIData") + @ApiOperation("承载能力评估数据查询-谐波电流幅值") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult queryCarryCapacityIData(@RequestBody @Validated CarryCapacityQueryDataParam queryParam) { + String methodDescribe = getMethodDescribe("queryCarryCapacityIData"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, carryCapcityService.queryCarryCapacityiData(queryParam), methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/carryCapacityCal") + @ApiOperation("承载能力评估") + @ApiImplicitParam(name = "calParam", value = "计算参数", required = true) + public HttpResult carryCapacityCal(@RequestBody @Validated CarryCapacityCalParam calParam) { + String methodDescribe = getMethodDescribe("carryCapacityCal"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, carryCapcityService.carryCapacityCal(calParam), methodDescribe); + } + +// @OperateInfo(info = LogEnum.BUSINESS_COMMON) +// @GetMapping("/carryCapacityTree") +// @ApiOperation("承载能力评估-台账树") +// public HttpResult> carryCapacityTree() { +// String methodDescribe = getMethodDescribe("carryCapacityTree"); +// List terminalTree = carryCapcityService.carryCapacityTree(); +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, terminalTree, methodDescribe); +// } + + @ResponseBody + @ApiOperation("导出数据集模板") + @GetMapping(value = "getExcelTemplate") + public HttpResult getExcelTemplate(HttpServletResponse response) throws IOException { + String sheetName = "数据集模版"; + List excels = new ArrayList<>(); + CarryCapcityDataEexcel exportHeadersExcel = new CarryCapcityDataEexcel(); + excels.add(exportHeadersExcel); + EasyExcel.write(response.getOutputStream(), CarryCapcityDataEexcel.class) + .sheet(sheetName) + .doWrite(excels); + EasyExcelUtil.writeWithSheetsWeb(response, "数据集模版.xlsx"); + return null; + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/uploadExcel") + @ApiOperation("上传数据集") + public HttpResult uploadExcel(@Validated ExcelDataParam excelDataParam) throws Exception { + String methodDescribe = getMethodDescribe("uploadExcel"); + boolean flag = carryCapcityService.uploadExcel(excelDataParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/carryCapacityEvaluate") + @ApiOperation("承载能力评估_充电站、电加热负荷、电气化铁路承载能力评估") + @ApiImplicitParam(name = "calParam", value = "计算参数", required = true) + public HttpResult carryCapacityEvaluate(@RequestBody @Validated CarryCapacityEvaluateParam calParam) { + String methodDescribe = getMethodDescribe("carryCapacityEvaluate"); + CarryCapacityDResultVO vo = carryCapcityService.carryCapacityEvaluate(calParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, vo, methodDescribe); + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityDevController.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityDevController.java new file mode 100644 index 0000000..5d48d2b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityDevController.java @@ -0,0 +1,85 @@ +package com.njcn.product.carrycapacity.controller; + + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityDeviceParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityDevicePO; +import com.njcn.product.carrycapacity.service.CarryCapacityDevicePOService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月21日 10:06 + */ +@RestController +@RequestMapping("carrycapacitydev") +@Api(tags = "承载能力评估用户设备") +@RequiredArgsConstructor +public class CarryCapacityDevController extends BaseController { + + + private final CarryCapacityDevicePOService carryCapacityDevicePOService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/add") + @ApiOperation("承载能力评估用户设备新增") + @ApiImplicitParam(name = "capacityDeviceParam", value = "新增参数", required = true) + public HttpResult add(@RequestBody @Validated CarryCapacityDeviceParam capacityDeviceParam) { + String methodDescribe = getMethodDescribe("add"); + Boolean flag = carryCapacityDevicePOService.add(capacityDeviceParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/remove") + @ApiOperation("承载能力评估用户设备批量删除") + @ApiImplicitParam(name = "devIds", value = "用户id集合", required = true) + public HttpResult remove(@RequestParam("devIds") List devIds) { + String methodDescribe = getMethodDescribe("remove"); + Boolean flag = carryCapacityDevicePOService.removeByIds(devIds); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/update") + @ApiOperation("承载能力评估用户设备编辑") + @ApiImplicitParam(name = "deviceParam", value = "编辑参数", required = true) + public HttpResult update(@RequestBody @Validated CarryCapacityDeviceParam.CarryCapacityDeviceUpdateParam deviceParam) { + String methodDescribe = getMethodDescribe("update"); + Boolean flag = carryCapacityDevicePOService.updateDevice(deviceParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queyDeviceList") + @ApiOperation("承载能力评估用户设备查询") + @ApiImplicitParam(name = "deviceParam", value = "编辑参数", required = true) + public HttpResult> queyDeviceList(@RequestBody @Validated CarryCapacityDeviceParam.CarryCapacityDeviceQueryParam deviceParam) { + String methodDescribe = getMethodDescribe("queyDeviceList"); + List list = carryCapacityDevicePOService.lambdaQuery() + .eq(StringUtils.isNotBlank(deviceParam.getDevId()), CarryCapacityDevicePO::getDevId, deviceParam.getDevId()) + .eq(StringUtils.isNotBlank(deviceParam.getUserId()), CarryCapacityDevicePO::getUserId, deviceParam.getUserId()).list(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe); + } + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityResultController.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityResultController.java new file mode 100644 index 0000000..e4528df --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityResultController.java @@ -0,0 +1,69 @@ +package com.njcn.product.carrycapacity.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityQueryDataParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityResultPO; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityResultParam; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDResultVO; +import com.njcn.product.carrycapacity.service.CarryCapacityResultPOService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月21日 10:06 + */ +@RestController +@RequestMapping("result") +@Api(tags = "承载能力评估结果") +@RequiredArgsConstructor +public class CarryCapacityResultController extends BaseController { + + + private final CarryCapacityResultPOService carryCapacityResultPOService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queryResultList") + @ApiOperation("承载能力评估列表查询") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult> queryResultList(@RequestBody @Validated CarryCapacityResultParam.CarryCapacityResultPageParam queryParam) { + String methodDescribe = getMethodDescribe("queryResultList"); + IPage vo = carryCapacityResultPOService.queryResultList(queryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, vo, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queryResultbyCondition") + @ApiOperation("承载能力评估列表查询") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult queryResultbyCondition(@RequestBody @Validated CarryCapacityQueryDataParam queryParam) { + String methodDescribe = getMethodDescribe("queryResultbyCondition"); + CarryCapacityDResultVO vo = carryCapacityResultPOService.queryResultbyCondition(queryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, vo, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/remove") + @ApiOperation("承载能力评估用户批量删除") + public HttpResult remove(@RequestParam("ids") List ids) { + String methodDescribe = getMethodDescribe("remove"); + Boolean flag = carryCapacityResultPOService.lambdaUpdate().in(CarryCapacityResultPO::getId, ids).set(CarryCapacityResultPO::getStatus, 0).update(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityStrategyController.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityStrategyController.java new file mode 100644 index 0000000..03ca1f8 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityStrategyController.java @@ -0,0 +1,100 @@ +package com.njcn.product.carrycapacity.controller; + + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityStrategyParam; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityStrategyDhlVO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityStrategyVO; +import com.njcn.product.carrycapacity.service.CarryCapacityStrategyDhlPOService; +import com.njcn.product.carrycapacity.service.CarryCapacityStrategyPOService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * Description: + * Date: 2024/3/5 10:35【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@RestController +@RequestMapping("carrycapacity") +@Api(tags = "承载能力评估策略配置") +@RequiredArgsConstructor +public class CarryCapacityStrategyController extends BaseController { + + + private final CarryCapacityStrategyPOService carryCapacityStrategyPOService; + private final CarryCapacityStrategyDhlPOService carryCapacityStrategyDhlPOService; + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/add") + @ApiOperation("用户新增承载能力评估策略(光伏)") + @ApiImplicitParam(name = "carryCapacityStrategyParam", value = "新增参数", required = true) + public HttpResult add(@RequestBody @Validated CarryCapacityStrategyParam carryCapacityStrategyParam) { + String methodDescribe = getMethodDescribe("add"); + Boolean flag = carryCapacityStrategyPOService.add(carryCapacityStrategyParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/addList") + @ApiOperation("用户新增承载能力评估策略组(光伏)") + @ApiImplicitParam(name = "carryCapacityStrategyParamList", value = "新增参数", required = true) + public HttpResult addList(@RequestBody @Validated List carryCapacityStrategyParamList) { + String methodDescribe = getMethodDescribe("addList"); + Boolean flag = carryCapacityStrategyPOService.addList(carryCapacityStrategyParamList); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queyDetail") + @ApiOperation("承载能力评估策略初始化查询(光伏)") + public HttpResult> queyDetail() { + String methodDescribe = getMethodDescribe("queyDetail"); + List carryCapacityStrategyVOList = carryCapacityStrategyPOService.queyDetail(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, carryCapacityStrategyVOList, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/restore") + @ApiOperation("承载能力评估策略一键还原") + public HttpResult restore() { + String methodDescribe = getMethodDescribe("restore"); + Boolean flag = carryCapacityStrategyPOService.restore(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/adddhl") + @ApiOperation("用户新增承载能力评估策略(电弧炉)") + @ApiImplicitParam(name = "capacityStrategyDhlVO", value = "新增参数", required = true) + public HttpResult adddhl(@RequestBody @Validated CarryCapacityStrategyDhlVO capacityStrategyDhlVO) { + String methodDescribe = getMethodDescribe("adddhl"); + Boolean flag = carryCapacityStrategyDhlPOService.adddhl(capacityStrategyDhlVO); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queyDetailDhl") + @ApiOperation("承载能力评估策略初始化查询(电弧炉)") + public HttpResult> queyDetailDhl() { + String methodDescribe = getMethodDescribe("queyDetailDhl"); + List car = carryCapacityStrategyDhlPOService.queyDetailDhl(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, car, methodDescribe); + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityUserController.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityUserController.java new file mode 100644 index 0000000..3a64ae7 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/controller/CarryCapacityUserController.java @@ -0,0 +1,90 @@ +package com.njcn.product.carrycapacity.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityUserParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityUserPO; +import com.njcn.product.carrycapacity.service.CarryCapacityUserPOService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月21日 10:06 + */ +@RestController +@RequestMapping("carrycapacityuser") +@Api(tags = "承载能力评估用户") +@RequiredArgsConstructor +public class CarryCapacityUserController extends BaseController { + + + private final CarryCapacityUserPOService carryCapacityUserPOService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/add") + @ApiOperation("承载能力评估用户新增") + @ApiImplicitParam(name = "carryCapacityUserParam", value = "新增参数", required = true) + public HttpResult add(@RequestBody @Validated CarryCapacityUserParam carryCapacityUserParam) { + String methodDescribe = getMethodDescribe("add"); + Boolean flag = carryCapacityUserPOService.add(carryCapacityUserParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/remove") + @ApiOperation("承载能力评估用户批量删除") + @ApiImplicitParam(name = "userIds", value = "用户id集合", required = true) + public HttpResult remove(@RequestParam("userIds") List userIds) { + String methodDescribe = getMethodDescribe("remove"); + Boolean flag = carryCapacityUserPOService.lambdaUpdate().in(CarryCapacityUserPO::getUserId,userIds).set(CarryCapacityUserPO::getStatus,0).update(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/update") + @ApiOperation("承载能力评估用户编辑") + @ApiImplicitParam(name = "userUpdateParam", value = "编辑参数", required = true) + public HttpResult update(@RequestBody @Validated CarryCapacityUserParam.CarryCapacityUserUpdateParam userUpdateParam) { + String methodDescribe = getMethodDescribe("update"); + Boolean flag = carryCapacityUserPOService.updateUser(userUpdateParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queyDetailUser") + @ApiOperation("承载能力评估用户查询") + @ApiImplicitParam(name = "pageParam", value = "编辑参数", required = true) + public HttpResult> queyDetailUser(@RequestBody @Validated CarryCapacityUserParam.CarryCapacityUserPageParam pageParam) { + String methodDescribe = getMethodDescribe("queyDetailUser"); + IPage page = carryCapacityUserPOService.queyDetailUser(pageParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, page, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queyDetailUserById") + @ApiOperation("承载能力评估用户查询") + public HttpResult queyDetailUserById(@RequestParam("userId") String userId) { + String methodDescribe = getMethodDescribe("queyDetailUserById"); + CarryCapacityUserPO po = carryCapacityUserPOService.queyDetailUserById(userId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, po, methodDescribe); + } + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/enums/CarryCapacityResponseEnum.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/enums/CarryCapacityResponseEnum.java new file mode 100644 index 0000000..de69ef6 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/enums/CarryCapacityResponseEnum.java @@ -0,0 +1,103 @@ +package com.njcn.product.carrycapacity.enums; + +import lombok.Getter; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年04月13日 10:50 + */ +@Getter +public enum CarryCapacityResponseEnum { + + ANALYSIS_USER_DATA_ERROR("A0101","解析用采数据内容失败"), + + INTERNAL_ERROR("A0101","系统内部异常"), + + USER_DATA_EMPTY("A0101","用采数据内容为空"), + + USER_DATA_NOT_FOUND("A0101","未找到用采数据"), + + RESP_DATA_NOT_FOUND("A0101","未找到责任划分数据"), + + WIN_TIME_ERROR("A0101","限值时间小于窗口"), + + CALCULATE_INTERVAL_ERROR("A0101","对齐计算间隔值非法"), + + RESP_RESULT_DATA_NOT_FOUND("A0101","未找到责任划分缓存数据"), + + USER_DATA_P_NODE_PARAMETER_ERROR("A0101","无用采用户或所有用户的完整性均不满足条件"), + + RESPONSIBILITY_PARAMETER_ERROR("A0101","调用接口程序计算失败,参数非法"), + + EVENT_EMPTY("A0102","没有查询到未分析事件"), + + USER_NAME_EXIST("A0103","用户名已存在"), + + DATA_NOT_FOUND("A0104","选择时间内暂无数据,请上传离线数据"), + + DATA_UNDERRUN("A0104","数据量不足,请根据模版上传充足近两周数据"), + + DOCUMENT_FORMAT_ERROR("A0105","数据缺失,导入失败!请检查导入文档的格式是否正确"), + DEVICE_LOST("A0104","用户下缺少设备"), + + USER_LOST("A0106","干扰源用户缺失"), + UNCOMPLETE_STRATEGY("A0106","配置安全,III级预警,II级预警,I级预警4条完整策略"), + EXISTENCE_EVALUATION_RESULT("A0104","存在评结果结果,如要评估,请删除后评估"), + + SG_USER_NAME_REPEAT("A0102","业务用户名重复"), + + SG_PRODUCT_LINE_NAME_REPEAT("A0102","生产线名重复"), + + SG_USER_ID_MISS("A0102","业务用户id缺失"), + + SG_PRODUCT_LINE_ID_MISS("A0102","生产线id缺失"), + + SG_MACHINE_ID_MISS("A0102","设备id缺失"), + + IMPORT_EVENT_DATA_FAIL("A0102","请检查导入数据的准确性"), + + PRODUCT_LINE_DATA_MISS("A0102","生产线数据缺失"), + + MACHINE_DATA_MISS("A0102","设备数据缺失"), + + INCOMING_LINE_DATA_MISS("A0102","进线数据缺失"), + + EVENT_DATA_MISS("A0102","没有可供参考的暂降数据"), + + WIN_DATA_ERROR("A0102","算法校验窗宽超限"), + + DATA_ERROR("A0102","算法校验数据长度超限"), + + INIT_DATA_ERROR("A0102","算法初始化数据失败"), + + USER_HAS_PRODUCT("A0102","当前用户存在生产线"), + + PRODUCT_HAS_MACHINE("A0102","当前生产线存在设备"), + + MACHINE_HAS_UNIT("A0102","当前设备存在元器件"), + + EVENT_TIME_ERROR("A0102","暂降事件时间格式有误,请检查"), + + INVALID_FILE_TYPE("A0102","请选择CSV文件"), + ; + + private final String code; + + private final String message; + + CarryCapacityResponseEnum(String code, String message) { + this.code = code; + this.message = message; + } + + public static String getCodeByMsg(String msg){ + for (CarryCapacityResponseEnum userCodeEnum : CarryCapacityResponseEnum.values()) { + if (userCodeEnum.message.equalsIgnoreCase(msg)) { + return userCodeEnum.code; + } + } + return ""; + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/enums/CarryingCapacityEnum.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/enums/CarryingCapacityEnum.java new file mode 100644 index 0000000..8831671 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/enums/CarryingCapacityEnum.java @@ -0,0 +1,164 @@ +package com.njcn.product.carrycapacity.enums; + +import lombok.Getter; + +/** +* @Description: 承载能力评估相关枚举 +* @Author: clam +* @Date: 2024/1/31 +*/ +@Getter + +public enum CarryingCapacityEnum { + + K("K","0.8","海南日照修正系数"), + /** + * h 3 ,5 ,7 ,9 ,11 ,13或者偶次 + * k_h 1.62, ,1.28 ,0.72 ,0.18 ,0.08 ,0 + */ + K_H_2("K_H_2","0.00","2次谐波电流K_2系数"), + K_H_3("K_H_3","1.62","3次谐波电流K_3系数"), + K_H_4("K_H_4","0.00","4次谐波电流K_4系数"), + K_H_5("K_H_5","1.28","5次谐波电流K_5系数"), + K_H_6("K_H_6","0.00","6次谐波电流K_6系数"), + K_H_7("K_H_7","0.72","7次谐波电流K_7系数"), + K_H_8("K_H_8","0.00","8次谐波电流K_8系数"), + K_H_9("K_H_9","0.18","9次谐波电流K_9系数"), + K_H_10("K_H_10","0.00","10次谐波电流K_10系数"), + K_H_11("K_H_11","0.08","11次谐波电流K_11系数"), + K_H_12("K_H_12","0.00","12次谐波电流K_12系数"), + K_H_13("K_H_13","0.00","13次谐波电流K_13系数"), + K_H_14("K_H_14","0.00","14次谐波电流K_14系数"), + K_H_15("K_H_15","0.00","15次谐波电流K_15系数"), + K_H_16("K_H_16","0.00","16次谐波电流K_16系数"), + K_H_17("K_H_17","0.00","17次谐波电流K_17系数"), + K_H_18("K_H_18","0.00","18次谐波电流K_18系数"), + K_H_19("K_H_19","0.00","19次谐波电流K_19系数"), + K_H_20("K_H_20","0.00","20次谐波电流K_20系数"), + K_H_21("K_H_21","0.00","21次谐波电流K_21系数"), + K_H_22("K_H_22","0.00","22次谐波电流K_22系数"), + K_H_23("K_H_23","0.00","23次谐波电流K_23系数"), + K_H_24("K_H_24","0.00","24次谐波电流K_24系数"), + K_H_25("K_H_25","0.00","25次谐波电流K_25系数"), + + //光伏逆变器第h次的典型谐波电流含有率:I_INV_H + I_INV_2("I_INV_2","0.254","2次典型谐波电流含有率"), + I_INV_3("I_INV_3","0.121","3次典型谐波电流含有率"), + I_INV_4("I_INV_4","0.087","4次典型谐波电流含有率"), + I_INV_5("I_INV_5","2.446","5次典型谐波电流含有率"), + I_INV_6("I_INV_6","0.024","6次典型谐波电流含有率"), + I_INV_7("I_INV_7","1.629","7次典型谐波电流含有率"), + I_INV_8("I_INV_8","0.042","8次典型谐波电流含有率"), + I_INV_9("I_INV_9","0.039","9次典型谐波电流含有率"), + I_INV_10("I_INV_10","0.037","10次典型谐波电流含有率"), + I_INV_11("I_INV_11","0.439","11次典型谐波电流含有率"), + I_INV_12("I_INV_12","0.021","12次典型谐波电流含有率"), + I_INV_13("I_INV_13","0.379","13次典型谐波电流含有率"), + I_INV_14("I_INV_14","0.042","14次典型谐波电流含有率"), + I_INV_15("I_INV_15","0.037","15次典型谐波电流含有率"), + I_INV_16("I_INV_16","0.043","16次典型谐波电流含有率"), + I_INV_17("I_INV_17","0.263","17次典型谐波电流含有率"), + I_INV_18("I_INV_18","0.017","18次典型谐波电流含有率"), + I_INV_19("I_INV_19","0.197","19次典型谐波电流含有率"), + I_INV_20("I_INV_20","0.062","20次典型谐波电流含有率"), + I_INV_21("I_INV_21","0.024","21次典型谐波电流含有率"), + I_INV_22("I_INV_22","0.032","22次典型谐波电流含有率"), + I_INV_23("I_INV_23","0.304","23次典型谐波电流含有率"), + I_INV_24("I_INV_24","0.03","24次典型谐波电流含有率"), + I_INV_25("I_INV_25","0.176","25次典型谐波电流含有率"), + I_INV_26("I_INV_26","0.032","26次典型谐波电流含有率"), + I_INV_27("I_INV_27","0.038","27次典型谐波电流含有率"), + I_INV_28("I_INV_28","0.031","28次典型谐波电流含有率"), + I_INV_29("I_INV_29","0.158","29次典型谐波电流含有率"), + I_INV_30("I_INV_30","0.024","30次典型谐波电流含有率"), + I_INV_31("I_INV_31","0.028","31次典型谐波电流含有率"), + I_INV_32("I_INV_32","0.026","32次典型谐波电流含有率"), + I_INV_33("I_INV_33","0.033","33次典型谐波电流含有率"), + I_INV_34("I_INV_34","0.018","34次典型谐波电流含有率"), + I_INV_35("I_INV_35","0.072","35次典型谐波电流含有率"), + + //电弧炉谐波电流含有率 +// EAF_I_2("EAF_I_2","0.6112","2次电弧炉谐波电流含有率"), + EAF_I_3("EAF_I_3","0.13484","3次电弧炉谐波电流含有率"), +// EAF_I_4("EAF_I_4","0.9906","4次电弧炉谐波电流含有率"), + EAF_I_5("EAF_I_5","0.017327","5次电弧炉谐波电流含有率"), +// EAF_I_6("EAF_I_6","0.5750","6次电弧炉谐波电流含有率"), + EAF_I_7("EAF_I_7","0.015288","7次电弧炉谐波电流含有率"), +// EAF_I_8("EAF_I_8","0.4782","8次电弧炉谐波电流含有率"), + EAF_I_9("EAF_I_9","0.001495","9次电弧炉谐波电流含有率"), +// EAF_I_10("EAF_I_10","0.6003","10次电弧炉谐波电流含有率"), + EAF_I_11("EAF_I_11","0.001203","11次电弧炉谐波电流含有率"), +// EAF_I_12("EAF_I_12","0.5242","12次电弧炉谐波电流含有率"), + EAF_I_13("EAF_I_13","0.001407","13次电弧炉谐波电流含有率"), +// EAF_I_14("EAF_I_14","0.5720","14次电弧炉谐波电流含有率"), + EAF_I_15("EAF_I_15","0.001676","15次电弧炉谐波电流含有率"), +// EAF_I_16("EAF_I_16","0.8234","16次电弧炉谐波电流含有率"), + EAF_I_17("EAF_I_17","0.001555","17次电弧炉谐波电流含有率"), +// EAF_I_18("EAF_I_18","0.8848","18次电弧炉谐波电流含有率"), + EAF_I_19("EAF_I_19","0.001159","19次电弧炉谐波电流含有率"), +// EAF_I_20("EAF_I_20","0.6789","20次电弧炉谐波电流含有率"), + + //充电桩谐波电流含有率 +// CP_I_2("CP_I_2","5.00","2次电弧炉谐波电流含有率"), + CP_I_3("CP_I_3","0.2011","3次电弧炉谐波电流含有率"), +// CP_I_4("CP_I_4","4.00","4次电弧炉谐波电流含有率"), + CP_I_5("CP_I_5","0.1069","5次电弧炉谐波电流含有率"), +// CP_I_6("CP_I_6","4.00","6次电弧炉谐波电流含有率"), + CP_I_7("CP_I_7","0.0647","7次电弧炉谐波电流含有率"), +// CP_I_8("CP_I_8","2.00","8次电弧炉谐波电流含有率"), + CP_I_9("CP_I_9","0.0376","9次电弧炉谐波电流含有率"), +// CP_I_10("CP_I_10","1.50","10次电弧炉谐波电流含有率"), + CP_I_11("CP_I_11","0.0232","11次电弧炉谐波电流含有率"), +// CP_I_12("CP_I_12","0.50","12次电弧炉谐波电流含有率"), + CP_I_13("CP_I_13","0.0155","13次电弧炉谐波电流含有率"), +// CP_I_14("CP_I_14","0.00","14次电弧炉谐波电流含有率"), + CP_I_15("CP_I_15","0.005956","15次电弧炉谐波电流含有率"), +// CP_I_16("CP_I_16","0.00","16次电弧炉谐波电流含有率"), + CP_I_17("CP_I_17","0.054185","17次电弧炉谐波电流含有率"), +// CP_I_18("CP_I_18","0.00","18次电弧炉谐波电流含有率"), + CP_I_19("CP_I_19","0.023503","19次电弧炉谐波电流含有率"), +// CP_I_20("CP_I_20","0.00","20次电弧炉谐波电流含有率"), + + //电气化铁路典型 + ER_I_3("ER_I_3","0.0068935","3次电弧炉谐波电流含有率"), + ER_I_5("ER_I_5","0.069575","5次电弧炉谐波电流含有率"), + ER_I_7("ER_I_7","0.032731","7次电弧炉谐波电流含有率"), + ER_I_9("ER_I_9","0.005197","9次电弧炉谐波电流含有率"), + ER_I_11("ER_I_11","0.045631","11次电弧炉谐波电流含有率"), + ER_I_13("ER_I_13","0.029196","13次电弧炉谐波电流含有率"), + ER_I_15("ER_I_15","0.017","15次电弧炉谐波电流含有率"), + ER_I_17("ER_I_17","0.0095","17次电弧炉谐波电流含有率"), + ER_I_19("ER_I_19","0.0080","19次电弧炉谐波电流含有率"), + + ; + /** + * 字段code + */ + private final String Code; + + /** + * 字段值 + */ + private final String value; + + /** + * 字段描述 + */ + private final String description; + + + CarryingCapacityEnum(String code, String value, String description) { + Code = code; + this.value = value; + this.description = description; + } + + public static String getValueByCode(String code) { + for (CarryingCapacityEnum item : CarryingCapacityEnum.values()) { + if (item.Code.equals(code)) { + return item.value; + } + } + return null; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityDataPOMapper.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityDataPOMapper.java new file mode 100644 index 0000000..de8a743 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityDataPOMapper.java @@ -0,0 +1,15 @@ +package com.njcn.product.carrycapacity.mapper; + +import com.github.jeffreyning.mybatisplus.base.MppBaseMapper; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityDataPO; + +/** + * + * Description: + * Date: 2024/3/6 14:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityDataPOMapper extends MppBaseMapper { +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityDevicePOMapper.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityDevicePOMapper.java new file mode 100644 index 0000000..2091703 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityDevicePOMapper.java @@ -0,0 +1,15 @@ +package com.njcn.product.carrycapacity.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityDevicePO; + +/** + * + * Description: + * Date: 2024/3/19 16:36【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityDevicePOMapper extends BaseMapper { +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityResultPOMapper.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityResultPOMapper.java new file mode 100644 index 0000000..55399ea --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityResultPOMapper.java @@ -0,0 +1,14 @@ +package com.njcn.product.carrycapacity.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityResultPO; + +/** + * Description: + * Date: 2024/3/8 16:23【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityResultPOMapper extends BaseMapper { +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityStrategyDhlPOMapper.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityStrategyDhlPOMapper.java new file mode 100644 index 0000000..e8c6480 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityStrategyDhlPOMapper.java @@ -0,0 +1,15 @@ +package com.njcn.product.carrycapacity.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityStrategyDhlPO; + +/** + * + * Description: + * Date: 2024/3/15 10:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityStrategyDhlPOMapper extends BaseMapper { +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityStrategyPOMapper.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityStrategyPOMapper.java new file mode 100644 index 0000000..10d40c7 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityStrategyPOMapper.java @@ -0,0 +1,14 @@ +package com.njcn.product.carrycapacity.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityStrategyPO; + +/** + * Description: + * Date: 2024/3/5 10:54【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityStrategyPOMapper extends BaseMapper { +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityUserPOMapper.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityUserPOMapper.java new file mode 100644 index 0000000..d2c69d0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/CarryCapacityUserPOMapper.java @@ -0,0 +1,15 @@ +package com.njcn.product.carrycapacity.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityUserPO; + +/** + * + * Description: + * Date: 2024/2/20 11:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityUserPOMapper extends BaseMapper { +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDataPOMapper.xml b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDataPOMapper.xml new file mode 100644 index 0000000..329dfad --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDataPOMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDevicePOMapper.xml b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDevicePOMapper.xml new file mode 100644 index 0000000..8bae8e0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDevicePOMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityResultPOMapper.xml b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityResultPOMapper.xml new file mode 100644 index 0000000..06f78d4 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityResultPOMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyDhlPOMapper.xml b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyDhlPOMapper.xml new file mode 100644 index 0000000..5cd8d4c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyDhlPOMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyPOMapper.xml b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyPOMapper.xml new file mode 100644 index 0000000..220e56d --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyPOMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityUserPOMapper.xml b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityUserPOMapper.xml new file mode 100644 index 0000000..05e7f97 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityUserPOMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataEexcel.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataEexcel.java new file mode 100644 index 0000000..07078a4 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataEexcel.java @@ -0,0 +1,292 @@ +package com.njcn.product.carrycapacity.pojo.excel; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.converters.localdatetime.LocalDateTimeStringConverter; +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2022/5/12 9:13 + */ +@Data +public class CarryCapcityDataEexcel { + + @ExcelProperty(index = 0,value = "时间(格式为yyyy-MM-dd hh:mm:ss)",converter = LocalDateTimeStringConverter.class) + @JSONField(format = "yyyy-MM-dd hh:mm:ss") + private LocalDateTime time; + + @ExcelProperty(index =1,value = {"电压","A"}) + private Double u_a; + + @ExcelProperty(index =2,value = {"电压","B"}) + private Double u_b; + + @ExcelProperty(index =3,value = {"电压","C"}) + private Double u_c; + + + @ExcelProperty(index =4,value = {"有功功率","A"}) + private Double p_a; + + @ExcelProperty(index =5,value = {"有功功率","B"}) + private Double p_b; + + @ExcelProperty(index =6,value = {"有功功率","C"}) + private Double p_c; + + @ExcelProperty(index =7,value = {"无功功率","A"}) + private Double q_a; + + @ExcelProperty(index =8,value = {"无功功率","B"}) + private Double q_b; + + @ExcelProperty(index =9,value = {"无功功率","C"}) + private Double q_c; + + @ExcelProperty(index =10,value = {"电流","2次","A"}) + private Double i2_a; + + @ExcelProperty(index =11,value = {"电流","2次","B"}) + private Double i2_b; + + @ExcelProperty(index =12,value = {"电流","2次","C"}) + private Double i2_c; + + @ExcelProperty(index =13,value = {"电流","3次","A"}) + private Double i3_a; + + @ExcelProperty(index =14,value = {"电流","3次","B"}) + private Double i3_b; + + @ExcelProperty(index =15,value = {"电流","3次","C"}) + private Double i3_c; + + @ExcelProperty(index =16,value = {"电流","4次","A"}) + private Double i4_a; + + @ExcelProperty(index =17,value = {"电流","4次","B"}) + private Double i4_b; + + @ExcelProperty(index =18,value = {"电流","4次","C"}) + private Double i4_c; + + @ExcelProperty(index =19,value = {"电流","5次","A"}) + private Double i5_a; + + @ExcelProperty(index =20,value = {"电流","5次","B"}) + private Double i5_b; + + @ExcelProperty(index =21,value = {"电流","5次","C"}) + private Double i5_c; + + @ExcelProperty(index =22,value = {"电流","6次","A"}) + private Double i6_a; + + @ExcelProperty(index =23,value = {"电流","6次","B"}) + private Double i6_b; + + @ExcelProperty(index =24,value = {"电流","6次","C"}) + private Double i6_c; + + @ExcelProperty(index =25,value = {"电流","7次","A"}) + private Double i7_a; + + @ExcelProperty(index =26,value = {"电流","7次","B"}) + private Double i7_b; + + @ExcelProperty(index =27,value = {"电流","7次","C"}) + private Double i7_c; + + @ExcelProperty(index =28,value = {"电流","8次","A"}) + private Double i8_a; + + @ExcelProperty(index =29,value = {"电流","8次","B"}) + private Double i8_b; + + @ExcelProperty(index =30,value = {"电流","8次","C"}) + private Double i8_c; + + @ExcelProperty(index =31,value = {"电流","9次","A"}) + private Double i9_a; + + @ExcelProperty(index =32,value = {"电流","9次","B"}) + private Double i9_b; + + @ExcelProperty(index =33,value = {"电流","9次","C"}) + private Double i9_c; + + @ExcelProperty(index =34,value = {"电流","10次","A"}) + private Double i10_a; + + @ExcelProperty(index =35,value = {"电流","10次","B"}) + private Double i10_b; + + @ExcelProperty(index =36,value = {"电流","10次","C"}) + private Double i10_c; + + @ExcelProperty(index =37,value = {"电流","11次","A"}) + private Double i11_a; + + @ExcelProperty(index =38,value = {"电流","11次","B"}) + private Double i11_b; + + @ExcelProperty(index =39,value = {"电流","11次","C"}) + private Double i11_c; + + @ExcelProperty(index =40,value = {"电流","12次","A"}) + private Double i12_a; + + @ExcelProperty(index =41,value = {"电流","12次","B"}) + private Double i12_b; + + @ExcelProperty(index =42,value = {"电流","12次","C"}) + private Double i12_c; + + @ExcelProperty(index =43,value = {"电流","13次","A"}) + private Double i13_a; + + @ExcelProperty(index =44,value = {"电流","13次","B"}) + private Double i13_b; + + @ExcelProperty(index =45,value = {"电流","13次","C"}) + private Double i13_c; + + @ExcelProperty(index =46,value = {"电流","14次","A"}) + private Double i14_a; + + @ExcelProperty(index =47,value = {"电流","14次","B"}) + private Double i14_b; + + @ExcelProperty(index =48,value = {"电流","14次","C"}) + private Double i14_c; + + @ExcelProperty(index =49,value = {"电流","15次","A"}) + private Double i15_a; + + @ExcelProperty(index =50,value = {"电流","15次","B"}) + private Double i15_b; + + @ExcelProperty(index =51,value = {"电流","15次","C"}) + private Double i15_c; + + @ExcelProperty(index =52,value = {"电流","16次","A"}) + private Double i16_a; + + @ExcelProperty(index =53,value = {"电流","16次","B"}) + private Double i16_b; + + @ExcelProperty(index =54,value = {"电流","16次","C"}) + private Double i16_c; + + @ExcelProperty(index =55,value = {"电流","17次","A"}) + private Double i17_a; + + @ExcelProperty(index =56,value = {"电流","17次","B"}) + private Double i17_b; + + @ExcelProperty(index =57,value = {"电流","17次","C"}) + private Double i17_c; + + @ExcelProperty(index =58,value = {"电流","18次","A"}) + private Double i18_a; + + @ExcelProperty(index =59,value = {"电流","18次","B"}) + private Double i18_b; + + @ExcelProperty(index =60,value = {"电流","18次","C"}) + private Double i18_c; + + @ExcelProperty(index =61,value = {"电流","19次","A"}) + private Double i19_a; + + @ExcelProperty(index =62,value = {"电流","19次","B"}) + private Double i19_b; + + @ExcelProperty(index =63,value = {"电流","19次","C"}) + private Double i19_c; + + @ExcelProperty(index =64,value = {"电流","20次","A"}) + private Double i20_a; + + @ExcelProperty(index =65,value = {"电流","20次","B"}) + private Double i20_b; + + @ExcelProperty(index =66,value = {"电流","20次","C"}) + private Double i20_c; + + @ExcelProperty(index =67,value = {"电流","21次","A"}) + private Double i21_a; + + @ExcelProperty(index =68,value = {"电流","21次","B"}) + private Double i21_b; + + @ExcelProperty(index =69,value = {"电流","21次","C"}) + private Double i21_c; + + @ExcelProperty(index =70,value = {"电流","22次","A"}) + private Double i22_a; + + @ExcelProperty(index =71,value = {"电流","22次","B"}) + private Double i22_b; + + @ExcelProperty(index =72,value = {"电流","22次","C"}) + private Double i22_c; + + @ExcelProperty(index =73,value = {"电流","23次","A"}) + private Double i23_a; + + @ExcelProperty(index =74,value = {"电流","23次","B"}) + private Double i23_b; + + @ExcelProperty(index =75,value = {"电流","23次","C"}) + private Double i23_c; + + @ExcelProperty(index =76,value = {"电流","24次","A"}) + private Double i24_a; + + @ExcelProperty(index =77,value = {"电流","24次","B"}) + private Double i24_b; + + @ExcelProperty(index =78,value = {"电流","24次","C"}) + private Double i24_c; + + @ExcelProperty(index =79,value = {"电流","25次","A"}) + private Double i25_a; + + @ExcelProperty(index =80,value = {"电流","25次","B"}) + private Double i25_b; + + @ExcelProperty(index =81,value = {"电流","25次","C"}) + private Double i25_c; + + public static void main(String[] args) { +// List objects = EasyExcelUtil.syncReadModel("C:\\Users\\无名\\Desktop\\11.xlsx", CarryCapcityDataEexcel.class, 0,3); +// System.out.println(objects); + + String sheetName = "sheetName"; + List excels = new ArrayList<>(); + CarryCapcityDataEexcel exportHeadersExcel = new CarryCapcityDataEexcel(); + excels.add(exportHeadersExcel); + + EasyExcel.write("C:\\\\Users\\\\无名\\\\Desktop\\\\22.xlsx", CarryCapcityDataEexcel.class) + .sheet(sheetName) + .doWrite(excels); + EasyExcel.write("C:\\\\Users\\\\无名\\\\Desktop\\\\22.xlsx", CarryCapcityDataEexcel.class) + .sheet("sheetName2") + .doWrite(excels); + + } + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataIEexcel.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataIEexcel.java new file mode 100644 index 0000000..fcb6cdc --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataIEexcel.java @@ -0,0 +1,424 @@ +package com.njcn.product.carrycapacity.pojo.excel; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.njcn.influx.pojo.po.DataI; +import lombok.Data; +import org.influxdb.annotation.Column; +import org.springframework.beans.BeanUtils; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2022/5/12 9:13 + */ +@Data +public class CarryCapcityDataIEexcel { + + @Excel(name = "时间",width = 30) + private Instant time; + + @Column(name = "line_id") + @Excel(name = "监测点id",width = 30) + private String lineId; + + @Excel(name = "数据类型(CP95)",width = 30) + private String valueType; + + + @Column(name = "i_2") + @Excel(name = "A项2次谐波幅值",width = 15) + private Double i2_a; + + @Column(name = "i_3") + @Excel(name = "A项3次谐波幅值",width = 15) + private Double i3_a; + + @Column(name = "i_4") + @Excel(name = "A项4次谐波幅值",width = 15) + private Double i4_a; + + @Column(name = "i_5") + @Excel(name = "A项5次谐波幅值",width = 15) + private Double i5_a; + + @Column(name = "i_6") + @Excel(name = "A项6次谐波幅值",width = 15) + private Double i6_a; + + @Column(name = "i_7") + @Excel(name = "A项7次谐波幅值",width = 15) + private Double i7_a; + + @Column(name = "i_8") + @Excel(name = "A项8次谐波幅值",width = 15) + private Double i8_a; + + @Column(name = "i_9") + @Excel(name = "A项9次谐波幅值",width = 15) + private Double i9_a; + + @Column(name = "i_10") + @Excel(name = "A项10次谐波幅值",width = 15) + private Double i10_a; + + @Column(name = "i_11") + @Excel(name = "A项11次谐波幅值",width = 15) + private Double i11_a; + + @Column(name = "i_12") + @Excel(name = "A项12次谐波幅值",width = 15) + private Double i12_a; + + @Column(name = "i_13") + @Excel(name = "A项13次谐波幅值",width = 15) + private Double i13_a; + + @Column(name = "i_14") + @Excel(name = "A项14次谐波幅值",width = 15) + private Double i14_a; + + @Column(name = "i_15") + @Excel(name = "A项15次谐波幅值",width = 15) + private Double i15_a; + + @Column(name = "i_16") + @Excel(name = "A项16次谐波幅值",width = 15) + private Double i16_a; + + @Column(name = "i_17") + @Excel(name = "A项17次谐波幅值",width = 15) + private Double i17_a; + + @Column(name = "i_18") + @Excel(name = "A项18次谐波幅值",width = 15) + private Double i18_a; + + @Column(name = "i_19") + @Excel(name = "A项19次谐波幅值",width = 15) + private Double i19_a; + + @Column(name = "i_20") + @Excel(name = "A项20次谐波幅值",width = 15) + private Double i20_a; + + @Column(name = "i_21") + @Excel(name = "A项21次谐波幅值",width = 15) + private Double i21_a; + + @Column(name = "i_22") + @Excel(name = "A项22次谐波幅值",width = 15) + private Double i22_a; + + @Column(name = "i_23") + @Excel(name = "A项23次谐波幅值",width = 15) + private Double i23_a; + + @Column(name = "i_24") + @Excel(name = "A项24次谐波幅值",width = 15) + private Double i24_a; + @Column(name = "i_25") + @Excel(name = "A项25次谐波幅值",width = 15) + private Double i25_a; + + @Column(name = "i_2") + @Excel(name = "B项2次谐波幅值",width = 15) + private Double i2_b; + + @Column(name = "i_3") + @Excel(name = "B项3次谐波幅值",width = 15) + private Double i3_b; + + @Column(name = "i_4") + @Excel(name = "B项4次谐波幅值",width = 15) + private Double i4_b; + + @Column(name = "i_5") + @Excel(name = "B项5次谐波幅值",width = 15) + private Double i5_b; + + @Column(name = "i_6") + @Excel(name = "B项6次谐波幅值",width = 15) + private Double i6_b; + + @Column(name = "i_7") + @Excel(name = "B项7次谐波幅值",width = 15) + private Double i7_b; + + @Column(name = "i_8") + @Excel(name = "B项8次谐波幅值",width = 15) + private Double i8_b; + + @Column(name = "i_9") + @Excel(name = "B项9次谐波幅值",width = 15) + private Double i9_b; + + @Column(name = "i_10") + @Excel(name = "B项10次谐波幅值",width = 15) + private Double i10_b; + + @Column(name = "i_11") + @Excel(name = "B项11次谐波幅值",width = 15) + private Double i11_b; + + @Column(name = "i_12") + @Excel(name = "B项12次谐波幅值",width = 15) + private Double i12_b; + + @Column(name = "i_13") + @Excel(name = "B项13次谐波幅值",width = 15) + private Double i13_b; + + @Column(name = "i_14") + @Excel(name = "B项14次谐波幅值",width = 15) + private Double i14_b; + + @Column(name = "i_15") + @Excel(name = "B项15次谐波幅值",width = 15) + private Double i15_b; + + @Column(name = "i_16") + @Excel(name = "B项16次谐波幅值",width = 15) + private Double i16_b; + + @Column(name = "i_17") + @Excel(name = "B项17次谐波幅值",width = 15) + private Double i17_b; + + @Column(name = "i_18") + @Excel(name = "B项18次谐波幅值",width = 15) + private Double i18_b; + + @Column(name = "i_19") + @Excel(name = "B项19次谐波幅值",width = 15) + private Double i19_b; + + @Column(name = "i_20") + @Excel(name = "B项20次谐波幅值",width = 15) + private Double i20_b; + + @Column(name = "i_21") + @Excel(name = "B项21次谐波幅值",width = 15) + private Double i21_b; + + @Column(name = "i_22") + @Excel(name = "B项22次谐波幅值",width = 15) + private Double i22_b; + + @Column(name = "i_23") + @Excel(name = "B项23次谐波幅值",width = 15) + private Double i23_b; + + @Column(name = "i_24") + @Excel(name = "B项24次谐波幅值",width = 15) + private Double i24_b; + @Column(name = "i_25") + @Excel(name = "B项25次谐波幅值",width = 15) + private Double i25_b; + + + + @Column(name = "i_2") + @Excel(name = "C项2次谐波幅值",width = 15) + private Double i2_c; + + @Column(name = "i_3") + @Excel(name = "C项3次谐波幅值",width = 15) + private Double i3_c; + + @Column(name = "i_4") + @Excel(name = "C项4次谐波幅值",width = 15) + private Double i4_c; + + @Column(name = "i_5") + @Excel(name = "C项5次谐波幅值",width = 15) + private Double i5_c; + + @Column(name = "i_6") + @Excel(name = "C项6次谐波幅值",width = 15) + private Double i6_c; + + @Column(name = "i_7") + @Excel(name = "C项7次谐波幅值",width = 15) + private Double i7_c; + + @Column(name = "i_8") + @Excel(name = "C项8次谐波幅值",width = 15) + private Double i8_c; + + @Column(name = "i_9") + @Excel(name = "C项9次谐波幅值",width = 15) + private Double i9_c; + + @Column(name = "i_10") + @Excel(name = "C项10次谐波幅值",width = 15) + private Double i10_c; + + @Column(name = "i_11") + @Excel(name = "C项11次谐波幅值",width = 15) + private Double i11_c; + + @Column(name = "i_12") + @Excel(name = "C项12次谐波幅值",width = 15) + private Double i12_c; + + @Column(name = "i_13") + @Excel(name = "C项13次谐波幅值",width = 15) + private Double i13_c; + + @Column(name = "i_14") + @Excel(name = "C项14次谐波幅值",width = 15) + private Double i14_c; + + @Column(name = "i_15") + @Excel(name = "C项15次谐波幅值",width = 15) + private Double i15_c; + + @Column(name = "i_16") + @Excel(name = "C项16次谐波幅值",width = 15) + private Double i16_c; + + @Column(name = "i_17") + @Excel(name = "C项17次谐波幅值",width = 15) + private Double i17_c; + + @Column(name = "i_18") + @Excel(name = "C项18次谐波幅值",width = 15) + private Double i18_c; + + @Column(name = "i_19") + @Excel(name = "C项19次谐波幅值",width = 15) + private Double i19_c; + + @Column(name = "i_20") + @Excel(name = "C项20次谐波幅值",width = 15) + private Double i20_c; + + @Column(name = "i_21") + @Excel(name = "C项21次谐波幅值",width = 15) + private Double i21_c; + + @Column(name = "i_22") + @Excel(name = "C项22次谐波幅值",width = 15) + private Double i22_c; + + @Column(name = "i_23") + @Excel(name = "C项23次谐波幅值",width = 15) + private Double i23_c; + + @Column(name = "i_24") + @Excel(name = "C项24次谐波幅值",width = 15) + private Double i24_c; + @Column(name = "i_25") + @Excel(name = "C项25次谐波幅值",width = 15) + private Double i25_c; + + //excel对象转DataI + public static List excelToPO(CarryCapcityDataIEexcel carryCapcityDataIEexcel) { + List data = new ArrayList<>(); + if (carryCapcityDataIEexcel == null) { + return null; + } + List phaseList = Stream.of("A", "B", "C").collect(Collectors.toList()); + for (String phase : phaseList) { + DataI dataI = new DataI(); + BeanUtils.copyProperties(carryCapcityDataIEexcel,dataI); + dataI.setPhaseType(phase); + dataI.setTime(carryCapcityDataIEexcel.getTime()); + + if (phase.equals("A")) { + + dataI.setI2( carryCapcityDataIEexcel.getI2_a()); + dataI.setI3( carryCapcityDataIEexcel.getI3_a()); + dataI.setI4( carryCapcityDataIEexcel.getI4_a()); + dataI.setI5( carryCapcityDataIEexcel.getI5_a()); + dataI.setI6( carryCapcityDataIEexcel.getI6_a()); + dataI.setI7( carryCapcityDataIEexcel.getI7_a()); + dataI.setI8( carryCapcityDataIEexcel.getI8_a()); + dataI.setI9( carryCapcityDataIEexcel.getI9_a()); + dataI.setI10( carryCapcityDataIEexcel.getI10_a()); + dataI.setI11( carryCapcityDataIEexcel.getI11_a()); + dataI.setI12( carryCapcityDataIEexcel.getI12_a()); + dataI.setI13( carryCapcityDataIEexcel.getI13_a()); + dataI.setI14( carryCapcityDataIEexcel.getI14_a()); + dataI.setI15( carryCapcityDataIEexcel.getI15_a()); + dataI.setI16( carryCapcityDataIEexcel.getI16_a()); + dataI.setI17( carryCapcityDataIEexcel.getI17_a()); + dataI.setI18( carryCapcityDataIEexcel.getI18_a()); + dataI.setI19( carryCapcityDataIEexcel.getI19_a()); + dataI.setI20( carryCapcityDataIEexcel.getI20_a()); + dataI.setI21( carryCapcityDataIEexcel.getI21_a()); + dataI.setI22( carryCapcityDataIEexcel.getI22_a()); + dataI.setI23( carryCapcityDataIEexcel.getI23_a()); + dataI.setI24( carryCapcityDataIEexcel.getI24_a()); + dataI.setI25( carryCapcityDataIEexcel.getI25_a()); + + + } else if (phase.equals("B")) { + dataI.setI2( carryCapcityDataIEexcel.getI2_b()); + dataI.setI3( carryCapcityDataIEexcel.getI3_b()); + dataI.setI4( carryCapcityDataIEexcel.getI4_b()); + dataI.setI5( carryCapcityDataIEexcel.getI5_b()); + dataI.setI6( carryCapcityDataIEexcel.getI6_b()); + dataI.setI7( carryCapcityDataIEexcel.getI7_b()); + dataI.setI8( carryCapcityDataIEexcel.getI8_b()); + dataI.setI9( carryCapcityDataIEexcel.getI9_b()); + dataI.setI10( carryCapcityDataIEexcel.getI10_b()); + dataI.setI11( carryCapcityDataIEexcel.getI11_b()); + dataI.setI12( carryCapcityDataIEexcel.getI12_b()); + dataI.setI13( carryCapcityDataIEexcel.getI13_b()); + dataI.setI14( carryCapcityDataIEexcel.getI14_b()); + dataI.setI15( carryCapcityDataIEexcel.getI15_b()); + dataI.setI16( carryCapcityDataIEexcel.getI16_b()); + dataI.setI17( carryCapcityDataIEexcel.getI17_b()); + dataI.setI18( carryCapcityDataIEexcel.getI18_b()); + dataI.setI19( carryCapcityDataIEexcel.getI19_b()); + dataI.setI20( carryCapcityDataIEexcel.getI20_b()); + dataI.setI21( carryCapcityDataIEexcel.getI21_b()); + dataI.setI22( carryCapcityDataIEexcel.getI22_b()); + dataI.setI23( carryCapcityDataIEexcel.getI23_b()); + dataI.setI24( carryCapcityDataIEexcel.getI24_b()); + dataI.setI25( carryCapcityDataIEexcel.getI25_b()); + + }else if (phase.equals("C")){ + dataI.setI2( carryCapcityDataIEexcel.getI2_c()); + dataI.setI3( carryCapcityDataIEexcel.getI3_c()); + dataI.setI4( carryCapcityDataIEexcel.getI4_c()); + dataI.setI5( carryCapcityDataIEexcel.getI5_c()); + dataI.setI6( carryCapcityDataIEexcel.getI6_c()); + dataI.setI7( carryCapcityDataIEexcel.getI7_c()); + dataI.setI8( carryCapcityDataIEexcel.getI8_c()); + dataI.setI9( carryCapcityDataIEexcel.getI9_c()); + dataI.setI10( carryCapcityDataIEexcel.getI10_c()); + dataI.setI11( carryCapcityDataIEexcel.getI11_c()); + dataI.setI12( carryCapcityDataIEexcel.getI12_c()); + dataI.setI13( carryCapcityDataIEexcel.getI13_c()); + dataI.setI14( carryCapcityDataIEexcel.getI14_c()); + dataI.setI15( carryCapcityDataIEexcel.getI15_c()); + dataI.setI16( carryCapcityDataIEexcel.getI16_c()); + dataI.setI17( carryCapcityDataIEexcel.getI17_c()); + dataI.setI18( carryCapcityDataIEexcel.getI18_c()); + dataI.setI19( carryCapcityDataIEexcel.getI19_c()); + dataI.setI20( carryCapcityDataIEexcel.getI20_c()); + dataI.setI21( carryCapcityDataIEexcel.getI21_c()); + dataI.setI22( carryCapcityDataIEexcel.getI22_c()); + dataI.setI23( carryCapcityDataIEexcel.getI23_c()); + dataI.setI24( carryCapcityDataIEexcel.getI24_c()); + dataI.setI25( carryCapcityDataIEexcel.getI25_c()); + + } + data.add(dataI); + } + return data; + } + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataPEexcel.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataPEexcel.java new file mode 100644 index 0000000..08fed37 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataPEexcel.java @@ -0,0 +1,65 @@ +package com.njcn.product.carrycapacity.pojo.excel; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.njcn.influx.pojo.bo.CarryCapcityData; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2022/5/12 9:13 + */ +@Data +public class CarryCapcityDataPEexcel { + + @Excel(name = "时间",width = 30) + private Instant time; + + @Excel(name = "监测点id",width = 30) + private String lineId; + + @Excel(name = "数据类型(CP95)",width = 30) + private String valueType; + + @Excel(name = "数据(A项有功功率)",width = 30) + private Double value_a; + @Excel(name = "数据(B项有功功率)",width = 30) + private Double value_b; + @Excel(name = "数据(C项有功功率)",width = 30) + private Double value_c; + + public static List excelToPO(CarryCapcityDataPEexcel carryCapcityDataPEexcel) { + List data = new ArrayList<>(); + if (carryCapcityDataPEexcel == null) { + return null; + } + List phaseList = Stream.of("A", "B", "C").collect(Collectors.toList()); + for (String phase : phaseList) { + CarryCapcityData carryCapcityData = new CarryCapcityData(); + BeanUtils.copyProperties(carryCapcityDataPEexcel,carryCapcityData); + carryCapcityData.setPhaseType(phase); + carryCapcityData.setTime(carryCapcityDataPEexcel.getTime()); + + if (phase.equals("A")) { + carryCapcityData.setValue(carryCapcityDataPEexcel.getValue_a()); + } else if (phase.equals("B")) { + carryCapcityData.setValue(carryCapcityDataPEexcel.getValue_b()); + }else if (phase.equals("C")){ + carryCapcityData.setValue(carryCapcityDataPEexcel.getValue_c()); + } + data.add(carryCapcityData); + + } + return data; + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataQEexcel.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataQEexcel.java new file mode 100644 index 0000000..630ca62 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataQEexcel.java @@ -0,0 +1,67 @@ +package com.njcn.product.carrycapacity.pojo.excel; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.njcn.influx.pojo.bo.CarryCapcityData; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2022/5/12 9:13 + */ +@Data +public class CarryCapcityDataQEexcel { + + @Excel(name = "时间",width = 30) + private Instant time; + + @Excel(name = "监测点id",width = 30) + private String lineId; + + + @Excel(name = "数据类型(CP95)",width = 30) + private String valueType; + + @Excel(name = "数据(A项无功功率)",width = 30) + private Double value_a; + @Excel(name = "数据(B项无功功率)",width = 30) + private Double value_b; + @Excel(name = "数据(C项无功功率)",width = 30) + private Double value_c; + + + public static List excelToPO(CarryCapcityDataQEexcel carryCapcityDataQEexcel) { + List data = new ArrayList<>(); + if (carryCapcityDataQEexcel == null) { + return null; + } + List phaseList = Stream.of("A", "B", "C").collect(Collectors.toList()); + for (String phase : phaseList) { + CarryCapcityData carryCapcityData = new CarryCapcityData(); + BeanUtils.copyProperties(carryCapcityDataQEexcel,carryCapcityData); + carryCapcityData.setPhaseType(phase); + carryCapcityData.setTime(carryCapcityDataQEexcel.getTime()); + + if (phase.equals("A")) { + carryCapcityData.setValue(carryCapcityDataQEexcel.getValue_a()); + } else if (phase.equals("B")) { + carryCapcityData.setValue(carryCapcityDataQEexcel.getValue_b()); + }else if (phase.equals("C")){ + carryCapcityData.setValue(carryCapcityDataQEexcel.getValue_c()); + } + data.add(carryCapcityData); + + } + return data; + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataVEexcel.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataVEexcel.java new file mode 100644 index 0000000..3555f79 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/CarryCapcityDataVEexcel.java @@ -0,0 +1,65 @@ +package com.njcn.product.carrycapacity.pojo.excel; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.njcn.influx.pojo.bo.CarryCapcityData; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2022/5/12 9:13 + */ +@Data +public class CarryCapcityDataVEexcel { + + @Excel(name = "时间",width = 30) + private Instant time; + + @Excel(name = "监测点id",width = 30) + private String lineId; + + @Excel(name = "数据类型(CP95)",width = 30) + private String valueType; + + + @Excel(name = "数据(A项电压)",width = 30) + private Double value_a; + @Excel(name = "数据(B项电压)",width = 30) + private Double value_b; + @Excel(name = "数据(C项电压)",width = 30) + private Double value_c; + + public static List excelToPO(CarryCapcityDataVEexcel carryCapcityDataVEexcel) { + List data = new ArrayList<>(); + if (carryCapcityDataVEexcel == null) { + return null; + } + List phaseList = Stream.of("A", "B", "C").collect(Collectors.toList()); + for (String phase : phaseList) { + CarryCapcityData carryCapcityData = new CarryCapcityData(); + BeanUtils.copyProperties(carryCapcityDataVEexcel,carryCapcityData); + carryCapcityData.setPhaseType(phase); + carryCapcityData.setTime(carryCapcityDataVEexcel.getTime()); + + if (phase.equals("A")) { + carryCapcityData.setValue(carryCapcityDataVEexcel.getValue_a()); + } else if (phase.equals("B")) { + carryCapcityData.setValue(carryCapcityDataVEexcel.getValue_b()); + }else if (phase.equals("C")){ + carryCapcityData.setValue(carryCapcityDataVEexcel.getValue_c()); + } + data.add(carryCapcityData); + + } + return data; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/ExcelDataDTO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/ExcelDataDTO.java new file mode 100644 index 0000000..21f8503 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/excel/ExcelDataDTO.java @@ -0,0 +1,24 @@ +package com.njcn.product.carrycapacity.pojo.excel; + +import com.njcn.influx.pojo.bo.CarryCapcityData; +import com.njcn.influx.pojo.po.DataI; +import lombok.Data; + +import java.util.List; + +/** + * Description: + * Date: 2024/3/12 14:31【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class ExcelDataDTO { + private List dataHarmPowerPList; + private List dataHarmPowerQList; + private List dataIList; + private List dataHarmPowerP2List; + private List dataHarmPowerQ2List; + private List dataHarmPowerU2List; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityCalParam.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityCalParam.java new file mode 100644 index 0000000..b960b21 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityCalParam.java @@ -0,0 +1,54 @@ +package com.njcn.product.carrycapacity.pojo.param; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +/** + * + * Description: + * Date: 2024/2/20 11:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CarryCapacityCalParam { + @NotBlank(message = "参数不能为空") + @ApiModelProperty("监测点索引") + private String lineId; + @ApiModelProperty("用户Id") + private String userId; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd") + private LocalDate startTime; + @ApiModelProperty("结束时间") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd") + private LocalDate endTime; + @ApiModelProperty(name = "scale",value = "电压等级") + private String scale; + @ApiModelProperty(name = "S_T",value = "S_T为配变额定容量(监测点基准容量)") + private Double S_T; + @ApiModelProperty(name = "S_pv",value = "S_pv为拟接入光伏容量") + private Double S_pv; + @ApiModelProperty(name = "stringMap",value = "首端电流模型参数A,B,C三项") + private Map stringMap; + + @ApiModelProperty(name = "P_βminMap",value = "有功功率最小CP95值A,B,C三项") + private Map P_βminMap; + + @ApiModelProperty(name = "Q_βminMap",value = "无功功率最小CP95值A,B,C三项") + private Map Q_βminMap; + + @ApiModelProperty(name = "I_βmax",value = "2-25次谐波幅值最大95概率值A,B,C三项中的最大值") + private List I_βmax; + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityDeviceParam.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityDeviceParam.java new file mode 100644 index 0000000..041a42c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityDeviceParam.java @@ -0,0 +1,51 @@ +package com.njcn.product.carrycapacity.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.web.constant.ValidMessage; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +/** + * Description: + * Date: 2024/3/20 9:59【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class CarryCapacityDeviceParam { + + private String userId; + /** + * 设备名称 + */ + private String devName; + + private String devScale; + + /** + * 设备用户协议容量(MVA) + */ + private Double protocolCapacity; + + @Data + @EqualsAndHashCode(callSuper = true) + public static class CarryCapacityDeviceUpdateParam extends CarryCapacityDeviceParam { + @ApiModelProperty("设备Id") + @NotBlank(message = "设备Id不能为空") + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR) + private String devId; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class CarryCapacityDeviceQueryParam extends CarryCapacityDeviceParam { + @ApiModelProperty("设备Id") + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR) + private String devId; + } +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityEvaluateParam.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityEvaluateParam.java new file mode 100644 index 0000000..79e9cb6 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityEvaluateParam.java @@ -0,0 +1,51 @@ +package com.njcn.product.carrycapacity.pojo.param; + +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityDevicePO; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * + * Description: + * Date: 2024/2/20 11:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CarryCapacityEvaluateParam { + @ApiModelProperty(value = "接线类型不可为空\"星型接法_0\", \"三角型接法_1\", \"开口三角型接法_2\"") + @NotBlank(message = "接线类型不能为空") + private String ptType; + @ApiModelProperty(value = "变压器连接方式") + private String connectionMode; + @ApiModelProperty(value = "功率因数(0.95-1之间)") + @NotNull(message = "功率因数不能为空") + private Double k; + + @ApiModelProperty(value = "专变用户,公变用户") + @NotBlank(message = "用户类型不能为空") + private String userMode; + + + @ApiModelProperty(name = "scale",value = "电压等级") + @NotBlank(message = "电压等级不能为空") + private String scale; + + @ApiModelProperty(name = "shortCapacity",value = "短路容量") + private Float shortCapacity; + + + @ApiModelProperty(name = "deviceCapacity",value = "设备容量") + private Float deviceCapacity; + @ApiModelProperty(name = "userList",value = "干扰源用户设备列表") + private List devList; +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityQueryDataParam.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityQueryDataParam.java new file mode 100644 index 0000000..1c94234 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityQueryDataParam.java @@ -0,0 +1,42 @@ +package com.njcn.product.carrycapacity.pojo.param; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import java.time.LocalDate; + +/** + * + * Description: + * Date: 2024/2/20 11:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CarryCapacityQueryDataParam { + + + @ApiModelProperty("监测点索引") + private String lineId; + @ApiModelProperty("用户Id") + private String userId; + @ApiModelProperty("开始时间") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd") + private LocalDate startTime; + @ApiModelProperty("结束时间") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd") + private LocalDate endTime; + + @Min(2) + @Max(25) + @ApiModelProperty("谐波次数") + private Integer time=2; +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityResultParam.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityResultParam.java new file mode 100644 index 0000000..e48565e --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityResultParam.java @@ -0,0 +1,52 @@ +package com.njcn.product.carrycapacity.pojo.param; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +/** + * Description: + * Date: 2024/3/8 16:23【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CarryCapacityResultParam { + /** + * 承载能力评估id + */ + private String id; + + + private String evaluateType; + + + @Data + @EqualsAndHashCode(callSuper = true) + public static class CarryCapacityResultPageParam extends CarryCapacityResultParam { + + @NotNull(message="当前页不能为空!") + @Min(value = 1, message = "当前页不能为0") + @ApiModelProperty(value = "当前页",name = "pageNum",dataType ="Integer",required = true) + private Integer pageNum; + /**显示条数*/ + @NotNull(message="显示条数不能为空!") + @ApiModelProperty(value = "显示条数",name = "pageSize",dataType ="Integer",required = true) + private Integer pageSize; + + @ApiModelProperty(value="起始时间") + private String startTime; + + @ApiModelProperty(value="结束时间") + private String endTime; + + } +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityStrategyParam.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityStrategyParam.java new file mode 100644 index 0000000..b2d6b0b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityStrategyParam.java @@ -0,0 +1,53 @@ +package com.njcn.product.carrycapacity.pojo.param; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * Description: + * Date: 2024/3/5 10:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CarryCapacityStrategyParam { + + private String id; + /** + * 总承载能力评估结果(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + @ApiModelProperty(value = "总承载能力评估结果(1-安全,2-III级预警,3-II级预警,4-I 级预警)") + @NotNull(message = "总承载能力评估结果不能为空") + private Integer result; + + /** + * 指标评估结果(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + @ApiModelProperty(value = "指标评估结果(1-安全,2-III级预警,3-II级预警,4-I 级预警)") + @NotNull(message = "指标评估结果不能为空") + private Integer indexResult; + + /** + * 比较符 + */ + @ApiModelProperty(value = "比较符") + @NotBlank(message = "比较符不能为空") + private String comparisonOperators; + + /** + * 数量 + */ + @ApiModelProperty(value = "数量") + @NotNull(message = "数量不能为空") + private Integer count; + + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityUserParam.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityUserParam.java new file mode 100644 index 0000000..7655d51 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/CarryCapacityUserParam.java @@ -0,0 +1,103 @@ +package com.njcn.product.carrycapacity.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.web.constant.ValidMessage; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.util.List; + +/** + * + * Description: + * Date: 2024/2/20 11:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CarryCapacityUserParam { + + + /** + * 用户名称 + */ + private String userName; + + /** + * 用户类型 + */ + private String userType; + + /** + * 电压等级(V) + */ + private String voltage; + + /** + * 用户协议容量(MVA) + */ + private Double protocolCapacity; + /** + * 省 + */ + private String province; + + /** + * 市 + */ + private String city; + + /** + * 区 + */ + private String region; + + /** + * 所属区域 + */ + private String area; + /** + * 更新操作实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class CarryCapacityUserUpdateParam extends CarryCapacityUserParam { + @ApiModelProperty("用户Id") + @NotBlank(message = "用户Id不能为空") + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR) + private String userId; + } + @Data + @EqualsAndHashCode(callSuper = true) + public static class CarryCapacityUserPageParam extends CarryCapacityUserParam { + + private String userId; + @NotNull(message="当前页不能为空!") + @Min(value = 1, message = "当前页不能为0") + @ApiModelProperty(value = "当前页",name = "pageNum",dataType ="Integer",required = true) + private Integer pageNum; + /**显示条数*/ + @NotNull(message="显示条数不能为空!") + @ApiModelProperty(value = "显示条数",name = "pageSize",dataType ="Integer",required = true) + private Integer pageSize; + private String voltage; + @ApiModelProperty(value="起始时间") + private String startTime; + + @ApiModelProperty(value="结束时间") + private String endTime; + + private List userTypeList; + } + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/ExcelDataParam.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/ExcelDataParam.java new file mode 100644 index 0000000..5836faa --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/param/ExcelDataParam.java @@ -0,0 +1,29 @@ +package com.njcn.product.carrycapacity.pojo.param; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.constraints.NotBlank; + +/** + * Description: + * Date: 2024/3/6 17:30【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class ExcelDataParam { + @NotBlank(message = "监测点索引为空") + @ApiModelProperty("监测点索引") + private String lineId; + + @ApiModelProperty("开始时间") + private String startTime; + @ApiModelProperty("结束时间") + private String endTime; + + @ApiModelProperty(value = "excel文件") + private MultipartFile file; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityDataPO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityDataPO.java new file mode 100644 index 0000000..d133715 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityDataPO.java @@ -0,0 +1,51 @@ +package com.njcn.product.carrycapacity.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.github.jeffreyning.mybatisplus.anno.MppMultiId; +import com.njcn.db.bo.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +/** + * + * Description: + * Date: 2024/3/6 14:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "carry_capacity_data") +public class CarryCapacityDataPO extends BaseEntity{ + /** + * 台区id + */ + @MppMultiId(value = "line_id") + private String lineId; + + /** + * 开始时间 + */ + @MppMultiId(value = "start_time") + private LocalDate startTime; + + /** + * 结束时间 + */ + @MppMultiId(value = "end_time") + private LocalDate endTime; + + /** + * 上传数据集地址 + */ + @TableField(value = "date_list") + private String dateList; + + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityDevicePO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityDevicePO.java new file mode 100644 index 0000000..4378b46 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityDevicePO.java @@ -0,0 +1,50 @@ +package com.njcn.product.carrycapacity.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2024/3/19 16:36【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +@TableName(value = "carry_capacity_device") +public class CarryCapacityDevicePO extends BaseEntity { + /** + * 设备id + */ + @TableId(value = "dev_id", type = IdType.ASSIGN_UUID) + private String devId; + + @TableField(value = "user_id") + private String userId; + + /** + * 设备名称 + */ + @TableField(value = "dev_name") + private String devName; + /** + * 设备额定电压 + */ + @TableField(value = "dev_scale") + private String devScale; + /** + * 设备用户协议容量(MVA) + */ + @TableField(value = "protocol_capacity") + private Double protocolCapacity; + + + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityResultPO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityResultPO.java new file mode 100644 index 0000000..0a2c598 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityResultPO.java @@ -0,0 +1,129 @@ +package com.njcn.product.carrycapacity.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +/** + * Description: + * Date: 2024/3/8 16:23【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "carry_capacity_result") +public class CarryCapacityResultPO extends BaseEntity { + /** + * 承载能力评估id + */ + @TableId(value = "id",type = IdType.ASSIGN_UUID) + private String id; + + /** + * 台区id + */ + @TableField(value = "line_id") + private String lineId; + + /** + * 用户id + */ + @TableField(value = "user_id") + private String userId; + + /** + * 开始时间 + */ + @TableField(value = "start_time") + private LocalDate startTime; + + /** + * 结束时间 + */ + @TableField(value = "end_time") + private LocalDate endTime; + + /** + * 配变首端电压等级(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + @TableField(value = "u_t_level") + private Integer uTLevel; + + /** + * 配变的功率因等级(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + @TableField(value = "pf_t_level") + private Integer pfTLevel; + + /** + * 等效负载率最小值等级(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + @TableField(value = "b_t_level") + private Integer bTLevel; + + /** + * 各次谐波电流幅值等级 (1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + @TableField(value = "i_level") + private Integer iLevel; + + /** + * 总结果等级(1-安全,2-III级预警,3-II级预警,4-I 级预警,5-禁止接入,6-允许接入) + */ + @TableField(value = "reslut_level") + private Integer reslutLevel; + + /** + * 评估日期 + */ + @TableField(value = "evaluate_date") + private LocalDate evaluateDate; + + /** + * 是否删除(0,无效,1有效) + */ + @TableField(value = "status") + private Integer status; + + @TableField(value = "evaluate_type") + private String evaluateType; + + + @TableField(value = "first_result") + private double firstResult; + + @TableField(value = "i_result_list") + private String iResultList; + + @TableField(value = "pt_type") + private String ptType; + @TableField(value = "connection_mode") + private String connectionMode; + @TableField(value = "k") + private Double k; + + @TableField(value = "user_mode") + private String userMode; + + + @TableField(value = "scale") + private String scale; + + @TableField(value = "short_capacity") + private Float shortCapacity; + + + @TableField(value = "device_capacity") + private Float deviceCapacity; + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityStrategyDhlPO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityStrategyDhlPO.java new file mode 100644 index 0000000..cec83a0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityStrategyDhlPO.java @@ -0,0 +1,98 @@ +package com.njcn.product.carrycapacity.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2024/3/15 10:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "carry_capacity_strategy_dhl") +public class CarryCapacityStrategyDhlPO extends BaseEntity { + /** + * id + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private String id; + + /** + * 充电桩,电弧炉,电气化铁路 + */ + @TableField(value = "type") + private String type; + + /** + * 一级评估比较符 + */ + @TableField(value = "comparison_operators_1") + private String comparisonOperators1; + + /** + * 一级评估数量 + */ + @TableField(value = "count_1") + private Integer count1; + + /** + * 二级级评估(2~20次谐波合格个数)比较符 + */ + @TableField(value = "comparison_operators_2") + private String comparisonOperators2; + + /** + * 二级评估(2~20次谐波合格)个数数量 + */ + @TableField(value = "count_2") + private Integer count2; + + /** + * 二级级评估(奇数谐波合格个数)比较符 + */ + @TableField(value = "comparison_operators_3") + private String comparisonOperators3; + + /** + * 二级评估(奇数次谐波合格)个数数量 + */ + @TableField(value = "count_3") + private Integer count3; + + /** + * 初始配置1,客户配置2 + */ + @TableField(value = "proto_flag") + private Integer protoFlag; + + /** + * 二级级评估(偶数谐波合格个数)比较符 + */ + @TableField(value = "comparison_operators_4") + private String comparisonOperators4; + + /** + * 二级评估(偶数次谐波合格)个数数量 + */ + @TableField(value = "count_4") + private Integer count4; + + /** + * 启用配置1,不启用配置2 + */ + @TableField(value = "user_flag") + private Integer userFlag; + + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityStrategyPO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityStrategyPO.java new file mode 100644 index 0000000..02ac55e --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityStrategyPO.java @@ -0,0 +1,61 @@ +package com.njcn.product.carrycapacity.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Description: + * Date: 2024/3/5 10:54【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "carry_capacity_strategy") +public class CarryCapacityStrategyPO extends BaseEntity { + /** + * 总承载能力评估结果(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + @TableField(value = "result") + private Integer result; + + /** + * 指标评估结果(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + @TableField(value = "index_result") + private Integer indexResult; + + /** + * 比较符 + */ + @TableField(value = "comparison_operators") + private String comparisonOperators; + + /** + * 数量 + */ + @TableField(value = "count") + private Integer count; + + /** + * 初始配置1,客户配置2 + */ + @TableField(value = "proto_flag") + private Integer protoFlag; + + /** + * 启用配置1,不启用配置2 + */ + @TableField(value = "user_flag") + private Integer userFlag; + + @TableField(value = "id") + private String id; + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityUserPO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityUserPO.java new file mode 100644 index 0000000..fc52b76 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/po/CarryCapacityUserPO.java @@ -0,0 +1,83 @@ +package com.njcn.product.carrycapacity.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2024/2/20 11:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "carry_capacity_user") +public class CarryCapacityUserPO extends BaseEntity { + /** + * 用户id + */ + @TableId(value = "user_id", type = IdType.ASSIGN_UUID) + private String userId; + + /** + * 用户名称 + */ + @TableField(value = "user_name") + private String userName; + + /** + * 用户类型 + */ + @TableField(value = "user_type") + private String userType; + + /** + * 电压等级(V) + */ + @TableField(value = "voltage") + private String voltage; + + /** + * 用户协议容量(MVA) + */ + @TableField(value = "protocol_capacity") + private Double protocolCapacity; + + /** + * 省 + */ + @TableField(value = "province") + private String province; + + /** + * 市 + */ + @TableField(value = "city") + private String city; + + /** + * 区 + */ + @TableField(value = "region") + private String region; + + /** + * 所属区域 + */ + @TableField(value = "area") + private String area; + + + @TableField(value = "status") + private Integer status; + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDResultVO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDResultVO.java new file mode 100644 index 0000000..39f0860 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDResultVO.java @@ -0,0 +1,107 @@ +package com.njcn.product.carrycapacity.pojo.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +/** + * Description: + * Date: 2024/2/27 11:24【需求编号】 + * + * @author clam + * @version V1.0.0 + */ + +@Data +public class CarryCapacityDResultVO { + + + private String id; + + /** + * 台区id + */ + private String lineId; + + private String lineName; + + /** + * 用户id + */ + private String userId; + + private String userName; + + /** + * 开始时间 + */ + private LocalDate startTime; + + /** + * 结束时间 + */ + private LocalDate endTime; + + + + private Integer uTLevel; + + /** + * 配变的功率因等级(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + private Integer pfTLevel; + + /** + * 等效负载率最小值等级(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + private Integer bTLevel; + + /** + * 各次谐波电流幅值等级 (1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + private Integer iLevel; + + /** + * 总结果等级(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + private Integer reslutLevel; + + private LocalDate evaluateDate; + + + private String evaluateType; + //电弧炉等评估结果 + + private double firstResult; + + private List iResultList; + + + private String connectionMode; + private Double k; + private String ptType; + + private String userMode; + + + private String scale; + + private Float shortCapacity; + + + private Float deviceCapacity; + + + @Data + public static class CarryCapacityIResult { + + @ApiModelProperty("谐波次数") + private Integer time=2; + + private Double i; + + private Double i_limit; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataIVO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataIVO.java new file mode 100644 index 0000000..7df9d6d --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataIVO.java @@ -0,0 +1,25 @@ +package com.njcn.product.carrycapacity.pojo.vo; + +import com.njcn.influx.pojo.bo.CarryCapcityData; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * Description: + * Date: 2024/2/27 11:24【需求编号】 + * + * @author clam + * @version V1.0.0 + */ + +@Data +public class CarryCapacityDataIVO { + @ApiModelProperty(name = "data",value = "谐波幅值数据") + private List data; + + @ApiModelProperty(name = "I_βmax",value = "2-25次谐波幅值最大95概率值A,B,C三项中的最大值") + private List I_βmax; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataQVO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataQVO.java new file mode 100644 index 0000000..424106c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataQVO.java @@ -0,0 +1,26 @@ +package com.njcn.product.carrycapacity.pojo.vo; + +import com.njcn.influx.pojo.bo.CarryCapcityData; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * Description: + * Date: 2024/2/27 11:24【需求编号】 + * + * @author clam + * @version V1.0.0 + */ + +@Data +public class CarryCapacityDataQVO { + @ApiModelProperty(name = "data",value = "有功功率数据") + private List data; + + @ApiModelProperty(name = "Q_βminMap",value = "无功功率最小CP95值A,B,C三项") + private Map Q_βminMap; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataVO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataVO.java new file mode 100644 index 0000000..cbe6f9c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityDataVO.java @@ -0,0 +1,37 @@ +package com.njcn.product.carrycapacity.pojo.vo; + +import com.njcn.influx.pojo.bo.CarryCapcityData; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * Description: + * Date: 2024/2/27 11:24【需求编号】 + * + * @author clam + * @version V1.0.0 + */ + +@Data +public class CarryCapacityDataVO { + @ApiModelProperty(name = "data",value = "有功功率数据") + private List data; + @ApiModelProperty(name = "stringMap",value = "首端电流模型参数A,B,C三项") + private Map stringMap; + + @ApiModelProperty(name = "P_βminMap",value = "有功功率最小CP95值A,B,C三项") + private Map P_βminMap; + @ApiModelProperty(name = "scale",value = "电压等级") + private String scale; + @ApiModelProperty(name = "devCapacity",value = "基准容量/额定容量(MVA)") + private Double standardCapacity; + + /** + * 用户协议容量(MVA) + */ + @ApiModelProperty(name = "dealCapacity",value = "用户协议容量(MVA)") + private Double protocolCapacity; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityStrategyDhlVO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityStrategyDhlVO.java new file mode 100644 index 0000000..8a1313f --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityStrategyDhlVO.java @@ -0,0 +1,76 @@ +package com.njcn.product.carrycapacity.pojo.vo; + +import lombok.Data; + +/** + * + * Description: + * Date: 2024/3/15 10:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class CarryCapacityStrategyDhlVO { + /** + * id + */ + private String id; + + /** + * 充电桩,电弧炉,电气化铁路 + */ + private String type; + + /** + * 一级评估比较符 + */ + private String comparisonOperators1; + + /** + * 一级评估数量 + */ + private Integer count1; + + /** + * 二级级评估(2~20次谐波合格个数)比较符 + */ + private String comparisonOperators2; + + /** + * 二级评估(2~20次谐波合格)个数数量 + */ + private Integer count2; + + /** + * 二级级评估(奇数谐波合格个数)比较符 + */ + private String comparisonOperators3; + + /** + * 二级评估(奇数次谐波合格)个数数量 + */ + private Integer count3; + + /** + * 初始配置1,客户配置2 + */ + private Integer protoFlag; + + /** + * 二级级评估(偶数谐波合格个数)比较符 + */ + private String comparisonOperators4; + + /** + * 二级评估(偶数次谐波合格)个数数量 + */ + private Integer count4; + + /** + * 启用配置1,不启用配置2 + */ + private Integer userFlag; + + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityStrategyVO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityStrategyVO.java new file mode 100644 index 0000000..25feaa2 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityStrategyVO.java @@ -0,0 +1,59 @@ +package com.njcn.product.carrycapacity.pojo.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * Description: + * Date: 2024/3/5 10:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class CarryCapacityStrategyVO { + + /** + * 总承载能力评估结果(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + + @ApiModelProperty(value = "总承载能力评估结果(1-安全,2-III级预警,3-II级预警,4-I 级预警)") + private Integer result; + + + + private List capacityStrategysingleVOList; + @Data + public static class CarryCapacityStrategysingleVO { + + private String id; + private List carryCapacityStrategyIndexVOList; + @Data + public static class CarryCapacityStrategyIndexVO { + + /** + * 指标评估结果(1-安全,2-III级预警,3-II级预警,4-I 级预警) + */ + @ApiModelProperty(value = "指标评估结果(1-安全,2-III级预警,3-II级预警,4-I 级预警)") + private Integer indexResult; + + /** + * 比较符 + */ + @ApiModelProperty(value = "比较符") + private String comparisonOperators; + + /** + * 数量 + */ + @ApiModelProperty(value = "数量") + private Integer count; + + } + + + + } +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityUserVO.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityUserVO.java new file mode 100644 index 0000000..e309c19 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/pojo/vo/CarryCapacityUserVO.java @@ -0,0 +1,53 @@ +package com.njcn.product.carrycapacity.pojo.vo; + +import com.njcn.db.bo.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2024/2/20 11:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CarryCapacityUserVO extends BaseEntity { + /** + * 用户id + */ + private String userId; + + /** + * 用户名称 + */ + private String userName; + + /** + * 用户类型 + */ + private String userType; + + /** + * 电压等级(V) + */ + private String voltage; + + /** + * 用户协议容量(MVA) + */ + private Double protocolCapacity; + + + + /** + * 所属区域 + */ + private String area; + + +} \ No newline at end of file diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityDataPOService.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityDataPOService.java new file mode 100644 index 0000000..cd7d879 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityDataPOService.java @@ -0,0 +1,17 @@ +package com.njcn.product.carrycapacity.service; + +import com.github.jeffreyning.mybatisplus.service.IMppService; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityDataPO; + +/** + * + * Description: + * Date: 2024/3/6 14:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityDataPOService extends IMppService { + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityDevicePOService.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityDevicePOService.java new file mode 100644 index 0000000..fb15c4d --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityDevicePOService.java @@ -0,0 +1,21 @@ +package com.njcn.product.carrycapacity.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityDeviceParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityDevicePO; + +/** + * + * Description: + * Date: 2024/3/19 16:36【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityDevicePOService extends IService{ + + + Boolean updateDevice(CarryCapacityDeviceParam.CarryCapacityDeviceUpdateParam deviceParam); + + Boolean add(CarryCapacityDeviceParam capacityDeviceParam); + } diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityResultPOService.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityResultPOService.java new file mode 100644 index 0000000..b0f2b95 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityResultPOService.java @@ -0,0 +1,25 @@ +package com.njcn.product.carrycapacity.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityQueryDataParam; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityResultParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityResultPO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDResultVO; + + +/** + * + * Description: + * Date: 2024/3/1 15:38【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityResultPOService extends IService{ + + + IPage queryResultList(CarryCapacityResultParam.CarryCapacityResultPageParam queryParam); + + CarryCapacityDResultVO queryResultbyCondition(CarryCapacityQueryDataParam queryParam); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityService.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityService.java new file mode 100644 index 0000000..954ec19 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityService.java @@ -0,0 +1,39 @@ +package com.njcn.product.carrycapacity.service; + + + +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityCalParam; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityEvaluateParam; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityQueryDataParam; +import com.njcn.product.carrycapacity.pojo.param.ExcelDataParam; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDResultVO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDataIVO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDataQVO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDataVO; + +import java.util.List; + +/** + * Description: + * Date: 2024/1/31 14:40【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityService { + + + CarryCapacityDataVO queryCarryCapacityData(CarryCapacityQueryDataParam queryParam); + + CarryCapacityDataQVO queryCarryCapacityqData(CarryCapacityQueryDataParam queryParam); + + CarryCapacityDataIVO queryCarryCapacityiData(CarryCapacityQueryDataParam queryParam); + + CarryCapacityDResultVO carryCapacityCal(CarryCapacityCalParam calParam); + + + boolean uploadExcel(ExcelDataParam excelDataParam) throws Exception; + + + CarryCapacityDResultVO carryCapacityEvaluate(CarryCapacityEvaluateParam calParam); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityStrategyDhlPOService.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityStrategyDhlPOService.java new file mode 100644 index 0000000..27ab0ab --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityStrategyDhlPOService.java @@ -0,0 +1,24 @@ +package com.njcn.product.carrycapacity.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityStrategyDhlPO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityStrategyDhlVO; + + +import java.util.List; + +/** + * + * Description: + * Date: 2024/3/15 10:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityStrategyDhlPOService extends IService{ + + + List queyDetailDhl(); + + Boolean adddhl(CarryCapacityStrategyDhlVO capacityStrategyDhlVO); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityStrategyPOService.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityStrategyPOService.java new file mode 100644 index 0000000..7201e0b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityStrategyPOService.java @@ -0,0 +1,29 @@ +package com.njcn.product.carrycapacity.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityStrategyParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityStrategyPO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityStrategyVO; + + +import java.util.List; + +/** + * + * Description: + * Date: 2024/3/5 10:33【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityStrategyPOService extends IService{ + + + Boolean add(CarryCapacityStrategyParam carryCapacityStrategyParam); + + List queyDetail(); + + Boolean restore(); + + Boolean addList(List carryCapacityStrategyParamList); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityUserPOService.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityUserPOService.java new file mode 100644 index 0000000..e75588f --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/CarryCapacityUserPOService.java @@ -0,0 +1,25 @@ +package com.njcn.product.carrycapacity.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityUserParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityUserPO; +/** + * + * Description: + * Date: 2024/2/20 11:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CarryCapacityUserPOService extends IService{ + + + Boolean add(CarryCapacityUserParam carryCapacityUserParam); + + Boolean updateUser(CarryCapacityUserParam.CarryCapacityUserUpdateParam userUpdateParam); + + IPage queyDetailUser(CarryCapacityUserParam.CarryCapacityUserPageParam pageParm); + + CarryCapacityUserPO queyDetailUserById(String userId); + } diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityDataPOServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityDataPOServiceImpl.java new file mode 100644 index 0000000..fdfb861 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityDataPOServiceImpl.java @@ -0,0 +1,20 @@ +package com.njcn.product.carrycapacity.service.impl; + +import com.github.jeffreyning.mybatisplus.service.MppServiceImpl; + +import com.njcn.product.carrycapacity.mapper.CarryCapacityDataPOMapper; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityDataPO; +import com.njcn.product.carrycapacity.service.CarryCapacityDataPOService; +import org.springframework.stereotype.Service; +/** + * + * Description: + * Date: 2024/3/6 14:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class CarryCapacityDataPOServiceImpl extends MppServiceImpl implements CarryCapacityDataPOService { + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityDevicePOServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityDevicePOServiceImpl.java new file mode 100644 index 0000000..66440e0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityDevicePOServiceImpl.java @@ -0,0 +1,85 @@ +package com.njcn.product.carrycapacity.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.carrycapacity.mapper.CarryCapacityDevicePOMapper; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityDeviceParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityDevicePO; +import com.njcn.product.carrycapacity.service.CarryCapacityDevicePOService; +import com.njcn.product.carrycapacity.util.CheckStringUtil; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * + * Description: + * Date: 2024/3/19 16:36【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class CarryCapacityDevicePOServiceImpl extends ServiceImpl implements CarryCapacityDevicePOService { + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updateDevice(CarryCapacityDeviceParam.CarryCapacityDeviceUpdateParam deviceParam) { + if(StringUtils.isBlank(deviceParam.getDevName())){ + throw new BusinessException("干扰源设备名称不能为空"); + } + checkName(deviceParam,true); + return this.lambdaUpdate().eq(CarryCapacityDevicePO::getDevId, deviceParam.getDevId()) + .set(CarryCapacityDevicePO::getDevName, deviceParam.getDevName()) + .set(CarryCapacityDevicePO::getDevScale, deviceParam.getDevScale()) + .set(CarryCapacityDevicePO::getProtocolCapacity, deviceParam.getProtocolCapacity()) + .update(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean add(CarryCapacityDeviceParam capacityDeviceParam) { + if(StringUtils.isBlank(capacityDeviceParam.getDevName())){ + throw new BusinessException("干扰源设备名称不能为空"); + } + checkName(capacityDeviceParam,false); + + CarryCapacityDevicePO carryCapacityDevice = new CarryCapacityDevicePO(); + BeanUtils.copyProperties(capacityDeviceParam,carryCapacityDevice); + return this.save(carryCapacityDevice); + } + + /** + * 检查名称是否已存在 + * + * @return 结果 + */ + private void checkName(CarryCapacityDeviceParam carryCapacityDeviceParam, boolean isUpdate) { + if(carryCapacityDeviceParam.getDevName().length()>32){ + throw new BusinessException("超过最大长度"); + + } + CheckStringUtil.checkName(carryCapacityDeviceParam.getDevName()); + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + //条件组合:where state = 1 and name = ? + lambdaQueryWrapper.eq(CarryCapacityDevicePO::getUserId,carryCapacityDeviceParam.getUserId()).eq(CarryCapacityDevicePO::getDevName, carryCapacityDeviceParam.getDevName()); + + //and id <> ? + if (isUpdate) { + if (carryCapacityDeviceParam instanceof CarryCapacityDeviceParam.CarryCapacityDeviceUpdateParam) { + lambdaQueryWrapper.ne(CarryCapacityDevicePO::getDevId, ((CarryCapacityDeviceParam.CarryCapacityDeviceUpdateParam) carryCapacityDeviceParam).getDevId()); + } + } + + //若存在条件数据抛出异常 + int count = this.getBaseMapper().selectCount(lambdaQueryWrapper); + if (count > 0) { + throw new BusinessException("干扰源设备名称已存在"); + } + + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityResultPOServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityResultPOServiceImpl.java new file mode 100644 index 0000000..f0c966e --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityResultPOServiceImpl.java @@ -0,0 +1,105 @@ +package com.njcn.product.carrycapacity.service.impl; + +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.product.carrycapacity.mapper.CarryCapacityResultPOMapper; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityQueryDataParam; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityResultParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityResultPO; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityUserPO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityDResultVO; +import com.njcn.product.carrycapacity.service.CarryCapacityResultPOService; +import com.njcn.product.carrycapacity.service.CarryCapacityUserPOService; +import com.njcn.product.device.ledger.mapper.LineMapper; +import com.njcn.product.device.ledger.pojo.vo.LineDetailVO; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * + * Description: + * Date: 2024/3/1 15:38【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +@RequiredArgsConstructor +public class CarryCapacityResultPOServiceImpl extends ServiceImpl implements CarryCapacityResultPOService { + private final CarryCapacityUserPOService carryCapacityUserPOService; + private final LineMapper lineMapper; + @Override + public IPage queryResultList(CarryCapacityResultParam.CarryCapacityResultPageParam queryParam) { + Page returnpage = new Page<> (queryParam.getPageNum ( ), queryParam.getPageSize ( )); + Page temppage = new Page<> (queryParam.getPageNum ( ), queryParam.getPageSize ( )); + + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(StringUtils.isNotBlank(queryParam.getEvaluateType()) ,CarryCapacityResultPO::getEvaluateType,queryParam.getEvaluateType()) + .between(StringUtils.isNotBlank(queryParam.getStartTime()) && StringUtils.isNotBlank(queryParam.getEndTime()) ,CarryCapacityResultPO::getEvaluateDate,queryParam.getStartTime()+" 00:00:00",queryParam.getEndTime()+" 23:59:59") + .eq(CarryCapacityResultPO::getStatus,1) + .orderByDesc(CarryCapacityResultPO::getEvaluateDate); + + IPage page = this.page(temppage, queryWrapper); + List collect = page.getRecords().stream().map(temp -> { + CarryCapacityDResultVO vo = new CarryCapacityDResultVO(); + BeanUtils.copyProperties(temp, vo); + String[] split = temp.getUserId().split(","); + List collect1 = Arrays.stream(split).sequential().map(userId -> { + CarryCapacityUserPO carryCapacityUser = carryCapacityUserPOService.queyDetailUserById(userId); + return carryCapacityUser.getUserName(); + }).collect(Collectors.toList()); + vo.setUserName(String.join(",", collect1)); + if (ObjectUtils.isNotEmpty(temp.getIResultList()) ){ + String iResultList = temp.getIResultList(); + List list = JSONUtil.toList(JSONUtil.toJsonStr(iResultList), CarryCapacityDResultVO.CarryCapacityIResult.class); + vo.setIResultList(list); + } + if(StringUtils.isNotBlank(vo.getLineId())){ + LineDetailVO data = lineMapper.getLineSubGdDetail(vo.getLineId()); + if(Objects.nonNull(data)){ + vo.setLineName(data.getGdName()+"->" + +data.getSubName()+"->" + +data.getDevName()+"->" + +data.getLineName()); + } + + } + + return vo; + }).collect(Collectors.toList()); + returnpage.setRecords(collect); + returnpage.setTotal(page.getTotal()); + + return returnpage; + } + + @Override + public CarryCapacityDResultVO queryResultbyCondition(CarryCapacityQueryDataParam queryParam) { + CarryCapacityDResultVO vo = new CarryCapacityDResultVO(); + + CarryCapacityResultPO one = this.lambdaQuery().eq(CarryCapacityResultPO::getLineId, queryParam.getLineId()) + .eq(CarryCapacityResultPO::getStartTime, queryParam.getStartTime()) + .eq(CarryCapacityResultPO::getEndTime, queryParam.getEndTime()) + .eq(CarryCapacityResultPO::getUserId, queryParam.getUserId()) + .eq(CarryCapacityResultPO::getStatus, 1).one(); + if(Objects.nonNull(one)){ + BeanUtils.copyProperties(one, vo); + } + return vo; + + + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityServiceImpl.java new file mode 100644 index 0000000..bf8a80f --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityServiceImpl.java @@ -0,0 +1,1272 @@ +package com.njcn.product.carrycapacity.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.json.JSONUtil; + +import com.njcn.common.pojo.exception.BusinessException; + +import com.njcn.influx.constant.InfluxDbSqlConstant; +import com.njcn.influx.imapper.DataHarmPowerPMapper; +import com.njcn.influx.imapper.DataHarmPowerQMapper; +import com.njcn.influx.imapper.DataIMapper; +import com.njcn.influx.imapper.DataVMapper; +import com.njcn.influx.pojo.bo.CarryCapcityData; +import com.njcn.influx.pojo.constant.InfluxDBTableConstant; +import com.njcn.influx.pojo.po.DataI; +import com.njcn.product.carrycapacity.enums.CarryCapacityResponseEnum; +import com.njcn.product.carrycapacity.enums.CarryingCapacityEnum; +import com.njcn.product.carrycapacity.pojo.excel.*; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityCalParam; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityEvaluateParam; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityQueryDataParam; +import com.njcn.product.carrycapacity.pojo.param.ExcelDataParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityDataPO; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityDevicePO; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityResultPO; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityUserPO; +import com.njcn.product.carrycapacity.pojo.vo.*; +import com.njcn.product.carrycapacity.service.*; +import com.njcn.product.carrycapacity.util.*; +import com.njcn.product.device.ledger.pojo.vo.LineDetailDataVO; +import com.njcn.product.device.ledger.service.LineService; +import com.njcn.product.device.overlimit.pojo.Overlimit; +import com.njcn.product.device.overlimit.util.COverlimitUtil; +import com.njcn.product.system.dict.pojo.po.DictData; +import com.njcn.product.system.dict.service.IDictDataService; +import com.njcn.product.system.dict.enums.DicDataEnum; +import com.njcn.redis.utils.RedisUtil; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * Description: + * Date: 2024/1/31 14:42【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +@RequiredArgsConstructor +public class CarryCapacityServiceImpl implements CarryCapacityService { + + private final LineService lineService; + + private final DataHarmPowerQMapper dataHarmPowerqMapper; + + private final DataHarmPowerPMapper dataHarmPowerpMapper; + private final DataVMapper datavMapper; + + private final DataIMapper dataiMapper; + + private final CarryCapacityStrategyPOService carryCapacityStrategyPOService; + private final CarryCapacityDataPOService carryCapacityDataPOService; + private final RedisUtil redisUtil; + private final CarryCapacityResultPOService carryCapacityResultPOService; + private final CarryCapacityUserPOService carryCapacityUserPOService; + private static final double DEFAULTVALUE = 3.1415926; + + private final FileUtils fileUtils; + + private final IDictDataService iDictDataService; + + + @Override + public CarryCapacityDataVO queryCarryCapacityData(CarryCapacityQueryDataParam queryParam) { + + //前一周数据 + List dataHarmPowerpList; + List dataHarmPowerqList; + //前2周的数据 + List dataHarmPowerP2List; + List dataHarmPowerQ2List; + List dataHarmPowerU2List; + + CarryCapacityDataVO carryCapacityDataVO = new CarryCapacityDataVO(); + String lineId = queryParam.getLineId(); + LineDetailDataVO data = lineService.getLineDetailData(lineId); +// //时间间隔 + Integer timeInterval = data.getTimeInterval(); + + LocalDate startDate = queryParam.getStartTime(); + LocalDate endDate = queryParam.getEndTime(); + //前2周的时间 + LocalDate startDate2 = startDate.plusWeeks(-1); + LocalDate endDate2 = endDate.plusWeeks(-1); + + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + String startTime = LocalDateTimeUtil.format(queryParam.getStartTime(), formatter) + " 00:00:00"; + String endTime = LocalDateTimeUtil.format(queryParam.getEndTime(), formatter) + " 23:59:00"; + + //先重redis读取数据,无数据,查看是否存在文件,不存在文件查数据库,数据校验补通过上传文件 + dataHarmPowerpList = (List) redisUtil.getObjectByKey(lineId + "#" + LocalDateTimeUtil.format(startDate, formatter) + "#" + LocalDateTimeUtil.format(endDate, formatter) + "#" + "P"); + dataHarmPowerP2List = (List) redisUtil.getObjectByKey(lineId + "#" + LocalDateTimeUtil.format(startDate2, formatter) + "#" + LocalDateTimeUtil.format(endDate2, formatter) + "#" + "P"); + dataHarmPowerQ2List = (List) redisUtil.getObjectByKey(lineId + "#" + LocalDateTimeUtil.format(startDate2, formatter) + "#" + LocalDateTimeUtil.format(endDate2, formatter) + "#" + "Q"); + dataHarmPowerU2List = (List) redisUtil.getObjectByKey(lineId + "#" + LocalDateTimeUtil.format(startDate2, formatter) + "#" + LocalDateTimeUtil.format(endDate2, formatter) + "#" + "U"); + + if (CollectionUtil.isEmpty(dataHarmPowerpList) || + CollectionUtil.isEmpty(dataHarmPowerP2List) || + CollectionUtil.isEmpty(dataHarmPowerQ2List) || + CollectionUtil.isEmpty(dataHarmPowerU2List)) { + CarryCapacityDataPO one = carryCapacityDataPOService.lambdaQuery().eq(CarryCapacityDataPO::getLineId, lineId) + .eq(CarryCapacityDataPO::getStartTime, queryParam.getStartTime()) + .eq(CarryCapacityDataPO::getEndTime, queryParam.getEndTime()).one(); + if (Objects.nonNull(one)) { + //todo 调用查询文件 + InputStream fileStream = null; + try { + fileStream = fileUtils.getFileStream(one.getDateList()); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + ExcelDataDTO excelDataDTO = parsingFile(one.getStartTime(), one.getEndTime(), fileStream); + dataHarmPowerpList = excelDataDTO.getDataHarmPowerPList(); + dataHarmPowerP2List = excelDataDTO.getDataHarmPowerP2List(); + dataHarmPowerQ2List = excelDataDTO.getDataHarmPowerQ2List(); + dataHarmPowerU2List = excelDataDTO.getDataHarmPowerU2List(); + + + } else { + /* 近一周的数据包括电流,电压,有功功率,无功功率,数据完整性校验就取有功功率一组数据校验,因为,要有都有要没有都没有,数据查询按时间间隔和tag分组, + 缺失布置3.1415926,后边更具3.1415926个数来判断数据完整性,及进行数据补充*/ + //有功功率 + String sqlP1 = "select mean(p)*1000 as value from data_harmpower_p where value_type='CP95' and phasic_type!='T' and line_id='" + lineId + + "' and time >= '" + startTime + "'and time <= '" + endTime + "'" + "group by time(" + timeInterval + "m) ,* fill(0.0031415926)" + InfluxDbSqlConstant.TZ; + dataHarmPowerpList = dataHarmPowerpMapper.getSqlResult(sqlP1); + + //数据校验 + if (CollectionUtil.isEmpty(dataHarmPowerpList)) { + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } else { + + if (checkData(dataHarmPowerpList, startDate, endDate, timeInterval)) { + + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } + } + //数据缺失填补 + linearInterpolation(dataHarmPowerpList); +// redisUtil.saveByKey(lineId+"#"+LocalDateTimeUtil.format(startDate, formatter)+"#"+LocalDateTimeUtil.format(endDate, formatter)+"#"+"P", +// dataHarmPowerPList); + + //无功功率 + String sqlQ1 = "select mean(q)*1000 as value from data_harmpower_q where value_type='CP95' and phasic_type!='T' and line_id='" + lineId + + "' and time >= '" + startTime + "'and time <= '" + endTime + "'" + "group by time(" + timeInterval + "m) ,* fill(0.0031415926)" + InfluxDbSqlConstant.TZ; + dataHarmPowerqList = dataHarmPowerqMapper.getSqlResult(sqlQ1); + //数据缺失填补 + //数据校验 + if (CollectionUtil.isEmpty(dataHarmPowerqList)) { + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } else { + + if (checkData(dataHarmPowerqList, startDate, endDate, timeInterval)) { + + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } + } + + linearInterpolation(dataHarmPowerqList); +// redisUtil.saveByKey(lineId+"#"+LocalDateTimeUtil.format(startDate, formatter)+"#"+LocalDateTimeUtil.format(endDate, formatter)+"#"+"Q", +// dataHarmPowerqList); + + + //前2周的数据用于计算首端电流模型参数 + String forwardStartTime = LocalDateTimeUtil.format(queryParam.getStartTime() + .plusWeeks(-1) + , formatter) + " 00:00:00"; + String forwardEndTime = LocalDateTimeUtil.format(queryParam.getEndTime() + .plusWeeks(-1) + , formatter) + " 23:59:00"; + + String sqlP2 = "select mean(p)*1000 as value from data_harmpower_p where value_type='CP95' and phasic_type!='T' and line_id='" + lineId + + "' and time >= '" + forwardStartTime + "'and time <= '" + forwardEndTime + "'" + "group by time(" + timeInterval + "m) ,* fill(0.0031415926)" + InfluxDbSqlConstant.TZ; + dataHarmPowerP2List = dataHarmPowerpMapper.getSqlResult(sqlP2); + //数据校验 + if (CollectionUtil.isEmpty(dataHarmPowerP2List)) { + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } else { + + if (checkData(dataHarmPowerP2List, startDate2, endDate2, timeInterval)) { + + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } + } + + //数据缺失填补 + linearInterpolation(dataHarmPowerP2List); + + + //无功功率 + String sqlQ2 = "select mean(q)*1000 as value from data_harmpower_q where value_type='CP95' and phasic_type!='T' and line_id='" + lineId + + "' and time >= '" + forwardStartTime + "'and time <= '" + forwardEndTime + "'" + "group by time(" + timeInterval + "m) ,* fill(0.0031415926)" + InfluxDbSqlConstant.TZ; + dataHarmPowerQ2List = dataHarmPowerqMapper.getSqlResult(sqlQ2); + //数据校验 + //数据校验 + if (CollectionUtil.isEmpty(dataHarmPowerQ2List)) { + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } else { + + if (checkData(dataHarmPowerQ2List, startDate2, endDate2, timeInterval)) { + + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } + } + + //数据缺失填补 + linearInterpolation(dataHarmPowerQ2List); + + + //电压 + String sqlU2 = "select mean(rms)*1000 as value from data_v where value_type='CP95' and phasic_type!='T' and line_id='" + lineId + + "' and time >= '" + forwardStartTime + "'and time <= '" + forwardEndTime + "'" + "group by time(" + timeInterval + "m) ,* fill(0.0031415926)" + InfluxDbSqlConstant.TZ; + dataHarmPowerU2List = datavMapper.getSqlResult(sqlU2); + //数据校验 + //数据校验 + if (CollectionUtil.isEmpty(dataHarmPowerU2List)) { + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } else { + + if (checkData(dataHarmPowerU2List, startDate2, endDate2, timeInterval)) { + + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } + } + //数据缺失填补 + linearInterpolation(dataHarmPowerU2List); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate, formatter) + "#" + LocalDateTimeUtil.format(endDate, formatter) + "#" + "P", + dataHarmPowerpList); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate2, formatter) + "#" + LocalDateTimeUtil.format(endDate2, formatter) + "#" + "P", + dataHarmPowerP2List); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate2, formatter) + "#" + LocalDateTimeUtil.format(endDate2, formatter) + "#" + "Q", + dataHarmPowerQ2List); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate2, formatter) + "#" + LocalDateTimeUtil.format(endDate2, formatter) + "#" + "U", + dataHarmPowerU2List); + + + } + + + } +// dataHarmPowerpList = dataHarmPowerpList.stream().map(temp -> { +// temp.setTime(temp.getTime().plusMillis(TimeUnit.HOURS.toMillis(8))); +// return temp; +// }).collect(Collectors.toList()); + carryCapacityDataVO.setData(dataHarmPowerpList); + + + List phaseType = Stream.of("A", "B", "C").collect(Collectors.toList()); + + Map results = new HashMap<>(4); + //计算最小Cp95值用于评估 + List finalDataHarmPowerpList = dataHarmPowerpList; + phaseType.forEach(phase -> { + List listP = finalDataHarmPowerpList.stream().filter(temp -> Utils.isTimeInRange(temp.getTime(), LocalTime.of(9, 0), LocalTime.of(15, 0))) + .filter(temp -> temp.getValue() != DEFAULTVALUE && Objects.nonNull(temp.getValue())) + .filter(temp -> Objects.equals(temp.getPhaseType(), phase)) + .map(CarryCapcityData::getValue) + .collect(Collectors.toList()); + double pMin = CarryCapacityUtil.calculatePercentile(listP, 1); + results.put(phase, pMin); + }); + + carryCapacityDataVO.setP_βminMap(results); + + try { + //用前2周的数据计算C,a,b + Map stringMap = caluParam(dataHarmPowerP2List, dataHarmPowerQ2List, dataHarmPowerU2List); + carryCapacityDataVO.setStringMap(stringMap); + return carryCapacityDataVO; + }catch (Exception e){ + return carryCapacityDataVO; + } + + } + + @Override + public CarryCapacityDataQVO queryCarryCapacityqData(CarryCapacityQueryDataParam queryParam) { + + CarryCapacityDataQVO carryCapacityDataqVO = new CarryCapacityDataQVO(); + String lineId = queryParam.getLineId(); + LineDetailDataVO data = lineService.getLineDetailData(lineId); + //时间间隔 + Integer timeInterval = data.getTimeInterval(); +// Integer timeInterval =10; + + //根据时间间隔算出最低数据量 7天*6小时*60分钟*3项*90%/时间间隔 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate startDate = queryParam.getStartTime(); + LocalDate endDate = queryParam.getEndTime(); + + String startTime = LocalDateTimeUtil.format(queryParam.getStartTime(), formatter) + " 00:00:00"; + String endTime = LocalDateTimeUtil.format(queryParam.getEndTime(), formatter) + " 23:59:00"; + List dataHarmPowerqList = new ArrayList<>(); + dataHarmPowerqList = (List) redisUtil.getObjectByKey(lineId + "#" + LocalDateTimeUtil.format(startDate, formatter) + "#" + LocalDateTimeUtil.format(endDate, formatter) + "#" + "Q"); + if (CollectionUtil.isEmpty(dataHarmPowerqList)) { + //无功功率 + String sqlQ1 = "select mean(q)*1000 as value from data_harmpower_q where value_type='CP95' and phasic_type!='T' and line_id='" + lineId + + "' and time >= '" + startTime + "'and time <= '" + endTime + "'" + "group by time(" + timeInterval + "m) ,* fill(0.0031415926)" + InfluxDbSqlConstant.TZ; + dataHarmPowerqList = dataHarmPowerqMapper.getSqlResult(sqlQ1); + if (CollectionUtil.isEmpty(dataHarmPowerqList)) { + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } else { + + if (checkData(dataHarmPowerqList, startDate, endDate, timeInterval)) { + + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } + } + //数据缺失填补 + linearInterpolation(dataHarmPowerqList); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate, formatter) + "#" + LocalDateTimeUtil.format(endDate, formatter) + "#" + "Q", + dataHarmPowerqList); + } +// dataHarmPowerqList = dataHarmPowerqList.stream().map(temp -> { +// temp.setTime(temp.getTime().plusMillis(TimeUnit.HOURS.toMillis(8))); +// return temp; +// }).collect(Collectors.toList()); + + carryCapacityDataqVO.setData(dataHarmPowerqList); + + + List phaseType = Stream.of("A", "B", "C").collect(Collectors.toList()); + + Map results = new HashMap<>(4); + //计算最小Cp95值用于评估 + List finalDataHarmPowerqList = dataHarmPowerqList; + phaseType.forEach(phase -> { + List listQ = finalDataHarmPowerqList.stream().filter(temp -> Utils.isTimeInRange(temp.getTime(), LocalTime.of(9, 0), LocalTime.of(15, 0))) + .filter(temp -> temp.getValue() != DEFAULTVALUE && Objects.nonNull(temp.getValue())) + .filter(temp -> Objects.equals(temp.getPhaseType(), phase)) + .map(CarryCapcityData::getValue) + .collect(Collectors.toList()); + double qMin = CarryCapacityUtil.calculatePercentile(listQ, 1); + results.put(phase, qMin); + }); + + carryCapacityDataqVO.setQ_βminMap(results); + return carryCapacityDataqVO; + } + + @Override + public CarryCapacityDataIVO queryCarryCapacityiData(CarryCapacityQueryDataParam queryParam) { + CarryCapacityDataIVO carryCapacityDataiVO = new CarryCapacityDataIVO(); + String lineId = queryParam.getLineId(); + LineDetailDataVO data = lineService.getLineDetailData(lineId); + //时间间隔 + Integer timeInterval = data.getTimeInterval(); + LocalDate startDate = queryParam.getStartTime(); + LocalDate endDate = queryParam.getEndTime(); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + String startTime = LocalDateTimeUtil.format(startDate, formatter) + " 00:00:00"; + String endTime = LocalDateTimeUtil.format(endDate, formatter) + " 23:59:00"; + + List dataI = new ArrayList<>(); + dataI = (List) redisUtil.getObjectByKey(lineId + "#" + LocalDateTimeUtil.format(startDate, formatter) + "#" + LocalDateTimeUtil.format(endDate, formatter) + "#" + "I"); + + if (CollectionUtil.isEmpty(dataI)) { + //电流 + StringBuilder stringBuilder1 = new StringBuilder(); + StringBuilder stringBuilder2 = new StringBuilder(); + for (int i = 2; i <= 25; i++) { + if (i == 25) { + stringBuilder1.append("mean(i_").append(i).append(") AS i_").append(i); + } else { + stringBuilder1.append("mean(i_").append(i).append(") AS i_").append(i).append(","); + } + } + stringBuilder2.append("line_id='").append(lineId).append("' and ").append(InfluxDbSqlConstant.TIME + " >= '").append(startTime).append("' and ").append(InfluxDbSqlConstant.TIME).append(" <= '").append(endTime).append("' group by time(").append(timeInterval).append("m),* fill(3.1415926) ").append(InfluxDbSqlConstant.TZ); + String sqlI1 = "select " + stringBuilder1 + " from " + InfluxDBTableConstant.DATA_I + " where value_type='CP95' and phasic_type!='T' and " + stringBuilder2; + dataI = dataiMapper.getSqlResult(sqlI1); + //此处查询influxdb少8个小时 + dataI = dataI.stream().map(temp -> { + temp.setTime(temp.getTime().plusMillis(TimeUnit.HOURS.toMillis(8))); + return temp; + }).collect(Collectors.toList()); + if (CollectionUtil.isEmpty(dataI)) { + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } else { + List iList = dataI.stream().map(temp -> { + CarryCapcityData carryCapcityData = new CarryCapcityData(); + BeanUtils.copyProperties(temp, carryCapcityData); + carryCapcityData.setValue(temp.getI2()); + return carryCapcityData; + }).collect(Collectors.toList()); + if (checkData(iList, startDate, endDate, timeInterval)) { + + throw new BusinessException(CarryCapacityResponseEnum.DATA_NOT_FOUND); + } + } + //数据缺失填补 + linearInterpolationI(dataI); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate, formatter) + "#" + LocalDateTimeUtil.format(endDate, formatter) + "#" + "I", + dataI); + + } + + List iList = dataI.stream().map(temp -> { + CarryCapcityData carryCapcityData = new CarryCapcityData(); + BeanUtils.copyProperties(temp, carryCapcityData); + carryCapcityData.setValue(Utils.getAttributeValueByPropertyName(temp, "i" + queryParam.getTime())); + return carryCapcityData; + }).collect(Collectors.toList()); + carryCapacityDataiVO.setData(iList); + + List iMaxList = new ArrayList<>(); + List integerList = Stream.of(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25).collect(Collectors.toList()); + List phaseType = Stream.of("A", "B", "C").collect(Collectors.toList()); + List finalDataI = dataI; + integerList.forEach(temp -> { + List tempList = new ArrayList<>(); + phaseType.forEach(phase -> { + + List tempDataiList = finalDataI.stream().filter(temp1 -> Utils.isTimeInRange(temp1.getTime(), LocalTime.of(9, 0), LocalTime.of(15, 0))) + .filter(temp1 -> temp1.getPhaseType().equals(phase)) + .collect(Collectors.toList()); + + List attributeValueByPropertyName = Utils.getAttributeValueByPropertyName(tempDataiList, "i" + temp); + double iCp95 = CarryCapacityUtil.calculatePercentile(attributeValueByPropertyName, 0); + tempList.add(iCp95); + }); + //取uList最大值 + double iMax = tempList.stream().mapToDouble(Double::doubleValue) + .max() + .getAsDouble(); + iMaxList.add(iMax); + }); + carryCapacityDataiVO.setI_βmax(iMaxList); + + return carryCapacityDataiVO; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public CarryCapacityDResultVO carryCapacityCal(CarryCapacityCalParam calParam) { + CarryCapacityDResultVO carryCapacitydResultVO = new CarryCapacityDResultVO(); + if(CollectionUtil.isEmpty(calParam.getStringMap())){ + throw new BusinessException("数据有误,模型训练失败"); + } + + String scale = calParam.getScale(); + String scaleValue = iDictDataService.getDicDataById(scale).getValue(); + + //todo S_T查询监测点的容量 + Double sT = calParam.getS_T(); + Double sPv = calParam.getS_pv(); + Double pPv = calParam.getS_pv() * Double.valueOf(CarryingCapacityEnum.K.getValue()); + List phaseType = Stream.of("A", "B", "C").collect(Collectors.toList()); + List uList = new ArrayList<>(); + List pftList = new ArrayList<>(); + List btList = new ArrayList<>(); + for (String phase : phaseType) { + Double pMin = calParam.getP_βminMap().get(phase)/1000.00; + Double qMin = calParam.getQ_βminMap().get(phase)/1000.00; + Double[] res = calParam.getStringMap().get(phase); + double bt = CarryCapacityUtil.calculateB(pMin, qMin, Double.parseDouble(CarryingCapacityEnum.K.getValue()), sT, sPv, pPv); + btList.add(bt); + double pft = CarryCapacityUtil.calculatePF_T(pMin, qMin, Double.parseDouble(CarryingCapacityEnum.K.getValue()), sPv); + pftList.add(pft); + double u = CarryCapacityUtil.calculateU(res[0], res[1], res[2], pMin, Double.parseDouble(CarryingCapacityEnum.K.getValue()), qMin, sPv, Double.parseDouble(scaleValue)); + uList.add(u); + } + //取uList最大值 + double utMax = uList.stream().mapToDouble(Double::doubleValue) + .max() + .getAsDouble(); + + double pftMax = pftList.stream().mapToDouble(Double::doubleValue) + .max() + .getAsDouble(); + + double btMax = btList.stream().mapToDouble(Double::doubleValue) + .max() + .getAsDouble(); + Integer utLevel = CarryCapacityUtil.evaluateVoltageLevel(utMax); + carryCapacitydResultVO.setUTLevel(utLevel); + + Integer pftLevel = CarryCapacityUtil.evaluatePowerFactorLevel(pftMax); + carryCapacitydResultVO.setPfTLevel(pftLevel); + + Integer btLevel = CarryCapacityUtil.evaluateEquivalentLoadRateLevel(btMax); + carryCapacitydResultVO.setBTLevel(btLevel); + //谐波电流幅值判断 + //获取限值 + Overlimit overlimit = lineService.getOverLimitData(calParam.getLineId()); + + //各次谐波电流 均小于国标限值 返回1 存在某次谐波电流幅值 超出限值,但在1.25倍限值内 返回2 存在某次谐波电流幅值超出限值1.25倍以上 返回3 存在多次谐波电流幅值均超出限值1.25倍以上 返回4 + //i_count1小于国标限值的个数,i_count2>=国标限值<=1.25倍的国标限值,i_count3>1.25倍的国标限值 + int iCount1 = 0, iCount2 = 0, iCount3 = 0; + for (int i = 0; i < calParam.getI_βmax().size(); i++) { + double itm = CarryCapacityUtil.calculateITm(calParam.getI_βmax().get(i), Double.parseDouble(CarryingCapacityEnum.K.getValue()), + Double.parseDouble(scaleValue), sPv, Double.parseDouble(Objects.requireNonNull(CarryingCapacityEnum.getValueByCode("K_H_" + (i + 2)))), + Double.parseDouble(Objects.requireNonNull(CarryingCapacityEnum.getValueByCode("I_INV_" + (i + 2))))); + double getUharm = PubUtils.getValueByMethod(overlimit, "getIharm", i + 2); + if (itm < getUharm) { + iCount1++; + } else if (itm >= getUharm && itm <= 1.25 * getUharm) { + iCount2++; + } else if (itm > 1.25 * getUharm) { + iCount3++; + } + + } + int iLevel = 1; + if (iCount3 > 1) { + iLevel = 4; + } else if (iCount3 == 1) { + iLevel = 3; + } else if (iCount2 == 1) { + iLevel = 2; + } + carryCapacitydResultVO.setILevel(iLevel); + //统计安全,3级预警,2级预警1级预警个数 + List list = Stream.of(utLevel, pftLevel, btLevel, iLevel).collect(Collectors.toList()); + int safeCount, warnCount3, warnCount2, warnCount1; + safeCount = (int) list.stream() + .filter(temp -> temp == 1) + .count(); + warnCount3 = (int) list.stream() + .filter(temp -> temp == 2) + .count(); + warnCount2 = (int) list.stream() + .filter(temp -> temp == 3) + .count(); + warnCount1 = (int) list.stream() + .filter(temp -> temp == 4) + .count(); + + + List carryCapacityStrategyVOList = carryCapacityStrategyPOService.queyDetail(); + carryCapacitydResultVO.setReslutLevel(5); + //1-安全,2-III级预警,3-II级预警,4-I 级预警,依次执行策略看是否符合 + for (int i = 1; i < 5; i++) { + boolean b = strategyReslut(carryCapacityStrategyVOList, i, safeCount, warnCount1, warnCount2, warnCount3); + + if (b) { + carryCapacitydResultVO.setReslutLevel(i); + break; + } + } + CarryCapacityResultPO carryCapacityResult = new CarryCapacityResultPO(); + List list1 = carryCapacityResultPOService.lambdaQuery().eq(CarryCapacityResultPO::getLineId, calParam.getLineId()) + .eq(CarryCapacityResultPO::getUserId, calParam.getUserId()) + .eq(CarryCapacityResultPO::getStartTime, calParam.getStartTime()) + .eq(CarryCapacityResultPO::getEndTime, calParam.getEndTime()) + .eq(CarryCapacityResultPO::getStatus, 1).list(); + if (CollectionUtil.isNotEmpty(list1)) { + throw new BusinessException(CarryCapacityResponseEnum.EXISTENCE_EVALUATION_RESULT); + + } + + carryCapacityResult.setLineId(calParam.getLineId()); + carryCapacityResult.setUserId(calParam.getUserId()); + // + CarryCapacityUserPO carryCapacityUser = carryCapacityUserPOService.queyDetailUserById(calParam.getUserId()); + carryCapacityResult.setEvaluateType(carryCapacityUser.getUserType()); + + carryCapacityResult.setStartTime(calParam.getStartTime()); + carryCapacityResult.setEndTime(calParam.getEndTime()); + carryCapacityResult.setUTLevel(carryCapacitydResultVO.getUTLevel()); + carryCapacityResult.setPfTLevel(carryCapacitydResultVO.getPfTLevel()); + carryCapacityResult.setBTLevel(carryCapacitydResultVO.getBTLevel()); + carryCapacityResult.setILevel(carryCapacitydResultVO.getILevel()); + carryCapacityResult.setReslutLevel(carryCapacitydResultVO.getReslutLevel()); + carryCapacityResult.setEvaluateDate(LocalDate.now()); + carryCapacityResult.setStatus(1); + carryCapacityResultPOService.save(carryCapacityResult); + + return carryCapacitydResultVO; + } + + + @SneakyThrows + @Override + public boolean uploadExcel(ExcelDataParam excelDataParam) { + + String lineId = excelDataParam.getLineId(); + LineDetailDataVO data = lineService.getLineDetailData(lineId); + //时间间隔 + Integer timeInterval = data.getTimeInterval(); + + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate startDate = LocalDate.parse(excelDataParam.getStartTime(), formatter); + LocalDate endDate = LocalDate.parse(excelDataParam.getEndTime(), formatter); + //前2周的时间 + LocalDate startDate2 = startDate.plusWeeks(-1); + LocalDate endDate2 = endDate.plusWeeks(-1); + + + //前一周的数据 + ExcelDataDTO excelDataDTO = parsingFile(startDate, endDate, excelDataParam.getFile().getInputStream()); + List dataHarmPowerpList = excelDataDTO.getDataHarmPowerPList(); + List dataHarmPowerqList = excelDataDTO.getDataHarmPowerQList(); + List dataiList = excelDataDTO.getDataIList(); + ////前2周的数据 + List dataHarmPowerp2List = excelDataDTO.getDataHarmPowerP2List(); + List dataHarmPowerq2List = excelDataDTO.getDataHarmPowerQ2List(); + List dataHarmPoweruList = excelDataDTO.getDataHarmPowerU2List(); + + //数据校验 + if (CollectionUtil.isEmpty(dataHarmPowerpList)) { + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的本周数据及其上周是否充足"); + } else { + + if (checkData(dataHarmPowerpList, startDate, endDate, timeInterval)) { + + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的本周数据是否充足"); + } + } + linearInterpolation(dataHarmPowerpList); + + //数据校验 + if (CollectionUtil.isEmpty(dataHarmPowerp2List)) { + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的本周数据及其上周是否充足"); + } else { + + if (checkData(dataHarmPowerp2List, startDate2, endDate2, timeInterval)) { + + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的前一周数据量是否充足"); + } + } + linearInterpolation(dataHarmPowerp2List); + + //数据校验 + if (CollectionUtil.isEmpty(dataHarmPowerqList)) { + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的本周数据及其上周是否充足"); + } else { + + if (checkData(dataHarmPowerqList, startDate, endDate, timeInterval)) { + + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的本周数据是否充足"); + } + } + linearInterpolation(dataHarmPowerqList); + + //数据校验 + if (CollectionUtil.isEmpty(dataHarmPowerq2List)) { + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的本周数据及其上周是否充足"); + } else { + + if (checkData(dataHarmPowerq2List, startDate2, endDate2, timeInterval)) { + + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的前一周数据量是否充足"); + } + } + linearInterpolation(dataHarmPowerq2List); + + //数据校验 + if (CollectionUtil.isEmpty(dataHarmPoweruList)) { + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的本周数据及其上周是否充足"); + } else { + + if (checkData(dataHarmPoweruList, startDate2, endDate2, timeInterval)) { + + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的前一周数据量是否充足"); + } + } + linearInterpolation(dataHarmPoweruList); + + if (CollectionUtil.isEmpty(dataiList)) { + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的本周数据及其上周是否充足"); + } else { + List iList = dataiList.stream().map(temp -> { + CarryCapcityData carryCapcityData = new CarryCapcityData(); + BeanUtils.copyProperties(temp, carryCapcityData); + carryCapcityData.setValue(temp.getI2()); + return carryCapcityData; + }).collect(Collectors.toList()); + if (checkData(iList, startDate, endDate, timeInterval)) { + + throw new BusinessException("数据量过少,请查看上传数据集的数据集时间与页面选择时间"+startDate+"-"+endDate+"的本周数据量是否充足"); + } + } + linearInterpolationI(dataiList); + +// 存入redis + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate, formatter) + "#" + LocalDateTimeUtil.format(endDate, formatter) + "#" + "P", + dataHarmPowerpList); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate2, formatter) + "#" + LocalDateTimeUtil.format(endDate2, formatter) + "#" + "P", + dataHarmPowerp2List); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate, formatter) + "#" + LocalDateTimeUtil.format(endDate, formatter) + "#" + "Q", + dataHarmPowerqList); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate2, formatter) + "#" + LocalDateTimeUtil.format(endDate2, formatter) + "#" + "Q", + dataHarmPowerq2List); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate2, formatter) + "#" + LocalDateTimeUtil.format(endDate2, formatter) + "#" + "U", + dataHarmPoweruList); + redisUtil.saveByKey(lineId + "#" + LocalDateTimeUtil.format(startDate, formatter) + "#" + LocalDateTimeUtil.format(endDate, formatter) + "#" + "I", + dataiList); + //todo 将文件存入文件服务器获取url + String filePath = fileUtils.uploadFile(excelDataParam.getFile()); +// String url = "temp"; + CarryCapacityDataPO carryCapacityData = new CarryCapacityDataPO(lineId, startDate, endDate, filePath); + carryCapacityDataPOService.saveOrUpdateByMultiId(carryCapacityData); + + + return true; + } + + + @Override + public CarryCapacityDResultVO carryCapacityEvaluate(CarryCapacityEvaluateParam calParam) { + CarryCapacityDResultVO vo = new CarryCapacityDResultVO(); + List carryCapacityiResultList = new ArrayList<>(); + + List devList = calParam.getDevList(); + if (CollectionUtil.isEmpty(devList)) { + throw new BusinessException(CarryCapacityResponseEnum.DEVICE_LOST); + } + String userId = devList.get(0).getUserId(); + CarryCapacityUserPO carryCapacityUser = carryCapacityUserPOService.queyDetailUserById(userId); + String userType = carryCapacityUser.getUserType(); + String code = iDictDataService.getDicDataById(userType).getCode(); + //用户协议容量 + double sumCapacity = carryCapacityUser.getProtocolCapacity(); + + double rate = sumCapacity / calParam.getShortCapacity(); + vo.setFirstResult(rate * 100); + + CarryCapacityResultPO carryCapacityResult = new CarryCapacityResultPO(); + carryCapacityResult.setFirstResult(rate * 100); + carryCapacityResult.setUserId(userId); + carryCapacityResult.setEvaluateDate(LocalDate.now()); + carryCapacityResult.setEvaluateType(userType); + carryCapacityResult.setStatus(1); + carryCapacityResult.setPtType(calParam.getPtType()); + carryCapacityResult.setConnectionMode(calParam.getConnectionMode()); + carryCapacityResult.setK(calParam.getK()); + carryCapacityResult.setUserMode(calParam.getUserMode()); + carryCapacityResult.setScale(calParam.getScale()); + carryCapacityResult.setShortCapacity(calParam.getShortCapacity()); + carryCapacityResult.setDeviceCapacity(calParam.getDeviceCapacity()); + + + carryCapacityResult.setStatus(1); + if (rate < 0.001) { + carryCapacityResult.setReslutLevel(6); + carryCapacityResultPOService.save(carryCapacityResult); + return vo; + } + + /*二次评估充电桩、电气化铁路如果经过变压器并网的是需要的,像电弧炉他是要经过一个电弧炉专用变压器并网的 + 正常如果是专变用户的话是经过变压器的高压侧进行考核,公变用户的正常是在低压侧进行考核, + 当变压器连接方式为YNyn零序电流可以流通计算变压器高压侧的谐波电流(零序电流次数为3、6、9、12、)否则为0 + */ + //设备电压等级单位KV + String sacaleValue = iDictDataService.getDicDataById(calParam.getScale()).getValue(); + +// //用户电压等级 + DictData data = iDictDataService.getDicDataById(carryCapacityUser.getVoltage()); + + float userSacaleValue = Float.parseFloat(data.getValue()) * (data.getCode().contains("k") ? 1000 : 1); + //用户模式专变用户,公变用户 + String userMode = iDictDataService.getDicDataById(calParam.getUserMode()).getCode(); + //变压器连接方式接线方式 + String connectionMode; + if (Objects.nonNull(calParam.getConnectionMode()) && !"".equals(calParam.getConnectionMode())) { + connectionMode = iDictDataService.getDicDataById(calParam.getConnectionMode()).getCode(); + + } else { + connectionMode = ""; + } + + List integerList = Stream.of(3, 5, 7, 9, 11, 13, 15, 17, 19).collect(Collectors.toList()); + + + Overlimit overlimit = new Overlimit(); + COverlimitUtil.iHarm(overlimit, Float.valueOf(sacaleValue), (float) sumCapacity, calParam.getDeviceCapacity(), calParam.getShortCapacity()); + + + if (DicDataEnum.Charging_Station_Users.getCode().equals(code)) { + + integerList.forEach(temp -> { + CarryCapacityDResultVO.CarryCapacityIResult carryCapacityiResultVO = new CarryCapacityDResultVO.CarryCapacityIResult(); + List ilist = new ArrayList<>(); + devList.forEach(dev -> { + DictData devScaledata = iDictDataService.getDicDataById(dev.getDevScale()); + double devScaleValue = Float.parseFloat(devScaledata.getValue()) * (devScaledata.getCode().contains("k") ? 1 : 0.0001); + //基波电流I_1:设备容量(转成KVA*1000)*K(功率因数)(转成kW)/更号3*电压等级(转成Kv) + + double i1 = dev.getProtocolCapacity() * 1000 * calParam.getK() / (Math.sqrt(3) * devScaleValue); + //低压侧 + double iH = i1 * (Double.parseDouble(Objects.requireNonNull(CarryingCapacityEnum.getValueByCode("CP_I_" + temp)))) / 100; + //当变压器连接方式为YNyn零序电流可以流通计算变压器高压侧的谐波电流(零序电流次数为3、6、9、12、)否则为0 + if (!DicDataEnum.YNyn.getCode().equals(connectionMode) && temp % 3 == 0) { + iH = 0.00; + } + ilist.add(iH); + + }); + //将变压器下多个设备电流合并 + Double mergeI = mergeiList(ilist); + + + //专变用户的话是经过变压器的高压侧进行考核,公变用户的正常是在低压侧进行考核 + if (DicDataEnum.SPECIAL_USER.getCode().equals(userMode)) { + mergeI = mergeI / (Double.parseDouble(sacaleValue) * 1000 / userSacaleValue); + } + + carryCapacityiResultVO.setTime(temp); + carryCapacityiResultVO.setI(mergeI); + double getUharm = PubUtils.getValueByMethod(overlimit, "getIharm", temp); + carryCapacityiResultVO.setI_limit(getUharm); + carryCapacityiResultList.add(carryCapacityiResultVO); + }); + + } else if (DicDataEnum.Electric_Heating_Load_Users.getCode().equals(code)) { + + integerList.forEach(temp -> { + CarryCapacityDResultVO.CarryCapacityIResult carryCapacityiResultVO = new CarryCapacityDResultVO.CarryCapacityIResult(); + List ilist = new ArrayList<>(); + devList.forEach(dev -> { + DictData devScaledata = iDictDataService.getDicDataById(dev.getDevScale()); + double devScaleValue = Float.parseFloat(devScaledata.getValue()) * (devScaledata.getCode().contains("k") ? 1 : 0.0001); + //基波电流I_1:设备容量(转成KVA*1000)*K(功率因数)(转成kW)/更号3*电压等级(转成Kv) + double i1 = dev.getProtocolCapacity() * 1000 * calParam.getK() / (Math.sqrt(3) * devScaleValue); + //低压侧 + double iH = i1 * (Double.parseDouble(Objects.requireNonNull(CarryingCapacityEnum.getValueByCode("EAF_I_" + temp)))) / 100; + //当变压器连接方式为YNyn零序电流可以流通计算变压器高压侧的谐波电流(零序电流次数为3、6、9、12、)否则为0 + if (!DicDataEnum.YNyn.getCode().equals(connectionMode) && temp % 3 == 0) { + iH = 0.00; + } + ilist.add(iH); + + }); + //将变压器下多个设备电流合并 + Double mergeI = mergeiList(ilist); + + + //专变用户的话是经过变压器的高压侧进行考核,公变用户的正常是在低压侧进行考核 + if (DicDataEnum.SPECIAL_USER.getCode().equals(userMode)) { + mergeI = mergeI / (Double.parseDouble(sacaleValue) * 1000 / userSacaleValue); + } + + carryCapacityiResultVO.setTime(temp); + carryCapacityiResultVO.setI(mergeI); + double getUharm = PubUtils.getValueByMethod(overlimit, "getIharm", temp); + carryCapacityiResultVO.setI_limit(getUharm); + carryCapacityiResultList.add(carryCapacityiResultVO); + }); + + } else if (DicDataEnum.Electrified_Rail_Users.getCode().equals(code)) { + integerList.forEach(temp -> { + CarryCapacityDResultVO.CarryCapacityIResult carryCapacityiResult = new CarryCapacityDResultVO.CarryCapacityIResult(); + List ilist = new ArrayList<>(); + devList.forEach(dev -> { + DictData devScaledata = iDictDataService.getDicDataById(dev.getDevScale()); + double devScaleValue = Float.parseFloat(devScaledata.getValue()) * (devScaledata.getCode().contains("k") ? 1 : 0.0001); + //基波电流I_1:设备容量(转成KVA*1000)*K(功率因数)(转成kW)/更号3*电压等级(转成Kv) + double i1 = dev.getProtocolCapacity() * 1000 * calParam.getK() / (Math.sqrt(3) * devScaleValue); + //低压侧 + double iH = i1 * (Double.parseDouble(Objects.requireNonNull(CarryingCapacityEnum.getValueByCode("ER_I_" + temp)))) / 100; + //当变压器连接方式为YNyn零序电流可以流通计算变压器高压侧的谐波电流(零序电流次数为3、6、9、12、)否则为0 + if (!DicDataEnum.YNyn.getCode().equals(connectionMode) && temp % 3 == 0) { + iH = 0.00; + } + ilist.add(iH); + + }); + //将变压器下多个设备电流合并 + Double mergeI = mergeiList(ilist); + + + //专变用户的话是经过变压器的高压侧进行考核,公变用户的正常是在低压侧进行考核 + if (DicDataEnum.SPECIAL_USER.getCode().equals(userMode)) { + mergeI = mergeI / (Double.parseDouble(sacaleValue) * 1000 / userSacaleValue); + } + + carryCapacityiResult.setTime(temp); + carryCapacityiResult.setI(mergeI); + double getUharm = PubUtils.getValueByMethod(overlimit, "getIharm", temp); + carryCapacityiResult.setI_limit(getUharm); + carryCapacityiResultList.add(carryCapacityiResult); + }); + } + vo.setIResultList(carryCapacityiResultList); + carryCapacityResult.setIResultList(JSONUtil.toJsonStr(carryCapacityiResultList)); + long count = carryCapacityiResultList.stream().filter(temp -> temp.getI() > temp.getI_limit()).count(); + carryCapacityResult.setReslutLevel(count == 0 ? 6 : 5); + vo.setReslutLevel(count == 0 ? 6 : 5); + carryCapacityResultPOService.save(carryCapacityResult); + + return vo; + } + + private Double mergeiList(List ilist) { + Double result; + + if (ilist.size() == 1) { + return ilist.get(0); + } else { + result = ilist.get(0); + for (int i = 1; i < ilist.size(); i++) { + double sum = result * result + ilist.get(i) * ilist.get(i) + Double.parseDouble(Objects.requireNonNull(CarryingCapacityEnum.getValueByCode("K_H_" + (i + 2)))) * result * ilist.get(i); + result = Math.sqrt(sum); + } + + } + + + return result; + } + + + public static ExcelDataDTO parsingFile(LocalDate startDate, LocalDate endDate, InputStream is) { +// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + List dataHarmPowerpList; + List dataHarmPowerqList; + List dataiList; + + //前2周的时间 + LocalDate startDate2 = startDate.plusWeeks(-1); + LocalDate endDate2 = endDate.plusWeeks(-1); + + //前一周的数据 + List dataHarmPowerP2List; + List dataHarmPowerQ2List; + List dataHarmPowerU2List; + + try { + List objects = EasyExcelUtil.syncReadModel(is, CarryCapcityDataEexcel.class, 0, 3); + objects = objects.stream().filter(temp -> Objects.nonNull(temp.getTime())).collect(Collectors.toList()); + List iEexcelList = new ArrayList<>(); + List vEexcelList = new ArrayList<>(); + List pEexcelList = new ArrayList<>(); + List qEexcelList = new ArrayList<>(); + objects.forEach(temp -> { + CarryCapcityDataIEexcel carryCapcityDataiEexcel = new CarryCapcityDataIEexcel(); + CarryCapcityDataVEexcel carryCapcityDatavEexcel = new CarryCapcityDataVEexcel(); + CarryCapcityDataPEexcel carryCapcityDatapEexcel = new CarryCapcityDataPEexcel(); + CarryCapcityDataQEexcel carryCapcityDataqEexcel = new CarryCapcityDataQEexcel(); + + BeanUtils.copyProperties(temp, carryCapcityDataiEexcel); + carryCapcityDataiEexcel.setTime(temp.getTime().atZone(ZoneId.systemDefault()).toInstant()); + carryCapcityDataiEexcel.setValueType("CP95"); + + carryCapcityDatavEexcel.setTime(temp.getTime().atZone(ZoneId.systemDefault()).toInstant()); + carryCapcityDatavEexcel.setValueType("CP95"); + carryCapcityDatavEexcel.setValue_a(temp.getU_a()); + carryCapcityDatavEexcel.setValue_b(temp.getU_b()); + carryCapcityDatavEexcel.setValue_c(temp.getU_c()); + + + carryCapcityDatapEexcel.setTime(temp.getTime().atZone(ZoneId.systemDefault()).toInstant()); + carryCapcityDatapEexcel.setValueType("CP95"); + carryCapcityDatapEexcel.setValue_a(temp.getP_a()); + carryCapcityDatapEexcel.setValue_b(temp.getP_b()); + carryCapcityDatapEexcel.setValue_c(temp.getP_c()); + + carryCapcityDataqEexcel.setTime(temp.getTime().atZone(ZoneId.systemDefault()).toInstant()); + carryCapcityDataqEexcel.setValueType("CP95"); + carryCapcityDataqEexcel.setValue_a(temp.getQ_a()); + carryCapcityDataqEexcel.setValue_b(temp.getQ_b()); + carryCapcityDataqEexcel.setValue_c(temp.getQ_c()); + iEexcelList.add(carryCapcityDataiEexcel); + vEexcelList.add(carryCapcityDatavEexcel); + pEexcelList.add(carryCapcityDatapEexcel); + qEexcelList.add(carryCapcityDataqEexcel); + + }); + + + List collect = iEexcelList.stream().map(CarryCapcityDataIEexcel::excelToPO).filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList()); + dataiList = collect.stream().filter( + item -> Utils.isTimeInRange(item.getTime(), startDate, endDate) + ).collect(Collectors.toList()); + + + // 校验合格的数据 + List collect2 = vEexcelList.stream().map(CarryCapcityDataVEexcel::excelToPO).filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList()); + // 业务逻辑 + dataHarmPowerU2List = collect2.stream().filter( + item -> Utils.isTimeInRange(item.getTime(), startDate2, endDate2) + ).collect(Collectors.toList()); + + + // 校验合格的数据 + List collect3 = pEexcelList.stream().map(CarryCapcityDataPEexcel::excelToPO).filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList()); + dataHarmPowerpList = collect3.stream().filter( + item -> Utils.isTimeInRange(item.getTime(), startDate, endDate) + ).collect(Collectors.toList()); + dataHarmPowerP2List = collect3.stream().filter( + item -> Utils.isTimeInRange(item.getTime(), startDate2, endDate2) + ).collect(Collectors.toList()); + + + List collect4 = qEexcelList.stream().map(CarryCapcityDataQEexcel::excelToPO).filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList()); + dataHarmPowerqList = collect4.stream().filter( + item -> Utils.isTimeInRange(item.getTime(), startDate, endDate) + ).collect(Collectors.toList()); + dataHarmPowerQ2List = collect4.stream().filter( + item -> Utils.isTimeInRange(item.getTime(), startDate2, endDate2) + ).collect(Collectors.toList()); + + } catch (Exception e) { + throw new BusinessException(CarryCapacityResponseEnum.DOCUMENT_FORMAT_ERROR); + } + ExcelDataDTO dto = new ExcelDataDTO(); + dto.setDataHarmPowerPList(dataHarmPowerpList); + dto.setDataHarmPowerQList(dataHarmPowerqList); + dto.setDataIList(dataiList); + dto.setDataHarmPowerP2List(dataHarmPowerP2List); + dto.setDataHarmPowerQ2List(dataHarmPowerQ2List); + dto.setDataHarmPowerU2List(dataHarmPowerU2List); + + return dto; + } + + /** + * @Description: 首先,找到缺失值的前一个和后一个非缺失值。 + * 计算两个非缺失值之间的差值。 + * 将差值除以两个非缺失值之间的距离,得到斜率。 + * 使用斜率和前一个非缺失值计算缺失值的近似值。 + * @Param: + * @Author: clam + * @Date: 2024/2/28 + */ + public static void linearInterpolation(List data) { + + + data.stream().collect(Collectors.groupingBy(CarryCapcityData::getPhaseType)).forEach((k, v) -> { + + for (int i = 0; i < v.size(); i++) { + if (v.get(i).getValue() == DEFAULTVALUE || Objects.isNull(v.get(i).getValue())) { + int prevIndex = i - 1; + int nextIndex = i + 1; + while (prevIndex >= 0 && (v.get(prevIndex).getValue() == DEFAULTVALUE || Objects.isNull(v.get(prevIndex).getValue()))) { + prevIndex--; + } + while (nextIndex < v.size() && (v.get(nextIndex).getValue() == DEFAULTVALUE || Objects.isNull(v.get(nextIndex).getValue()))) { + nextIndex++; + } + if (prevIndex >= 0 && nextIndex < v.size()) { + double slope = (v.get(nextIndex).getValue() - v.get(prevIndex).getValue()) / (nextIndex - prevIndex); + v.get(i).setValue(v.get(prevIndex).getValue() + slope * (i - prevIndex)); + } else { + v.get(i).setValue(DEFAULTVALUE); + } + } + } + }); + + + } + + /** + * @Description: linearInterpolationI 电流数据缺失填补 + * @Param: + * @return: void + * @Author: clam + * @Date: 2024/2/28 + */ + public static void linearInterpolationI(List data) { + + data.stream().collect(Collectors.groupingBy(DataI::getPhaseType)).forEach((k, v) -> { + for (int i = 0; i < v.size(); i++) { + if (v.get(i).getI2() == DEFAULTVALUE || Objects.isNull(v.get(i).getI2())) { + int prevIndex = i - 1; + int nextIndex = i + 1; + while (prevIndex >= 0 && (v.get(prevIndex).getI2() == DEFAULTVALUE || Objects.isNull(v.get(prevIndex).getI2()))) { + prevIndex--; + } + while (nextIndex < v.size() && (v.get(nextIndex).getI2() == DEFAULTVALUE || Objects.isNull(v.get(nextIndex).getI2()))) { + nextIndex++; + } + if (prevIndex >= 0 && nextIndex < v.size()) { + double slope = (v.get(nextIndex).getI2() - v.get(prevIndex).getI2()) / (nextIndex - prevIndex); + v.get(i).setI2(v.get(prevIndex).getI2() + slope * (i - prevIndex)); + v.get(i).setI3(v.get(prevIndex).getI3() + slope * (i - prevIndex)); + v.get(i).setI4(v.get(prevIndex).getI4() + slope * (i - prevIndex)); + v.get(i).setI5(v.get(prevIndex).getI5() + slope * (i - prevIndex)); + v.get(i).setI6(v.get(prevIndex).getI6() + slope * (i - prevIndex)); + v.get(i).setI7(v.get(prevIndex).getI7() + slope * (i - prevIndex)); + v.get(i).setI8(v.get(prevIndex).getI8() + slope * (i - prevIndex)); + v.get(i).setI9(v.get(prevIndex).getI9() + slope * (i - prevIndex)); + v.get(i).setI10(v.get(prevIndex).getI10() + slope * (i - prevIndex)); + v.get(i).setI11(v.get(prevIndex).getI11() + slope * (i - prevIndex)); + v.get(i).setI12(v.get(prevIndex).getI12() + slope * (i - prevIndex)); + v.get(i).setI13(v.get(prevIndex).getI13() + slope * (i - prevIndex)); + v.get(i).setI14(v.get(prevIndex).getI14() + slope * (i - prevIndex)); + v.get(i).setI15(v.get(prevIndex).getI15() + slope * (i - prevIndex)); + v.get(i).setI16(v.get(prevIndex).getI16() + slope * (i - prevIndex)); + v.get(i).setI17(v.get(prevIndex).getI17() + slope * (i - prevIndex)); + v.get(i).setI18(v.get(prevIndex).getI18() + slope * (i - prevIndex)); + v.get(i).setI19(v.get(prevIndex).getI19() + slope * (i - prevIndex)); + v.get(i).setI20(v.get(prevIndex).getI20() + slope * (i - prevIndex)); + v.get(i).setI21(v.get(prevIndex).getI21() + slope * (i - prevIndex)); + v.get(i).setI22(v.get(prevIndex).getI22() + slope * (i - prevIndex)); + v.get(i).setI23(v.get(prevIndex).getI23() + slope * (i - prevIndex)); + v.get(i).setI24(v.get(prevIndex).getI24() + slope * (i - prevIndex)); + v.get(i).setI25(v.get(prevIndex).getI25() + slope * (i - prevIndex)); + + } else { + v.get(i).setI2(DEFAULTVALUE); + v.get(i).setI3(DEFAULTVALUE); + v.get(i).setI4(DEFAULTVALUE); + v.get(i).setI5(DEFAULTVALUE); + v.get(i).setI6(DEFAULTVALUE); + v.get(i).setI7(DEFAULTVALUE); + v.get(i).setI8(DEFAULTVALUE); + v.get(i).setI9(DEFAULTVALUE); + v.get(i).setI10(DEFAULTVALUE); + v.get(i).setI11(DEFAULTVALUE); + v.get(i).setI12(DEFAULTVALUE); + v.get(i).setI13(DEFAULTVALUE); + v.get(i).setI14(DEFAULTVALUE); + v.get(i).setI15(DEFAULTVALUE); + v.get(i).setI16(DEFAULTVALUE); + v.get(i).setI17(DEFAULTVALUE); + v.get(i).setI18(DEFAULTVALUE); + v.get(i).setI19(DEFAULTVALUE); + v.get(i).setI20(DEFAULTVALUE); + v.get(i).setI21(DEFAULTVALUE); + v.get(i).setI22(DEFAULTVALUE); + v.get(i).setI23(DEFAULTVALUE); + v.get(i).setI24(DEFAULTVALUE); + v.get(i).setI25(DEFAULTVALUE); + + } + } + } + }); + + + } + + /** + * @Description: calUParam 首端电压模型训练获取A,B,C三项C,a,b参数 + * @Param: + * @return: java.util.Map + * @Author: clam + * @Date: 2024/2/29 + */ + public static Map caluParam(List dataHarmPowerpList2, List dataHarmPowerqList2, List datavList2) { + Map results = new HashMap<>(4); + List phaseType = Stream.of("A", "B", "C").collect(Collectors.toList()); + + + phaseType.forEach(phase -> { + List listP2 = dataHarmPowerpList2.stream().filter(temp -> Utils.isTimeInRange(temp.getTime(), LocalTime.of(9, 0), LocalTime.of(15, 0))) + .filter(temp -> temp.getPhaseType().equals(phase)) + .map(CarryCapcityData::getValue) + .collect(Collectors.toList()); + + List listQ2 = dataHarmPowerqList2.stream().filter(temp -> Utils.isTimeInRange(temp.getTime(), LocalTime.of(9, 0), LocalTime.of(15, 0))) + .filter(temp -> temp.getPhaseType().equals(phase)) + .map(CarryCapcityData::getValue) + .collect(Collectors.toList()); + + List listV2 = datavList2.stream().filter(temp -> Utils.isTimeInRange(temp.getTime(), LocalTime.of(9, 0), LocalTime.of(15, 0))) + .filter(temp -> temp.getPhaseType().equals(phase)) + .map(CarryCapcityData::getValue) + .collect(Collectors.toList()); + //todo 抽取5000条数据(抽取方式待确定) + Double[] res = new Double[3]; + CarryCapacityUtil.cznlpgDataTrain(listV2, listP2, listQ2, listV2.size(), res); + results.put(phase, res); + + }); + + return results; + } + + private static boolean compareNumbers(int num1, int num2, String operator) { + if ("/".equals(operator)) { + return true; + } else if ("<".equals(operator)) { + return num1 < num2; + } else if (">".equals(operator)) { + return num1 > num2; + } else if ("<=".equals(operator)) { + return num1 <= num2; + } else if (">=".equals(operator)) { + return num1 >= num2; + } else if ("==".equals(operator)) { + return num1 == num2; + } else if ("!=".equals(operator)) { + return num1 != num2; + } else { + throw new IllegalArgumentException("无效的操作符"); + } + } + + private static boolean strategyReslut(List carryCapacityStrategyVOList, int resultLevel, int safeCount, int warnCount1, int warnCount2, int warnCount3) { + + CarryCapacityStrategyVO carryCapacityStrategyVO = carryCapacityStrategyVOList.stream() + .filter(temp -> temp.getResult() == resultLevel) + .collect(Collectors.toList()).get(0); + //每个策略组结果 + List list = new ArrayList<>(); + List capacityStrategysingleVOList = carryCapacityStrategyVO.getCapacityStrategysingleVOList(); + capacityStrategysingleVOList.forEach(temp -> { + CarryCapacityStrategyVO.CarryCapacityStrategysingleVO.CarryCapacityStrategyIndexVO carryCapacityStrategyIndexVO = temp.getCarryCapacityStrategyIndexVOList().stream() + .filter(temp1 -> temp1.getIndexResult() == 1) + .collect(Collectors.toList()).get(0); + boolean b1 = compareNumbers(safeCount, carryCapacityStrategyIndexVO.getCount(), carryCapacityStrategyIndexVO.getComparisonOperators()); + CarryCapacityStrategyVO.CarryCapacityStrategysingleVO.CarryCapacityStrategyIndexVO vo2 = temp.getCarryCapacityStrategyIndexVOList().stream() + .filter(temp1 -> temp1.getIndexResult() == 2) + .collect(Collectors.toList()).get(0); + boolean b2 = compareNumbers(warnCount3, vo2.getCount(), vo2.getComparisonOperators()); + + CarryCapacityStrategyVO.CarryCapacityStrategysingleVO.CarryCapacityStrategyIndexVO vo3 = temp.getCarryCapacityStrategyIndexVOList().stream() + .filter(temp1 -> temp1.getIndexResult() == 3) + .collect(Collectors.toList()).get(0); + boolean b3 = compareNumbers(warnCount2, vo3.getCount(), vo3.getComparisonOperators()); + + + CarryCapacityStrategyVO.CarryCapacityStrategysingleVO.CarryCapacityStrategyIndexVO vo4 = temp.getCarryCapacityStrategyIndexVOList().stream() + .filter(temp1 -> temp1.getIndexResult() == 4) + .collect(Collectors.toList()).get(0); + boolean b4 = compareNumbers(warnCount1, vo4.getCount(), vo4.getComparisonOperators()); + Boolean flag = b1 && b2 && b3 && b4; + list.add(flag); + }); + long count = list.stream().filter(temp -> temp).count(); + return count > 0; + } + + private static boolean checkData(List list, LocalDate startTime, LocalDate endTime, int timeInterval) { + boolean flag = false; + long daysBetween = ChronoUnit.DAYS.between(startTime, endTime); + //根据时间间隔算出最低数据量 1天*6小时*60分钟*90%/时间间隔算出一天一个的数据 + int minDataNum = 6 * 60 * 3 * 80 / (100 * timeInterval); + //合格天数》=3通过 + int days = 0; + + for (long i = 0; i <= daysBetween; i++) { + LocalDate currentDay = startTime.plusDays(i); + long count = list.stream() + .filter(temp -> Utils.isTimeInRange(temp.getTime(), LocalTime.of(9, 0), LocalTime.of(15, 0))) + .filter(temp -> temp.getValue() != DEFAULTVALUE && Objects.nonNull(temp.getValue())) + .filter(temp -> temp.getTime().atZone(ZoneId.systemDefault()).toLocalDate().equals(currentDay)).count(); + if (count >= minDataNum) { + days++; + } + + } + if (days >= 3) { + flag = true; + } + + return !flag; + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityStrategyDhlPOServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityStrategyDhlPOServiceImpl.java new file mode 100644 index 0000000..00532ac --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityStrategyDhlPOServiceImpl.java @@ -0,0 +1,62 @@ +package com.njcn.product.carrycapacity.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.product.carrycapacity.mapper.CarryCapacityStrategyDhlPOMapper; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityStrategyDhlPO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityStrategyDhlVO; +import com.njcn.product.carrycapacity.service.CarryCapacityStrategyDhlPOService; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * + * Description: + * Date: 2024/3/15 10:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class CarryCapacityStrategyDhlPOServiceImpl extends ServiceImpl implements CarryCapacityStrategyDhlPOService { + + + @Override + public List queyDetailDhl() { + List list = this.lambdaQuery().eq(CarryCapacityStrategyDhlPO::getUserFlag, 1).list(); + return list.stream().map(t -> { + CarryCapacityStrategyDhlVO vo = new CarryCapacityStrategyDhlVO(); + vo.setCount1(t.getCount1()); + vo.setCount2(t.getCount2()); + vo.setCount3(t.getCount3()); + vo.setCount4(t.getCount4()); + vo.setComparisonOperators1(t.getComparisonOperators1()); + vo.setComparisonOperators2(t.getComparisonOperators2()); + vo.setComparisonOperators3(t.getComparisonOperators3()); + vo.setComparisonOperators4(t.getComparisonOperators4()); + vo.setId(t.getId()); + vo.setProtoFlag(t.getProtoFlag()); + vo.setType(t.getType()); + return vo; + }).collect(Collectors.toList()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean adddhl(CarryCapacityStrategyDhlVO capacityStrategyDhlVO) { + this.lambdaUpdate().eq(CarryCapacityStrategyDhlPO::getId, capacityStrategyDhlVO.getId()). + set(CarryCapacityStrategyDhlPO::getUserFlag, 2).update(); + CarryCapacityStrategyDhlPO carryCapacityStrategyDhlPO = new CarryCapacityStrategyDhlPO(); + BeanUtils.copyProperties(capacityStrategyDhlVO,carryCapacityStrategyDhlPO); + carryCapacityStrategyDhlPO.setId(null); + carryCapacityStrategyDhlPO.setUserFlag(1); + carryCapacityStrategyDhlPO.setProtoFlag(2); + boolean save = this.save(carryCapacityStrategyDhlPO); + return save; + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityStrategyPOServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityStrategyPOServiceImpl.java new file mode 100644 index 0000000..7fc76d4 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityStrategyPOServiceImpl.java @@ -0,0 +1,139 @@ +package com.njcn.product.carrycapacity.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.carrycapacity.enums.CarryCapacityResponseEnum; +import com.njcn.product.carrycapacity.mapper.CarryCapacityStrategyDhlPOMapper; +import com.njcn.product.carrycapacity.mapper.CarryCapacityStrategyPOMapper; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityStrategyParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityStrategyDhlPO; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityStrategyPO; +import com.njcn.product.carrycapacity.pojo.vo.CarryCapacityStrategyVO; +import com.njcn.product.carrycapacity.service.CarryCapacityStrategyPOService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * + * Description: + * Date: 2024/3/5 10:33【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +@RequiredArgsConstructor +public class CarryCapacityStrategyPOServiceImpl extends ServiceImpl implements CarryCapacityStrategyPOService { + private final CarryCapacityStrategyDhlPOMapper carryCapacityStrategyDhlPOMapper; + @Override + @Transactional(rollbackFor = {Exception.class}) + public Boolean add(CarryCapacityStrategyParam carryCapacityStrategyParam) { + + CarryCapacityStrategyPO carryCapacityStrategy = new CarryCapacityStrategyPO(); + BeanUtils.copyProperties(carryCapacityStrategyParam, carryCapacityStrategy); + //将原始策略处理为不启用 + this.lambdaUpdate().eq(CarryCapacityStrategyPO::getResult, carryCapacityStrategyParam.getResult()) + .eq(CarryCapacityStrategyPO::getId, carryCapacityStrategyParam.getId()) + .eq(CarryCapacityStrategyPO::getIndexResult, carryCapacityStrategyParam.getIndexResult()) + .eq(CarryCapacityStrategyPO::getProtoFlag, 1) + .set(CarryCapacityStrategyPO::getUserFlag,2) + .update(); + QueryWrapper lambdaQuery = new QueryWrapper<>(); + lambdaQuery.lambda() + .eq(CarryCapacityStrategyPO::getResult, carryCapacityStrategyParam.getResult()) + .eq(CarryCapacityStrategyPO::getId, carryCapacityStrategyParam.getId()) + .eq(CarryCapacityStrategyPO::getIndexResult, carryCapacityStrategyParam.getIndexResult()) + .eq(CarryCapacityStrategyPO::getProtoFlag, 2); + //将客户对应策略删除 + this.remove(lambdaQuery); + //新增客户策略; + carryCapacityStrategy.setProtoFlag(2); + carryCapacityStrategy.setUserFlag(1); + return this.save(carryCapacityStrategy); + } + + @Override + public List queyDetail() { + List result = new ArrayList<>(); + List list = this.lambdaQuery().eq(CarryCapacityStrategyPO::getUserFlag, 1).list(); + Map>> collect = list.stream().collect(Collectors.groupingBy(CarryCapacityStrategyPO::getResult, + Collectors.groupingBy(CarryCapacityStrategyPO::getId))); + collect.forEach((key, value) -> { + CarryCapacityStrategyVO vo = new CarryCapacityStrategyVO(); + vo.setResult(key); + List capacityStrategysingleVOList =new ArrayList<>(); + value.forEach((k, v) -> { + CarryCapacityStrategyVO.CarryCapacityStrategysingleVO vo1 = new CarryCapacityStrategyVO.CarryCapacityStrategysingleVO(); + vo1.setId(k); + vo1.setCarryCapacityStrategyIndexVOList(v.stream().map(temp -> { + CarryCapacityStrategyVO.CarryCapacityStrategysingleVO.CarryCapacityStrategyIndexVO vo2 = new CarryCapacityStrategyVO.CarryCapacityStrategysingleVO.CarryCapacityStrategyIndexVO(); + BeanUtils.copyProperties(temp, vo2); + return vo2; + }).collect(Collectors.toList())); + capacityStrategysingleVOList.add(vo1); + }); + vo.setCapacityStrategysingleVOList(capacityStrategysingleVOList); + result.add(vo); + }); + + return result; + } + + @Override + public Boolean restore() { + //将客户对应策略删除 + QueryWrapper lambdaQuery = new QueryWrapper<>(); + lambdaQuery.lambda() + .eq(CarryCapacityStrategyPO::getProtoFlag, 2); + this.remove(lambdaQuery); + + //将原始策略处理为启用 + boolean update = this.lambdaUpdate().eq(CarryCapacityStrategyPO::getProtoFlag, 1) + .set(CarryCapacityStrategyPO::getUserFlag, 1) + .update(); + + //电弧炉初始化 + QueryWrapper lambdaQuery2 = new QueryWrapper<>(); + lambdaQuery2.lambda() + .eq(CarryCapacityStrategyDhlPO::getProtoFlag, 2); + carryCapacityStrategyDhlPOMapper.delete(lambdaQuery2); + UpdateWrapper lambdaQuery3 = new UpdateWrapper<>(); + lambdaQuery3.lambda() + .eq(CarryCapacityStrategyDhlPO::getProtoFlag, 1) + .set(CarryCapacityStrategyDhlPO::getUserFlag, 1); + + carryCapacityStrategyDhlPOMapper.update(null,lambdaQuery3); + + return update; + } + + @Override + public Boolean addList(List carryCapacityStrategyParamList) { + UUID uuid = UUID.randomUUID(); + if(4!=carryCapacityStrategyParamList.size()){ + throw new BusinessException(CarryCapacityResponseEnum.UNCOMPLETE_STRATEGY); + + } + List collect = carryCapacityStrategyParamList.stream().map(temp -> { + CarryCapacityStrategyPO po = new CarryCapacityStrategyPO(); + BeanUtils.copyProperties(temp, po); + po.setId(uuid.toString()); + //新增客户策略; + po.setProtoFlag(2); + po.setUserFlag(1); + return po; + }).collect(Collectors.toList()); + return this.saveBatch(collect); + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityUserPOServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityUserPOServiceImpl.java new file mode 100644 index 0000000..5b5804b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/service/impl/CarryCapacityUserPOServiceImpl.java @@ -0,0 +1,123 @@ +package com.njcn.product.carrycapacity.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.common.pojo.constant.LogInfo; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.auth.pojo.vo.UserVO; +import com.njcn.product.auth.service.IUserService; +import com.njcn.product.carrycapacity.enums.CarryCapacityResponseEnum; +import com.njcn.product.carrycapacity.mapper.CarryCapacityUserPOMapper; +import com.njcn.product.carrycapacity.pojo.param.CarryCapacityUserParam; +import com.njcn.product.carrycapacity.pojo.po.CarryCapacityUserPO; +import com.njcn.product.carrycapacity.service.CarryCapacityUserPOService; +import com.njcn.product.carrycapacity.util.CheckStringUtil; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Objects; + +/** + * + * Description: + * Date: 2024/2/20 11:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +@RequiredArgsConstructor +public class CarryCapacityUserPOServiceImpl extends ServiceImpl implements CarryCapacityUserPOService { + private final IUserService userService; + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean add(CarryCapacityUserParam carryCapacityUserParam) { + if(StringUtils.isBlank(carryCapacityUserParam.getUserName())){ + throw new BusinessException("用户称不能为空"); + } + checkName(carryCapacityUserParam,false); + CarryCapacityUserPO carryCapacityUser = new CarryCapacityUserPO(); + BeanUtils.copyProperties(carryCapacityUserParam, carryCapacityUser); + carryCapacityUser.setStatus(1); + return this.save(carryCapacityUser); + } + /** + * 检查名称是否已存在 + * + * @return 结果 + */ + private void checkName(CarryCapacityUserParam carryCapacityUserParam, boolean isUpdate) { + if(carryCapacityUserParam.getUserName().length()>32){ + throw new BusinessException("超过最大长度"); + + } + CheckStringUtil.checkName(carryCapacityUserParam.getUserName()); + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + //条件组合:where state = 1 and name = ? + lambdaQueryWrapper.eq(CarryCapacityUserPO::getStatus, DataStateEnum.ENABLE.getCode()).eq(CarryCapacityUserPO::getUserName, carryCapacityUserParam.getUserName()); + + //and id <> ? + if (isUpdate) { + if (carryCapacityUserParam instanceof CarryCapacityUserParam.CarryCapacityUserUpdateParam) { + lambdaQueryWrapper.ne(CarryCapacityUserPO::getUserId, ((CarryCapacityUserParam.CarryCapacityUserUpdateParam) carryCapacityUserParam).getUserId()); + } + } + + //若存在条件数据抛出异常 + int count = this.getBaseMapper().selectCount(lambdaQueryWrapper); + if (count > 0) { + throw new BusinessException(CarryCapacityResponseEnum.USER_NAME_EXIST); + } + + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean updateUser(CarryCapacityUserParam.CarryCapacityUserUpdateParam userUpdateParam) { + if(StringUtils.isBlank(userUpdateParam.getUserName())){ + throw new BusinessException("用户称不能为空"); + } + checkName(userUpdateParam,true); + CarryCapacityUserPO carryCapacityUser = new CarryCapacityUserPO(); + BeanUtils.copyProperties(userUpdateParam, carryCapacityUser); + + + return this.updateById(carryCapacityUser); + } + + @Override + public IPage queyDetailUser(CarryCapacityUserParam.CarryCapacityUserPageParam pageParm) { + Page returnpage = new Page<> (pageParm.getPageNum ( ), pageParm.getPageSize ( )); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(CarryCapacityUserPO::getStatus,1) + .eq(StringUtils.isNotBlank(pageParm.getUserId()) ,CarryCapacityUserPO::getUserId,pageParm.getUserId()) + .eq(StringUtils.isNotBlank(pageParm.getVoltage()) ,CarryCapacityUserPO::getVoltage,pageParm.getVoltage()) + .eq(StringUtils.isNotBlank(pageParm.getUserType()) ,CarryCapacityUserPO::getUserType,pageParm.getUserType()) + .in(CollectionUtil.isNotEmpty(pageParm.getUserTypeList()) ,CarryCapacityUserPO::getUserType,pageParm.getUserTypeList()) + .between(StringUtils.isNotBlank(pageParm.getStartTime()) && StringUtils.isNotBlank(pageParm.getEndTime()) ,CarryCapacityUserPO::getCreateTime,pageParm.getStartTime()+" 00:00:00",pageParm.getEndTime()+" 23:59:59"). + orderByDesc(CarryCapacityUserPO::getCreateTime); + + IPage page = this.page (returnpage, queryWrapper); + page.getRecords().forEach(temp->{ + UserVO user = userService.getUserById(temp.getCreateBy()); + + temp.setCreateBy(Objects.isNull(user)? LogInfo.UNKNOWN_USER:user.getName()); + }); + return page; + } + + @Override + public CarryCapacityUserPO queyDetailUserById(String userId) { + return this.lambdaQuery().eq(CarryCapacityUserPO::getUserId,userId).one(); + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/CarryCapacityUtil.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/CarryCapacityUtil.java new file mode 100644 index 0000000..7df40ba --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/CarryCapacityUtil.java @@ -0,0 +1,323 @@ +package com.njcn.product.carrycapacity.util; + +import com.njcn.common.pojo.exception.BusinessException; +import org.apache.commons.math3.linear.DecompositionSolver; +import org.apache.commons.math3.linear.LUDecomposition; +import org.apache.commons.math3.linear.MatrixUtils; +import org.apache.commons.math3.linear.RealMatrix; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +public class CarryCapacityUtil { + + private static final String DATA_CSV = "C:\\njcn\\pqs\\pqs-advance\\advance-boot\\src\\main\\resources\\test.csv"; + private static final int MAX_PRO_DATA_NUM = 5000; + private static final int MAX_DATA_COL_NUM = 9; + private static double[][] arr = new double[MAX_PRO_DATA_NUM][MAX_DATA_COL_NUM]; + + public static void main(String[] args) { + double[] data_u = new double[MAX_PRO_DATA_NUM]; + double[] data_p = new double[MAX_PRO_DATA_NUM]; + double[] data_q = new double[MAX_PRO_DATA_NUM]; + + int data_num = parseCSV(DATA_CSV, data_u, data_p, data_q); + System.out.println("data_num: " + data_num); + + double[] res = new double[3]; +// cznlpgDataTrain(data_u, data_p, data_q, data_num, res); + System.out.println("C = " + res[0] + " a = " + res[1] + " b = " + res[2]); + } + + private static int parseCSV(String path, double[] data_u, double[] data_p, double[] data_q) { + int line = 0; + try (BufferedReader br = new BufferedReader(new FileReader(path))) { + String lines; + while ((lines = br.readLine()) != null) { + String[] tokens = lines.split(","); + for (int i = 0; i < tokens.length; i++) { + arr[line][i] = Double.parseDouble(tokens[i]); + } + + System.out.println("line " + line + ": "); + for (int i = 0; i < tokens.length; i++) { + System.out.println("arr[" + line + "][" + i + "]=" + arr[line][i]); + } + + data_u[line] = arr[line][0]; + data_p[line] = arr[line][1]; + data_q[line] = arr[line][2]; + + line++; + } + } catch (IOException e) { + e.printStackTrace(); + } + return line; + } + /* + * 模型训练 + * */ + public static void cznlpgDataTrain(List u, List p, List q, int num, Double[] outRes) { + if (num > MAX_PRO_DATA_NUM) { + return; + } + + RealMatrix matPQ = MatrixUtils.createRealMatrix(num, 3); + RealMatrix matU = MatrixUtils.createRealMatrix(num, 1); + RealMatrix matW = MatrixUtils.createRealMatrix(3, 1); + + // Matrix assignment + for (int i = 0; i < num; i++) { + matPQ.setEntry(i, 0, 1); + matPQ.setEntry(i, 1, p.get(i)); + matPQ.setEntry(i, 2, q.get(i)); + + matU.setEntry(i, 0, u.get(i)); + } + +// System.out.println("matPQ="); + printMatrix(matPQ); +// System.out.println("matPQ transpose="); + printMatrix(matPQ.transpose()); + + // w = inv(PQ1'*PQ1)*PQ1'*U + // U = 224.5133 - 2.3041e-5 * P - 1.1900e-4 * Q + RealMatrix matPQT = matPQ.transpose(); + RealMatrix matInverse = inverseMatrix(matPQT.multiply(matPQ)); + matW = matInverse.multiply(matPQT).multiply(matU); + + outRes[0] = matW.getEntry(0, 0); + outRes[1] = matW.getEntry(1, 0); + outRes[2] = matW.getEntry(2, 0); + } + + private static void printMatrix(RealMatrix matrix) { + System.out.println(matrix); + } + + + //矩阵求逆 + + public static RealMatrix inverseMatrix(RealMatrix matrix) { + try { + LUDecomposition LUDe = new LUDecomposition(matrix); + DecompositionSolver solver = LUDe.getSolver(); + RealMatrix result = solver.getInverse(); + return result; + }catch (Exception e){ + System.out.println("数据存在问题无法进行矩阵求逆"); + throw new BusinessException("数据存在问题无法进行矩阵求逆"); + } + + + } + + /** + * @Description: 负载率约束指标计算P_βmin和Q_βmin分别为近一周的配变每日9时~15时段的负载率数据中概率95%小值所对应时刻的有功功率和无功功率值; + * S_T为配变额定容量;S_pv为拟接入光伏容量;k为修正系数 ,取值可参照如下。 + * 台区日照条件 k + * 光照强度大于1250kWh/m^2 0.8~0.9 + * 光照强度小于1250kWh/m^2 0.75~0.8 + * 海南 0.8 + * @Param: + * @return: double Loadrate + * @Author: clam + * @Date: 2024/1/26 + */ + public static double calculateB(double P_βmin, double Q_βmin, double k, double S_T, double S_pv, double P_pv) { + double term1 = Math.pow(P_βmin - k * S_T, 2); + double term2 = Math.pow(Q_βmin, 2); + double numerator = Math.sqrt(term1 + term2); + if (P_βmin > P_pv) { + return numerator / S_pv; + } else { + return -numerator / S_pv; + } + + } + /** + * @Description: calculatePF_T 功率因数指标计算 + * @Param: + * @return: double + * @Author: clam + * @Date: 2024/2/20 + */ + public static double calculatePF_T(double P_βmin, double Q_βmin, double k, double S_pv) { + double term1 = Math.pow(P_βmin - k * S_pv, 2); + double term2 = Math.pow(Q_βmin, 2); + double v = P_βmin - k * S_pv; + double numerator = Math.sqrt(term1 + term2); + + return v/numerator; + + } + + /** + * @Description: 总结: + * p_min和 q_min能够根据测点数据获取得到; + * S_pv为拟接入光伏容量,此部分需要现场选取好台区后获取。 + * k为修正系数,徐工提供海南k系数,是否需要考虑不同季节台区日照系数。 + * C、a、b需要用模型计算,是此算法中难点。 + * 结论:【拟接入光伏容量】为入参;【A/B/C相有功功率】和【A/B/C相无功功率值】95%小值从A/B/C相历史数据中计算得出; 为枚举参数;能够计算三相配变首端电压 、 、 ,从而得出U 。 + * 380v -U=C-a(p_min -k*S_pv/3)-b*q_min + * 220v -U=C-a(p_min -k*S_pv)-b*q_min + *(后续咨询只分单项三项,目前数据都是三项) + * @Param: + * @return: double + * @Author: clam + * @Date: 2024/2/2 + */ + public static double calculateU(double C, double a, double b, double p_min, double K, double q_min,double S_pv, double voltage) { + +// if (voltage == 220) { +// return C-a*(p_min-K*S_pv)-b*q_min; +// } else if (voltage == 380) { + return C-a*(p_min-K*S_pv/3)-b*q_min; +// } else { +// return 0; +// } + + } + + /** + * I_(stock,h)为台区一周内的h次谐波电流95%概率大值,I_"inv" ^h%为光伏逆变器第h次的典型谐波电流含有率; + * S_pv为拟接入光伏容量,此部分需要现场选取好台区后获取。 + * k为修正系数,徐工提供海南k系数,是否需要考虑不同季节台区日照系数。 + * 结论:【电压等级】为入参;I_(stock,h)为台区一周内的h次谐波电流95%概率大值,I_"inv" ^h%为光伏逆变器第h次的典型谐波电流含有率, + * 为枚举参数;k为枚举参数;能够计算各次的谐波电流幅值 、 、 ,从而得出 。 + * (后续咨询只分单项三项,目前数据都是三项) + */ + public static double calculateITm(double I_cp95, double k, double voltage, double S_pv, double K, double I_inv) { + double term1 = Math.pow(I_cp95, 2); + double term2 = 0, term3 = 0; +// if (voltage == 220) { +// term2 = Math.pow(k * S_pv * I_inv / 220, 2); +// term3 = K * I_cp95 * (k * S_pv * I_inv / 220); +// } else if (voltage == 380) { + term2 = Math.pow(k * S_pv * I_inv /(3 * 220) , 2); + term3 = K * I_cp95 * (k * S_pv * I_inv / (3 * 220)); +// } else { +// return 0; +// } + + double sumOfTerms = term1 + term2 + term3; + + return Math.sqrt(sumOfTerms); + } + + /** + * @Description: evaluateVoltageLevel 根据规则评估配变首端电压等级 + * @Param: + * @return: int + * @Author: clam + * @Date: 2024/1/30 + */ + public static int evaluateVoltageLevel(double voltage) { + if (voltage <= 235.4) { + return 1; // 安全 + } else if (voltage > 235.4 && voltage <= 253.0) { + return 2; // Ⅲ级预警 + } else if (voltage > 253.0 && voltage < 260.0) { + return 3; // Ⅱ级预警 + } else { + return 4; // Ⅰ级预警 + } + } + + /** + * @Description: evaluatePowerFactorLevel // 根据规则评估功率因数等级 + * @Param: + * @return: int + * @Author: clam + * @Date: 2024/1/30 + */ + public static int evaluatePowerFactorLevel(double powerFactor) { + if (powerFactor >= 0.9) { + return 1; // 安全 + } else if (powerFactor >= 0.85 && powerFactor < 0.9) { + return 2; // Ⅲ级预警 + } else if (powerFactor >= 0.8 && powerFactor < 0.85) { + return 3; // Ⅱ级预警 + } else { + return 4; // Ⅰ级预警 + } + } + + /** + * @Description: / 根据规则评估等效负载率等级 + * @Param: + * @return: int + * @Author: clam + * @Date: 2024/1/30 + */ + public static int evaluateEquivalentLoadRateLevel(double equivalentLoadRate) { + if (equivalentLoadRate >= 0.0) { + return 1; // 安全 + } else if (equivalentLoadRate >= -40.0 && equivalentLoadRate < 0.0) { + return 2; // Ⅲ级预警 + } else if (equivalentLoadRate >= -80.0 && equivalentLoadRate < -40.0) { + return 3; // Ⅱ级预警 + } else { + return 4; // Ⅰ级预警 + } + } + + /** + * @Description: 判断O:各项指标是否均为“安全” 安全接入 + * 判断2: 至多2项指标达到“III级预警”,其余指标均为“安全” 3接入预警 + * @: 超过2项指标达到“III级预警”且无“II级预警”及以上的指标:或至多1项指标达到“I 级预警且其余指标均为“安全” 2接入预警 + * 判断@: 至多2项指标达到“II 级预警”且其余指标均为“安全”: 或至多1项指标达到“II级预警”且其余指标存在“III级预警” 1级接入预警 + * 否则 限制接入 + * @Param: + * @return: + * @Author: clam + * @Date: 2024/1/30 + */ + public static int evaluateG(List indicators) { + long count1 = indicators.stream().filter(i -> i == 1).count(); + long count2 = indicators.stream().filter(i -> i == 2).count(); + long count3 = indicators.stream().filter(i -> i == 3).count(); + if (count1 == 4) { + return 1; + } else if (count2 <= 2 && count2 + count1 == 4) { + return 2; + } else if ((count2 >= 2 && count2 + count1 == 4) || (count3 == 1 && count1 == 3)) { + return 3; + } else if ((count3 <= 2 && count1 + count3 == 4) || (count3 == 1 && count2 >= 1 && count1 + count2 == 3)) { + return 4; + } else { + return 5; + } + } + + /** + * 计算一组数据的最大95概率值,最小95概率值 入参一组double集合,一个flag表示计算类型 返回double + * + * @param data + * @param type 0 最大95概率值 1 最小95概率值 + * @return + */ + public static double calculatePercentile(List data, Integer type) { + // 对数组进行排序 + // 正序排序 + Collections.sort(data); + int index =0; + if (type == 0) { + // 计算最大95%概率值的索引 + index =(int) Math.ceil(0.95 * data.size()) ; + } else if (type == 1) { + // 计算最小95%概率值的索引 + index = (int) Math.ceil(0.05 * data.size()) - 1; + } + + // 根据计算类型返回相应的值 + return data.get(index); + } + + +} + diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/CheckStringUtil.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/CheckStringUtil.java new file mode 100644 index 0000000..e8ba768 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/CheckStringUtil.java @@ -0,0 +1,29 @@ +package com.njcn.product.carrycapacity.util; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.web.constant.ValidMessage; + +import java.util.regex.Pattern; + +/** + * Description: + * Date: 2024/12/10 14:51【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public class CheckStringUtil { + public static void checkName(String name) { + String SPECIALCHARACTER ="[<>%'%;()&+/\\\\-\\\\\\\\_|@*?#$!,.]|html"; + Pattern pattern = Pattern.compile(SPECIALCHARACTER); + if(pattern.matcher(name).find()){ + throw new BusinessException(ValidMessage.NAME_SPECIAL_REGEX); + } + } + +// public static void main(String[] args) { +// checkName("100迈岭站2djvjva13ad"); +// } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelDefaultListener.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelDefaultListener.java new file mode 100644 index 0000000..34a0858 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelDefaultListener.java @@ -0,0 +1,88 @@ +package com.njcn.product.carrycapacity.util; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.exception.ExcelDataConvertException; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Description: + * Date: 2024/3/15 16:02【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Slf4j +public abstract class EasyExcelDefaultListener extends AnalysisEventListener { + + /** + * 批处理阈值 + */ + private static final int BATCH_COUNT = 20; + + /** + * 用来存放待处理的数据 + */ + @Getter + private List list = new ArrayList<>(BATCH_COUNT); + + /** + * 读取excel数据前操作
+ * + * 只有不读取表头数据时才会触发此方法) + */ + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + log.info("======================================================"); + log.info("======================================================"); + } + + /** + * 读取excel数据操作 + * @param obj + * @param context + */ + @Override + public void invoke(T obj, AnalysisContext context) { + list.add(obj); + + if (list.size() >= BATCH_COUNT) { + //将数据保存到数据库中 + fun(list); + list.clear(); + } + } + + /** + * 具体业务 + */ + protected abstract void fun(List list); + + /** + * 读取完excel数据后的操作 + */ + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + if (list.size() > 0) { + fun(list); + } + } + + /** + * 在读取excel异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。 + */ + @Override + public void onException(Exception exception, AnalysisContext context) { + log.error("解析失败,但是继续解析下一行:{}", exception.getMessage()); + if (exception instanceof ExcelDataConvertException) { + ExcelDataConvertException ex = (ExcelDataConvertException) exception; + log.error("第{}行,第{}列解析异常,数据为:{}", ex.getRowIndex(), ex.getColumnIndex(), ex.getCellData()); + } + } +} + diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelUtil.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelUtil.java new file mode 100644 index 0000000..1d77ac8 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelUtil.java @@ -0,0 +1,428 @@ +package com.njcn.product.carrycapacity.util; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.EasyExcelFactory; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.write.handler.WriteHandler; +import lombok.SneakyThrows; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Description: + * Date: 2024/3/15 15:58【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public class EasyExcelUtil { + + //====================================================无JAVA模型读取excel数据=============================================================== + + /** + * 同步无模型读(默认读取sheet0,从第2行开始读) + * @param file excel文件的绝对路径 + */ + public static List> syncRead(String file) { + return EasyExcelFactory.read(file).sheet().doReadSync(); + } + + /** + * 同步无模型读(自定义读取sheetX,从第2行开始读) + * @param file excel文件的绝对路径 + * @param sheetNum sheet页号,从0开始 + */ + public static List> syncRead(String file, Integer sheetNum) { + return EasyExcelFactory.read(file).sheet(sheetNum).doReadSync(); + } + + /** + * 同步无模型读(指定sheet和表头占的行数) + * @param file + * @param sheetNum sheet页号,从0开始 + * @param headNum 表头占的行数,从0开始(如果要连表头一起读出来则传0) + */ + public static List> syncRead(String file, Integer sheetNum, Integer headNum) { + return EasyExcelFactory.read(file).sheet(sheetNum).headRowNumber(headNum).doReadSync(); + } + + /** + * 同步无模型读(指定sheet和表头占的行数) + * @param inputStream + * @param sheetNum sheet页号,从0开始 + * @param headNum 表头占的行数,从0开始(如果要连表头一起读出来则传0) + */ + public static List> syncRead(InputStream inputStream, Integer sheetNum, Integer headNum) { + return EasyExcelFactory.read(inputStream).sheet(sheetNum).headRowNumber(headNum).doReadSync(); + } + + /** + * 同步无模型读(指定sheet和表头占的行数) + * @param file + * @param sheetNum sheet页号,从0开始 + * @param headNum 表头占的行数,从0开始(如果要连表头一起读出来则传0) + */ + public static List> syncRead(File file, Integer sheetNum, Integer headNum) { + return EasyExcelFactory.read(file).sheet(sheetNum).headRowNumber(headNum).doReadSync(); + } + //====================================================无JAVA模型读取excel数据=============================================================== + + //====================================================将excel数据同步到JAVA模型属性里=============================================================== + + /** + * 同步按模型读(默认读取sheet0,不读取表头,从第2行开始读) + * @param file + * @param clazz 模型的类类型(excel数据会按该类型转换成对象) + */ + public static List syncReadModel(String file, Class clazz) { + return EasyExcelFactory.read(file).sheet().head(clazz).doReadSync(); + } + + /** + * 同步按模型读(默认表头占一行,不读取表头,从第2行开始读) + * @param file + * @param clazz 模型的类类型(excel数据会按该类型转换成对象) + * @param sheetNum sheet页号,从0开始 + */ + public static List syncReadModel(String file, Class clazz, Integer sheetNum, Integer headNum) { + return EasyExcelFactory.read(file).sheet(sheetNum).headRowNumber(headNum).head(clazz).doReadSync(); + } + + /** + * 同步按模型读(指定sheet,不读取表头) + * @param inputStream + * @param clazz 模型的类类型(excel数据会按该类型转换成对象) + * @param sheetNum sheet页号,从0开始 + */ + public static List syncReadModel(InputStream inputStream, Class clazz, Integer sheetNum, Integer headNum) { + return EasyExcelFactory.read(inputStream).sheet(sheetNum).headRowNumber(headNum).head(clazz).doReadSync(); + } + + /** + * 同步按模型读(指定sheet,不读取表头) + * @param file + * @param clazz 模型的类类型(excel数据会按该类型转换成对象) + * @param sheetNum sheet页号,从0开始 + */ + public static List syncReadModel(File file, Class clazz, Integer sheetNum, Integer headNum) { + return EasyExcelFactory.read(file).sheet(sheetNum).headRowNumber(headNum).head(clazz).doReadSync(); + } + //====================================================将excel数据同步到JAVA模型属性里=============================================================== + + //====================================================异步读取excel数据=============================================================== + + /** + * 异步无模型读(默认读取sheet0,不读取表头,从第2行开始读) + * @param listener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等 + * @param file 表头占的行数,从0开始(如果要连表头一起读出来则传0) + */ + public static void asyncRead(String file, AnalysisEventListener listener) { + EasyExcelFactory.read(file, listener).sheet().doRead(); + } + + /** + * 异步无模型读(默认表头占一行,不读取表头,从第2行开始读) + * @param file 表头占的行数,从0开始(如果要连表头一起读出来则传0) + * @param listener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等 + * @param sheetNum sheet页号,从0开始 + */ + public static void asyncRead(String file, AnalysisEventListener listener, Integer sheetNum) { + EasyExcelFactory.read(file, listener).sheet(sheetNum).doRead(); + } + + /** + * 异步无模型读(指定sheet和表头占的行数) + * @param inputStream + * @param listener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等 + * @param sheetNum sheet页号,从0开始 + * @param headNum 表头占的行数,从0开始(如果要连表头一起读出来则传0) + */ + public static void asyncRead(InputStream inputStream, AnalysisEventListener listener, Integer sheetNum, Integer headNum) { + EasyExcelFactory.read(inputStream, listener).sheet(sheetNum).headRowNumber(headNum).doRead(); + } + + /** + * 异步无模型读(指定sheet和表头占的行数) + * @param file + * @param listener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等 + * @param sheetNum sheet页号,从0开始 + * @param headNum 表头占的行数,从0开始(如果要连表头一起读出来则传0) + */ + public static void asyncRead(File file, AnalysisEventListener listener, Integer sheetNum, Integer headNum) { + EasyExcelFactory.read(file, listener).sheet(sheetNum).headRowNumber(headNum).doRead(); + } + + /** + * 异步无模型读(指定sheet和表头占的行数) + * @param file + * @param listener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等 + * @param sheetNum sheet页号,从0开始 + * @param headNum 表头占的行数,从0开始(如果要连表头一起读出来则传0) + * @return + */ + public static void asyncRead(String file, AnalysisEventListener listener, Integer sheetNum, Integer headNum) { + EasyExcelFactory.read(file, listener).sheet(sheetNum).headRowNumber(headNum).doRead(); + } + //====================================================异步读取excel数据=============================================================== + + //====================================================将excel数据异步到JAVA模型属性里=============================================================== + /** + * 异步按模型读取(默认读取sheet0,不读取表头,从第2行开始读) + * @param file + * @param listener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等 + * @param clazz 模型的类类型(excel数据会按该类型转换成对象) + */ + public static void asyncReadModel(String file, AnalysisEventListener listener, Class clazz) { + EasyExcelFactory.read(file, clazz, listener).sheet().doRead(); + } + + /** + * 异步按模型读取(默认表头占一行,不读取表头,从第2行开始读) + * @param file + * @param listener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等 + * @param clazz 模型的类类型(excel数据会按该类型转换成对象) + * @param sheetNum sheet页号,从0开始 + */ + public static void asyncReadModel(String file, AnalysisEventListener listener, Class clazz, Integer sheetNum) { + EasyExcelFactory.read(file, clazz, listener).sheet(sheetNum).doRead(); + } + + /** + * 异步按模型读取 + * @param file + * @param listener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等 + * @param clazz 模型的类类型(excel数据会按该类型转换成对象) + * @param sheetNum sheet页号,从0开始 + */ + public static void asyncReadModel(File file, AnalysisEventListener listener, Class clazz, Integer sheetNum) { + EasyExcelFactory.read(file, clazz, listener).sheet(sheetNum).doRead(); + } + + /** + * 异步按模型读取 + * @param inputStream + * @param listener 监听器,在监听器中可以处理行数据LinkedHashMap,表头数据,异常处理等 + * @param clazz 模型的类类型(excel数据会按该类型转换成对象) + * @param sheetNum sheet页号,从0开始 + */ + public static void asyncReadModel(InputStream inputStream, AnalysisEventListener listener, Class clazz, Integer sheetNum) { + EasyExcelFactory.read(inputStream, clazz, listener).sheet(sheetNum).doRead(); + } + //====================================================将excel数据异步到JAVA模型属性里=============================================================== + + + //====================================================无JAVA模型写文件=============================================================== + /** + * 无模板写文件 + * @param file + * @param head 表头数据 + * @param data 表内容数据 + */ + public static void write(String file, List> head, List> data) { + EasyExcel.write(file).head(head).sheet().doWrite(data); + } + + /** + * 无模板写文件 + * @param file + * @param head 表头数据 + * @param data 表内容数据 + * @param sheetNum sheet页号,从0开始 + * @param sheetName sheet名称 + */ + public static void write(String file, List> head, List> data, Integer sheetNum, String sheetName) { + EasyExcel.write(file).head(head).sheet(sheetNum, sheetName).doWrite(data); + } + //====================================================无JAVA模型写文件=============================================================== + + //====================================================有Excel模板写文件=============================================================== + /** + * 根据excel模板文件写入文件,可以实现向已有文件中添加数据的功能 + * @param file + * @param template + * @param data + */ + public static void writeTemplate(String file, String template, List data) { + EasyExcel.write(file).withTemplate(template).sheet().doWrite(data); + } + + /** + * 根据excel模板文件写入文件 + * @param file + * @param template + * @param clazz + * @param data + */ + public static void writeTemplate(String file, String template, Class clazz, List data) { + EasyExcel.write(file, clazz).withTemplate(template).sheet().doWrite(data); + } + + + + //====================================================无模板写文件=============================================================== + + //====================================================有模板写文件=============================================================== + /** + * 按模板写文件 + * @param file + * @param clazz 表头模板 + * @param data 数据 + */ + public static void write(String file, Class clazz, List data) { + EasyExcel.write(file, clazz).sheet().doWrite(data); + } + + /** + * 按模板写文件 + * @param file + * @param clazz 表头模板 + * @param data 数据 + * @param sheetNum sheet页号,从0开始 + * @param sheetName sheet名称 + */ + public static void write(String file, Class clazz, List data, Integer sheetNum, String sheetName) { + EasyExcel.write(file, clazz).sheet(sheetNum, sheetName).doWrite(data); + } + + /** + * 按模板写文件 + * @param file + * @param clazz 表头模板 + * @param data 数据 + * @param writeHandler 自定义的处理器,比如设置table样式,设置超链接、单元格下拉框等等功能都可以通过这个实现(需要注册多个则自己通过链式去调用) + * @param sheetNum sheet页号,从0开始 + * @param sheetName sheet名称 + */ + public static void write(String file, Class clazz, List data, WriteHandler writeHandler, Integer sheetNum, String sheetName) { + EasyExcel.write(file, clazz).registerWriteHandler(writeHandler).sheet(sheetNum, sheetName).doWrite(data); + } + + /** + * 按模板写文件(包含某些字段) + * @param file + * @param clazz 表头模板 + * @param data 数据 + * @param includeCols 包含字段集合,根据字段名称显示 + * @param sheetNum sheet页号,从0开始 + * @param sheetName sheet名称 + */ + public static void writeInclude(String file, Class clazz, List data, Set includeCols, Integer sheetNum, String sheetName) { + EasyExcel.write(file, clazz).includeColumnFiledNames(includeCols).sheet(sheetNum, sheetName).doWrite(data); + } + + /** + * 按模板写文件(排除某些字段) + * @param file + * @param clazz 表头模板 + * @param data 数据 + * @param excludeCols 过滤排除的字段,根据字段名称过滤 + * @param sheetNum sheet页号,从0开始 + * @param sheetName sheet名称 + */ + public static void writeExclude(String file, Class clazz, List data, Set excludeCols, Integer sheetNum, String sheetName) { + EasyExcel.write(file, clazz).excludeColumnFiledNames(excludeCols).sheet(sheetNum, sheetName).doWrite(data); + } + + //------------------------------------------------------------------------------------------------ + /** + * 多个sheet页的数据链式写入 + * + * @param file + */ + public static EasyExcelWriteTool writeWithSheets(String file) { + return new EasyExcelWriteTool(file); + } + + /** + * 多个sheet页的数据链式写入 + * + * @param file + */ + public static EasyExcelWriteTool writeWithSheets(File file) { + return new EasyExcelWriteTool(file); + } + + /** + * 多个sheet页的数据链式写入 + * + * @param outputStream + */ + public static EasyExcelWriteTool writeWithSheets(OutputStream outputStream) { + return new EasyExcelWriteTool(outputStream); + } + + /** + * 多个sheet页的数据链式写入(失败了会返回一个有部分数据的Excel) + * + * @param response + * @param exportFileName 导出的文件名称 + */ + @SneakyThrows + public static EasyExcelWriteTool writeWithSheetsWeb(HttpServletResponse response, String exportFileName) throws IOException { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + + // 这里URLEncoder.encode可以防止中文乱码 + String fileName = URLEncoder.encode(exportFileName, "UTF-8"); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); + + return new EasyExcelWriteTool(response.getOutputStream()); + } + + public static void main(String[] args) { + List> maps = EasyExcelUtil.syncRead("C:\\Users\\无名\\Desktop\\excel\\工作表.xlsx"); + + System.out.println(maps); + List> maps1 = maps.subList(2, 51); + List> maps2 = maps.subList(51, 52); + List> maps3 = maps.subList(55, 104); + List> maps4 = maps.subList(104, 105); + List iMax = maps1.stream().map(temp -> { + double a = Double.valueOf(temp.get(5)); + double b = Double.valueOf(temp.get(10)); + double c = Double.valueOf(temp.get(15)); + double v = a > b ? a : b; + double max = v > c ? v : c; + return max; + + }).collect(Collectors.toList()); + Double iNeg = maps2.stream().map(temp -> { + double a = Double.valueOf(temp.get(5)); + double b = Double.valueOf(temp.get(10)); + double c = Double.valueOf(temp.get(15)); + double v = a > b ? a : b; + double max = v > c ? v : c; + return max; + + }).findFirst().get(); + + List uMax = maps3.stream().map(temp -> { + double a = Double.valueOf(temp.get(5)); + double b = Double.valueOf(temp.get(10)); + double c = Double.valueOf(temp.get(15)); + double v = a > b ? a : b; + double max = v > c ? v : c; + return max; + + }).collect(Collectors.toList()); + + Double unbalance = maps2.stream().map(temp -> { + double a = Double.valueOf(temp.get(5)); + double b = Double.valueOf(temp.get(10)); + double c = Double.valueOf(temp.get(15)); + double v = a > b ? a : b; + double max = v > c ? v : c; + return max; + + }).findFirst().get(); + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelWriteTool.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelWriteTool.java new file mode 100644 index 0000000..e746ce0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/EasyExcelWriteTool.java @@ -0,0 +1,68 @@ +package com.njcn.product.carrycapacity.util; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.metadata.WriteSheet; +import org.apache.poi.ss.formula.functions.T; + +import java.io.File; +import java.io.OutputStream; +import java.util.List; + +/** + * Description: + * Date: 2024/3/15 16:00【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public class EasyExcelWriteTool { + + private int sheetNum; + private ExcelWriter excelWriter; + + public EasyExcelWriteTool(OutputStream outputStream) { + excelWriter = EasyExcel.write(outputStream).build(); + } + + public EasyExcelWriteTool(File file) { + excelWriter = EasyExcel.write(file).build(); + } + + public EasyExcelWriteTool(String filePath) { + excelWriter = EasyExcel.write(filePath).build(); + } + + /** + * 链式模板表头写入 + * @param clazz 表头格式 + * @param data 数据 List 或者List> + * @return + */ + public EasyExcelWriteTool writeModel(Class clazz, List data, String sheetName) { + final WriteSheet writeSheet = EasyExcel.writerSheet(this.sheetNum++, sheetName).head(clazz).build(); + excelWriter.write(data, writeSheet); + return this; + } + + /** + * 链式自定义表头写入 + * @param head + * @param data 数据 List 或者List> + * @param sheetName + * @return + */ + public EasyExcelWriteTool write(List> head, List data, String sheetName) { + final WriteSheet writeSheet = EasyExcel.writerSheet(this.sheetNum++, sheetName).head(head).build(); + excelWriter.write(data, writeSheet); + return this; + } + + /** + * 使用此类结束后,一定要关闭流 + */ + public void finish() { + excelWriter.finish(); + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/FileUtils.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/FileUtils.java new file mode 100644 index 0000000..4b84de7 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/FileUtils.java @@ -0,0 +1,83 @@ +package com.njcn.product.carrycapacity.util; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +/** + * Description: + * Date: 2025/07/09 下午 3:14【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Component +public class FileUtils { + @Value("${file.upload-dir}") + private String baseDir; // 从配置文件中注入 + /** + * 上传文件到本地目录 + * @param file Spring MultipartFile 对象 + * @return 返回存储的文件相对路径(含文件名) + * @throws IOException 当文件操作失败时抛出 + */ + public String uploadFile(MultipartFile file) throws IOException { + // 确保目录存在 + Path dirPath = Paths.get(baseDir); + if (!Files.exists(dirPath)) { + Files.createDirectories(dirPath); + } + + // 生成唯一文件名(保留原始扩展名) + String originalName = file.getOriginalFilename(); + String extension = originalName.substring(originalName.lastIndexOf(".")); + String fileName = UUID.randomUUID() + extension; + + // 构建目标路径 + Path targetPath = Paths.get(baseDir, fileName); + + // 保存文件 + try (InputStream inputStream = file.getInputStream()) { + Files.copy(inputStream, targetPath); + } + + return targetPath.toString(); + } + + /** + * 根据本地路径读取文件流 + * @param filePath 文件的绝对路径 + * @return 文件输入流(需调用方关闭) + * @throws FileNotFoundException 当文件不存在时抛出 + */ + public InputStream getFileStream(String filePath) throws FileNotFoundException { + File file = new File(filePath); + if (!file.exists() || !file.isFile()) { + throw new FileNotFoundException("文件不存在: " + filePath); + } + return new FileInputStream(file); + } + + /** + * 安全读取文件流(自动关闭资源) + * @param filePath 文件的绝对路径 + * @param consumer 使用流的回调接口 + * @throws IOException 当文件操作失败时抛出 + */ + public void consumeFileStream(String filePath, InputStreamConsumer consumer) throws IOException { + try (InputStream is = getFileStream(filePath)) { + consumer.accept(is); + } + } + + @FunctionalInterface + public interface InputStreamConsumer { + void accept(InputStream stream) throws IOException; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/PubUtils.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/PubUtils.java new file mode 100644 index 0000000..c0b79fd --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/PubUtils.java @@ -0,0 +1,554 @@ +package com.njcn.product.carrycapacity.util; + +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.type.TypeFactory; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.lang.Integer.parseInt; + + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年04月12日 14:21 + */ +public class PubUtils { + + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private static final String DATE_TIME = "yyyy-MM-dd HH:mm:ss"; + + private static final String DATE = "yyyy-MM-dd"; + + private static final String TIME = "HH:mm:ss"; + + + /** + * 生成随机码,包含字母。--> 大写 + * + * @param length 随机码长度 + */ + public static String randomCode(int length) { + return RandomUtil.randomString(length).toUpperCase(Locale.ENGLISH); + } + + + /**** + * ***** ***** 验证IP是否属于某个IP段 ipSection IP段(以'-'分隔) ip 所验证的IP号码 ***** ***** + **/ + public static boolean ipExistsInRange(String ip, String ipSection) { + ipSection = ipSection.trim(); + ip = ip.trim(); + int idx = ipSection.indexOf('-'); + String beginIp = ipSection.substring(0, idx); + String endIp = ipSection.substring(idx + 1); + return getIp2long(beginIp) <= getIp2long(ip) && getIp2long(ip) <= getIp2long(endIp); + } + + private static long getIp2long(String ip) { + ip = ip.trim(); + String[] ips = ip.split("\\."); + long ip2long = 0L; + for (int i = 0; i < 4; ++i) { + ip2long = ip2long << 8 | parseInt(ips[i]); + } + return ip2long; + } + + /** + * 获取当前时间 + * + * @author cdf + * @date 2021/7/26 + */ + public static String getNow() { + DateFormat bf = new SimpleDateFormat("yyyyMMddHHmmss"); + return bf.format(new Date()); + } + + /** + * 毫秒转时间 ms:需要转换的毫秒时间 + */ + public static Date ms2Date(Long ms) { + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(ms); + return c.getTime(); + } + + /** + * 日期转字符串函数 date:需要转换的日期 strFormat:转换的格式(yyyy-MM-dd HH:mm:ss) + */ + public static String date2String(Date date, String strFormat){ + SimpleDateFormat format = new SimpleDateFormat(strFormat); + + return format.format(date); + } + + /** + * 获取当前web的IP + */ + public static String getLocalIp() { + String host; + try { + host = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + e.printStackTrace(); + host = "127.0.0.1"; + } + return host; + } + + /** + * 将JSON转为实体对象 + * + * @param jsonStr json + * @param targetType 对象类型 + * @param 对象 + */ + public static T json2obj(String jsonStr, Type targetType) { + try { + JavaType javaType = TypeFactory.defaultInstance().constructType(targetType); + MAPPER.registerModule(new JavaTimeModule()); + MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + return MAPPER.readValue(jsonStr, javaType); + } catch (IOException e) { + throw new IllegalArgumentException("将JSON转换为对象时发生错误:" + jsonStr, e); + } + } + + /** + * 将实体对象转为JSON + * + * @param object 实体对象 + */ + public static String obj2json(Object object) { + try { + MAPPER.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + return MAPPER.writeValueAsString(object); + } catch (IOException e) { + throw new IllegalArgumentException("将实体对象转为JSON时发生错误:" + object, e); + } + } + + + /** + * 判断一个数字是否在区间内 + * + * @param current 待判断数字 + * @param min 最小值 + * @param max 最大值 + */ + public static boolean rangeInDefined(int current, int min, int max) { + return Math.max(min, current) == Math.min(current, max); + } + + /** + * 将起始日期字符串 yyyy-MM-dd 转为 yyyy-MM-dd HH:mm:ss的LocalDateTime + */ + public static LocalDateTime beginTimeToLocalDateTime(String beginTime) { + beginTime = beginTime + StrUtil.SPACE + "00:00:00"; + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME); + return LocalDateTime.parse(beginTime, dateTimeFormatter); + } + + /** + * 将截止日期字符串 yyyy-MM-dd 转为 yyyy-MM-dd HH:mm:ss的LocalDateTime + */ + public static LocalDateTime endTimeToLocalDateTime(String endTime) { + endTime = endTime + StrUtil.SPACE + "23:59:59"; + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME); + return LocalDateTime.parse(endTime, dateTimeFormatter); + } + + /** + * 将字符串日期转为LocalDate日期(只用于日期转换) + */ + public static LocalDate localDateFormat(String time) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE); + return LocalDate.parse(time, dateTimeFormatter); + } + + public static LocalDateTime localDateTimeFormat(String time) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME); + return LocalDateTime.parse(time, dateTimeFormatter); + } + + /** + * 校验时间格式 + */ + public static boolean checkDateTime(String time) { + if(StrUtil.isBlank(time)){ + throw new BusinessException(CommonResponseEnum.TIME_ERROR); + } + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_TIME); + + try { + simpleDateFormat.parse(time); + } catch (Exception e) { + throw new BusinessException(CommonResponseEnum.TIME_ERROR); + } + + return true; + } + + /** + * 校验字符串起始时间和结束时间并返回时间格式时间 + * @author cdf + * @date 2023/8/10 + */ + public static List checkLocalDate(String startTime,String endTime) { + List resultList = new ArrayList<>(); + if(StrUtil.isBlank(startTime) || StrUtil.isBlank(endTime)){ + throw new BusinessException(CommonResponseEnum.TIME_ERROR); + } + try { + startTime = startTime+StrUtil.SPACE+"00:00:00"; + endTime = endTime+StrUtil.SPACE+"23:59:59"; + LocalDateTime start = LocalDateTime.parse(startTime,DateTimeFormatter.ofPattern(DATE_TIME)); + LocalDateTime end = LocalDateTime.parse(endTime,DateTimeFormatter.ofPattern(DATE_TIME)); + resultList.add(start); + resultList.add(end); + } catch (Exception e) { + throw new BusinessException(CommonResponseEnum.TIME_ERROR); + } + return resultList; + } + + + + + /** + * 用于获取对象中,前缀一样,后缀为2~50的属性值 + * + * @param object 待操作对象 + * @param methodPrefix 方法前缀 + * @param number 方法后缀 + * @return 对象属性值 + */ + public static Float getValueByMethod(Object object, String methodPrefix, Integer number) { + try { + Method method = object.getClass().getMethod(methodPrefix + number); + return (Float) method.invoke(object); + } catch (Exception e) { + throw new BusinessException(CommonResponseEnum.REFLECT_METHOD_EXCEPTION); + } + } + + + /** + * 用于获取对象中,前缀一样,后缀为2~50的属性值 + * + * @param object 待操作对象 + * @param methodPrefix 方法前缀 + * @param number 方法后缀 + * @return 对象属性值 + */ + public static Double getValueByMethodDouble(Object object, String methodPrefix, Integer number) { + try { + Method method = object.getClass().getMethod(methodPrefix + number); + return (Double) method.invoke(object); + } catch (Exception e) { + throw new BusinessException(CommonResponseEnum.REFLECT_METHOD_EXCEPTION); + } + } + + + public static List getStartTimeEndTime(String beginDate, String endDate) throws Exception { + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Calendar cal = Calendar.getInstance(); + cal.setTime(sdf.parse(beginDate)); + List startTimeEndTime = null; + for (long d = cal.getTimeInMillis(); d <= sdf.parse(endDate).getTime(); d = getDplaus(cal)) { + startTimeEndTime.add(sdf.format(d)); + } + return startTimeEndTime; + } + + public static long getDplaus(Calendar c) { + c.set(Calendar.DAY_OF_MONTH, c.get(Calendar.DAY_OF_MONTH) + 1); + return c.getTimeInMillis(); + } + + public static String comFlag(Integer comFlag) { + switch (comFlag) { + case 0: + return "中断"; + case 1: + return "正常"; + default: + return ""; + } + } + + public static String runFlag(Integer runFlag) { + switch (runFlag) { + case 0: + return "投运"; + case 1: + return "热备用"; + case 2: + return "停运"; + default: + return ""; + } + } + + //监测点运行状态(0:投运;1:检修;2:停运;3:调试;4:退运) + public static String lineRunFlag(Integer runFlag) { + switch (runFlag) { + case 0: + return "投运"; + case 1: + return "检修"; + case 2: + return "停运"; + case 3: + return "调试"; + case 4: + return "退运"; + default: + return ""; + } + } + + public static Integer getRunFlag(String runFlag) { + switch (runFlag) { + case "投运": + return 0; + case "热备用": + return 1; + case "停运": + return 2; + default: + return -1; + } + } + public static Double getDefectSeverity(String defectSeverity) { + switch (defectSeverity) { + case "轻缺陷": + return 0.02; + case "较重缺陷": + return 0.12; + case "严重缺陷": + return 0.42; + default: + return 0.00; + } + } + + public static String ptType(Integer ptType) { + switch (ptType) { + case 0: + return "星型接线"; + case 1: + return "三角型接线"; + case 2: + return "开口三角型接线"; + default: + return ""; + } + } + + public static Integer ptTypeName(String ptType) { + switch (ptType) { + case "星型接线": + return 0; + case "三角型接线": + return 1; + case "开口三角型接线": + return 2; + default: + return -1; + } + } + + /** + * 将当前时间的秒数置为0 + * + * @param date 时间 + */ + public static Date getSecondsAsZero(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.SECOND, 0); + return calendar.getTime(); + } + + /** + * 根据起始时间和截止时间返回yyyy-MM-dd的日期, + * + * @param startTime 起始时间 + * @param endTime 截止时间 + */ + public static List getTimes(Date startTime, Date endTime) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + List result = new ArrayList<>(); + Calendar start = Calendar.getInstance(); + start.setTime(startTime); + Calendar end = Calendar.getInstance(); + end.setTime(endTime); + end.set(end.get(Calendar.YEAR), end.get(Calendar.MONTH), end.get(Calendar.DAY_OF_MONTH), 0, 0, 0); + long interval = end.getTimeInMillis() - start.getTimeInMillis(); + result.add(sdf.format(start.getTime())); + if (interval > 0) { + int days = (int) (interval / 86400000); + for (int i = 0; i < days; i++) { + start.add(Calendar.DAY_OF_MONTH, 1); + result.add(sdf.format(start.getTime())); + } + } + return result; + } + + /*** + * 将instant转为date 处理8小时误差 + * @author hongawen + * @date 2023/7/20 15:58 + * @param instant 日期 + * @return Instant + */ + public static Date instantToDate(Instant instant){ + return Date.from(instant.minusMillis(TimeUnit.HOURS.toMillis(8))); + } + + /*** + * 将date转为instant 处理8小时误差 + * @author hongawen + * @date 2023/7/20 15:58 + * @param date 日期 + * @return Instant + */ + public static Instant dateToInstant(Date date){ + return date.toInstant().plusMillis(TimeUnit.HOURS.toMillis(8)); + } + + + /** + * 根据参数返回float的四舍五入值 + * + * @param i 保留的位数 + * @param value float原值 + */ + public static Float floatRound(int i, float value) { + BigDecimal bp = new BigDecimal(value); + return bp.setScale(i, RoundingMode.HALF_UP).floatValue(); + } + + /** + * 根据参数返回double的四舍五入值 + * + * @param i 保留的位数 + * @param value double原值 + */ + public static double doubleRound(int i, double value) { + BigDecimal bp = new BigDecimal(value); + return bp.setScale(i, RoundingMode.HALF_UP).doubleValue(); + } + + //*****************************************xuyang添加,用于App******************************************************** + /** + * 正则表达式字符串 + * 要匹配的字符串 + * + * @return 如果str 符合 regex的正则表达式格式,返回true, 否则返回 false; + */ + public static boolean match(String regex, String str) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(str); + return matcher.matches(); + } + + /** + * 生成随机推荐码 + */ + public static String getCode(Integer number){ + final String BASIC = "123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"; + char[] basicArray = BASIC.toCharArray(); + Random random = new Random(); + char[] result = new char[number]; + for (int i = 0; i < result.length; i++) { + int index = random.nextInt(100) % (basicArray.length); + result[i] = basicArray[index]; + } + return new String(result); + } + + /** + * 将字节数组转成Float数组 + * @param bytes + * @return + */ + public static List byteArrayToFloatList(byte[] bytes){ + List d = new ArrayList<>(bytes.length/8); + byte[] doubleBuffer = new byte[4]; + for(int j = 0; j < bytes.length; j += 4) { + System.arraycopy(bytes, j, doubleBuffer, 0, doubleBuffer.length); + d.add(bytes2Float(doubleBuffer)); + } + return d; + } + + public static float bytes2Float(byte[] arr) { + int accum = 0; + accum = accum|(arr[0] & 0xff); + accum = accum|(arr[1] & 0xff) << 8; + accum = accum|(arr[2] & 0xff) << 16; + accum = accum|(arr[3] & 0xff) << 24; + return Float.intBitsToFloat(accum); + } + + /** + * 将字节数组转成Double数组 + * @param arr + * @return + */ + public static List byteArrayToDoubleList(byte[] arr){ + List d = new ArrayList<>(arr.length/8); + byte[] doubleBuffer = new byte[8]; + for(int j = 0; j < arr.length; j += 8) { + System.arraycopy(arr, j, doubleBuffer, 0, doubleBuffer.length); + d.add(bytes2Double(doubleBuffer)); + } + return d; + } + + /** + * 将byte转换成double + * @param arr + * @return + */ + public static double bytes2Double(byte[] arr) { + long value = 0; + for (int i = 0; i < 8; i++) { + value |= ((long) (arr[i] & 0xff)) << (8 * i); + } + return Double.longBitsToDouble(value); + } + //***************************************************添加结束******************************************************** +} diff --git a/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/Utils.java b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/Utils.java new file mode 100644 index 0000000..53d1c59 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/carrycapacity/util/Utils.java @@ -0,0 +1,151 @@ +package com.njcn.product.carrycapacity.util; + +/** + * @Author: Sunwei 【sunW2016@163.com】 + * @Description: + * @Date: Create in 22:28 2018/3/5 + * @Modified By: + * @author njcn + */ + +import cn.hutool.core.collection.CollectionUtil; + +import java.lang.reflect.Field; +import java.time.*; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/***************************************************************** + * 字符串转基础类型,可能转换不成功,封装该方法 + * 第一个参数为需要转换的字符串 + * 第二个参数为null时,直接抛出异常,为数值则传入一个默认值 +*****************************************************************/ +public class Utils { + public static int getIntValue(String s,Integer integer) { + try { + integer = Integer.parseInt(s); + } catch (Exception e) { + if (null == integer) { + throw e; + } + } + + return integer.intValue(); + } + + /** + * String对象转float + * + * @param f + * @return + */ + public static float getFloatValue(String s,Float f) { + try { + f = Float.parseFloat(s); + } catch (Exception e) { + if (null == f) { + throw e; + } + } + + return f.floatValue(); + } + + /** + * String对象转double + * + * @param d + * @return + */ + public static double getDoubleValue(String s,Double d) { + try { + d = Double.parseDouble(s); + } catch (Exception e) { + if (null == d) { + throw e; + } + } + + return d.doubleValue(); + } + + /** + * int转String对象 + */ + public static String int2String(int iValue) { + return Integer.toString(iValue); + } + + /** + * float转String + */ + public static String float2String(float fValue) { + return Float.toString(fValue); + } + + /** + * 按指定大小,分隔集合,将集合按规定个数分为n个部分 + * @author cdf + * @date 2021/10/26 + */ + public static List> splitList(List list, int len){ + if(CollectionUtil.isEmpty(list) || len<1){ + return null; + } + List> result = new ArrayList<>(); + int size = list.size(); + int count = (size+len-1)/1000; + for(int i=0;i subList= list.subList(i*len,((i+1)*len>size?size:len*(i+1))); + result.add(subList); + } + return result; + } + + // 辅助方法:检查时间是否在指定范围内 + public static boolean isTimeInRange(Instant instant, LocalTime startTime, LocalTime endTime) { + LocalTime localTime = instant.atZone(Instant.now().atZone(ZoneId.systemDefault()).getZone()).toLocalTime(); + return !localTime.isBefore(startTime) && !localTime.isAfter(endTime); + } + + //辅助方法:检查时间是否在指定范围内startTime,endTime + public static boolean isTimeInRange(Instant instant, LocalDate startTime, LocalDate endTime) { + // 将Instant对象转换为LocalDateTime对象 + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + Instant instant1 = startTime.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant().plusMillis(TimeUnit.HOURS.toMillis(8)); + Instant instant2 = endTime.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant().plusMillis(TimeUnit.HOURS.toMillis(8)); + // 检查LocalDateTime对象是否在startTime和endTime之间 + boolean isInRange = instant1.isBefore(instant) && instant2.isAfter(instant); + + // 返回结果 + return isInRange; + } + + + public static List getAttributeValueByPropertyName(List list, String propertyName) { + List resultList = new ArrayList<>(); + for (T item : list) { + try { + Field field = item.getClass().getDeclaredField(propertyName); + field.setAccessible(true); + resultList.add((Double) field.get(item)); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + return resultList; + } + + public static Double getAttributeValueByPropertyName(T item, String propertyName) { + Double result = null; + try { + Field field = item.getClass().getDeclaredField(propertyName); + field.setAccessible(true); + result=(Double) field.get(item); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + return result; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/controller/LineController.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/controller/LineController.java new file mode 100644 index 0000000..fd76cb5 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/controller/LineController.java @@ -0,0 +1,69 @@ +package com.njcn.product.device.ledger.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.common.utils.LogUtil; + +import com.njcn.product.device.ledger.pojo.vo.LineDetailDataVO; +import com.njcn.product.device.ledger.service.LineService; +import com.njcn.product.device.overlimit.pojo.Overlimit; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import springfox.documentation.annotations.ApiIgnore; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * @author denghuajun + * @date 2022/2/23 + * 监测点相关 + */ +@Slf4j +@Api(tags = "监测点管理") +@RestController +@RequestMapping("/line") +@RequiredArgsConstructor +public class LineController extends BaseController { + + private final LineService lineService; + + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/getLineDetailData") + @ApiOperation("根据监测点id获取监测点详情") + @ApiImplicitParam(name = "id", value = "监测点id", required = true) + public HttpResult getLineDetailData(@RequestParam("id") String id) { + String methodDescribe = getMethodDescribe("getLineDetailData"); + LineDetailDataVO result = lineService.getLineDetailData(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/getOverLimitData") + @ApiOperation("根据监测点id获取越限数值") + @ApiImplicitParam(name = "id", value = "监测点id", required = true) + public HttpResult getOverLimitData(@RequestParam("id") String id) { + String methodDescribe = getMethodDescribe("getOverLimitData"); + Overlimit result = lineService.getOverLimitData(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/controller/TerminalTreeController.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/controller/TerminalTreeController.java new file mode 100644 index 0000000..fe423dd --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/controller/TerminalTreeController.java @@ -0,0 +1,62 @@ +package com.njcn.product.device.ledger.controller; + + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; + +import com.njcn.product.device.ledger.pojo.dto.TerminalTree; +import com.njcn.product.device.ledger.pojo.param.DeviceInfoParam; +import com.njcn.product.device.ledger.service.TerminalTreeService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * pqs + * 终端树控制器 + * @author cdf + * @date 2021/7/19 + */ +@Slf4j +@Api(tags = "终端树管理") +@RestController +@RequiredArgsConstructor +@RequestMapping("/terminalTree") +public class TerminalTreeController extends BaseController { + + private final TerminalTreeService terminalTreeService; + + + + /** + * 获取终端台账设备树 + * @author cdf + * @date 2021/7/19 + */ + + @ApiOperation("获取5层终端树") + @OperateInfo(info = LogEnum.BUSINESS_MEDIUM) + @PostMapping("getTerminalTreeForFive") + @ApiImplicitParam(name = "deviceInfoParam", value = "台账查询参数", required = true) + public HttpResult> getTerminalTreeForFive(@RequestBody @Validated DeviceInfoParam deviceInfoParam){ + String methodDescribe = getMethodDescribe("getTerminalTreeForFive"); + List tree = terminalTreeService.getTerminalTreeForFive(deviceInfoParam); + + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, tree, methodDescribe); + } + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/DeptLineMapper.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/DeptLineMapper.java new file mode 100644 index 0000000..dbd6408 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/DeptLineMapper.java @@ -0,0 +1,47 @@ +package com.njcn.product.device.ledger.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.device.ledger.pojo.po.DeptLine; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author denghuajun + * @since 2022-01-12 18:04 + */ +public interface DeptLineMapper extends BaseMapper { + + + @Select ("SELECT\n" + + "\tpq_dept_line.Id,\n" + + "\tpq_dept_line.Line_Id\n" + + "FROM\n" + + "\tpq_dept_line\n" + + "WHERE\n" + + "\tEXISTS (\n" + + "\t\tSELECT\n" + + "\t\t\t1\n" + + "\t\tFROM\n" + + "\t\t\tpq_device,\n" + + "\t\t\tpq_line\n" + + "\t\tWHERE\n" + + "\t\t\tSUBSTRING_INDEX(\n" + + "\t\t\t\tSUBSTRING_INDEX(pq_line.Pids, ',', 5),\n" + + "\t\t\t\t',',\n" + + "\t\t\t\t- 1\n" + + "\t\t\t) = pq_device.Id\n" + + "\t\tAND pq_line.Id = pq_dept_line.Line_Id and (pq_device.Dev_Data_Type= 2 or pq_device.Dev_Data_Type = #{devDataType})\n" + + "\t)") + List getLineByDeptRelation(@Param("devDataType")Integer devDataType); + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/DeviceMapper.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/DeviceMapper.java new file mode 100644 index 0000000..0d337c4 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/DeviceMapper.java @@ -0,0 +1,24 @@ +package com.njcn.product.device.ledger.mapper; + + + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.device.ledger.pojo.po.Device; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author cdf + * @since 2022-01-04 + */ +public interface DeviceMapper extends BaseMapper { + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/LineDetailMapper.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/LineDetailMapper.java new file mode 100644 index 0000000..797daa5 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/LineDetailMapper.java @@ -0,0 +1,24 @@ +package com.njcn.product.device.ledger.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.device.ledger.pojo.po.LineDetail; +import com.njcn.product.device.ledger.pojo.vo.LineDetailDataVO; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author cdf + * @since 2022-01-04 + */ +public interface LineDetailMapper extends BaseMapper { + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/LineMapper.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/LineMapper.java new file mode 100644 index 0000000..4c02ab0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/LineMapper.java @@ -0,0 +1,103 @@ +package com.njcn.product.device.ledger.mapper; + + +import cn.hutool.core.date.DateTime; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.dto.SimpleDTO; + +import com.njcn.product.device.ledger.pojo.dto.DeviceType; +import com.njcn.product.device.ledger.pojo.param.DeviceInfoParam; +import com.njcn.product.device.ledger.pojo.po.Line; +import com.njcn.product.device.ledger.pojo.vo.LineDetailVO; +import com.njcn.web.pojo.vo.LineDataVO; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + *

+ * Mapper 接口 + *

+ * + * @author cdf + * @since 2022-01-04 + */ +public interface LineMapper extends BaseMapper { + + List getLineDetail(@Param("ids") List ids); + /** + * 获取监测点信息 + * + * @param id 监测点id + * @return 结果 + */ + LineDetailVO getLineSubGdDetail(@Param("id") String id); + + /** + * 根据监测点id,获取所有监测点 + * + * @param ids 监测点id + * @param deviceInfoParam 监测点查询条件 + * @return 监测点数据 + */ + List getLineByCondition(@Param("ids") List ids, @Param("deviceInfoParam") DeviceInfoParam deviceInfoParam); + + /** + * 查询终端信息 + * + * @param devIds 终端索引 + * @param deviceType 终端筛选条件 + * @param manufacturer 终端厂家 + */ + List getDeviceByCondition(@Param("devIds") List devIds, @Param("deviceType") DeviceType deviceType, @Param("manufacturer") List manufacturer); + + /** + * 查询母线信息 + * + * @param voltageIds 母线索引 + * @param scale 电压等级 + */ + List getVoltageByCondition(@Param("voltageIds") List voltageIds, @Param("scale") List scale); + + List getSubByCondition(@Param("subIds") List subIds, @Param("scale") List scale); + + + /** + * 查询母线id + * + * @param voltageIds 母线索引集合 + * @param scale 电压等级 + */ + List getVoltageIdByScale(@Param("voltageIds") List voltageIds, @Param("scale") String scale); + + /** + * 查询变电站id + * + * @param subIds 变电站索引集合 + * @param scale 电压等级 + */ + List getSubIdByScale(@Param("subIds") List subIds, @Param("scale") String scale); + + /** + * 查询监测点id + * + * @param lineIds 监测点索引集合 + * @param loadType 干扰源类型 + */ + List getLineIdByLoadType(@Param("lineIds") List lineIds, @Param("loadType") String loadType); + + + /** + * 查询终端id + * + * @param deviceIds 终端索引集合 + * @param manufacturer 制造厂家 + */ + List getDeviceIdByManufacturer(@Param("deviceIds") List deviceIds, @Param("manufacturer") String manufacturer); + + List getDeviceIdByPowerFlag(@Param("lineIds")List lineIds, @Param("powerFlag")Integer manufacturer); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/OverlimitMapper.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/OverlimitMapper.java new file mode 100644 index 0000000..e1478d6 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/OverlimitMapper.java @@ -0,0 +1,17 @@ +package com.njcn.product.device.ledger.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.device.overlimit.pojo.Overlimit; + +/** + *

+ * Mapper 接口 + *

+ * + * @author cdf + * @since 2022-01-04 + */ +public interface OverlimitMapper extends BaseMapper { + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/TreeMapper.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/TreeMapper.java new file mode 100644 index 0000000..324b44f --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/TreeMapper.java @@ -0,0 +1,45 @@ +package com.njcn.product.device.ledger.mapper; + +import com.njcn.product.device.ledger.pojo.dto.TerminalTree; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2022/2/28 + */ +public interface TreeMapper { + + /** + * 根据供电公司索引获取出省会的信息 + * @param gdIndexes 供电公司索引 + * @return 省会信息 + */ + List getProvinceList(@Param("gdIndex")List gdIndexes); + + /** + * 获取出供电公司的信息 + * @param gdIndexes 供电公司索引 + * @return 供电公司信息 + */ + List getGdList(@Param("gdIndex")List gdIndexes); + + /** + * 获取出变电站的信息 + * @param subIndexes 变电站索引 + * @return 变电站信息 + */ + List getSubList(@Param("subIndex")List subIndexes); + + /** + * 根据监测点索引获取监测点级五层树数据 + * @param lineIndexes 监测点索引 + * @return 监测点信息 + */ + List getLineList(@Param("lineIndex")List lineIndexes); + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/VoltageMapper.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/VoltageMapper.java new file mode 100644 index 0000000..34a2007 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/VoltageMapper.java @@ -0,0 +1,23 @@ +package com.njcn.product.device.ledger.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.device.ledger.pojo.po.LineDetail; +import com.njcn.product.device.ledger.pojo.po.Voltage; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author cdf + * @since 2022-01-04 + */ +public interface VoltageMapper extends BaseMapper { + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/DeptLineMapper.xml b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/DeptLineMapper.xml new file mode 100644 index 0000000..db288ed --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/DeptLineMapper.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/DeviceMapper.xml b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/DeviceMapper.xml new file mode 100644 index 0000000..b607515 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/DeviceMapper.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/LineDetailMapper.xml b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/LineDetailMapper.xml new file mode 100644 index 0000000..54ff10a --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/LineDetailMapper.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/LineMapper.xml b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/LineMapper.xml new file mode 100644 index 0000000..99aac36 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/LineMapper.xml @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/OverlimitMapper.xml b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/OverlimitMapper.xml new file mode 100644 index 0000000..96450fa --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/OverlimitMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/TreeMapper.xml b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/TreeMapper.xml new file mode 100644 index 0000000..0d4c2df --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/TreeMapper.xml @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/VoltageMapper.xml b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/VoltageMapper.xml new file mode 100644 index 0000000..adfec87 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/mapper/mapping/VoltageMapper.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/DeviceType.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/DeviceType.java new file mode 100644 index 0000000..18965b0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/DeviceType.java @@ -0,0 +1,42 @@ +package com.njcn.product.device.ledger.pojo.dto; + + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + + +/** + * 设备状态类 + * @author hongawen + * @version 1.0.0 + * @date 2022年02月11日 14:54 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DeviceType implements Serializable { + + /** + * 终端模型(0:虚拟设备;1:实际设备;2:离线设备;)默认是实际设备 + */ + private List devModel; + + /** + * 终端状态(0:投运;1:热备用;2:停运) + */ + private List runFlag; + + /** + * 数据类型(0:暂态系统;1:稳态系统;2:两个系统) + */ + private List dataType ; + + /** + * 通讯状态(0:中断;1:正常) + */ + private List comFlag ; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/GeneralDeviceDTO.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/GeneralDeviceDTO.java new file mode 100644 index 0000000..5dfa0b7 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/GeneralDeviceDTO.java @@ -0,0 +1,67 @@ +package com.njcn.product.device.ledger.pojo.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年09月07日 10:48 + * name对应统计名称:如 区域:南京市、苏州市;电压等级:10kV、220kV... + * index对应统计索引:如 区域:南京市索引、苏州市索引;电压等级:10kV索引、220kV索引... + * gdIndexes:供电公司索引集合 + * subIndexes:变电站索引集合 + * deviceIndexes:终端索引集合 + * voltageIndexes:母线索引集合 + * lineIndexes:监测点索引集合 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class GeneralDeviceDTO implements Serializable { + + /** + * name对应统计名称:如 区域:南京市、苏州市;电压等级:10kV、220kV... + */ + @ApiModelProperty(name = "name", value = "名称") + private String name; + + /** + * index对应统计索引:如 区域:南京市索引、苏州市索引;电压等级:10kV索引、220kV索引... + */ + private String index; + + /** + * gdIndexes:供电公司索引集合 + */ + private List gdIndexes = new ArrayList<>(); + + /** + * subIndexes:变电站索引集合 + */ + private List subIndexes = new ArrayList<>(); + + /** + * deviceIndexes:终端索引集合 + */ + private List deviceIndexes = new ArrayList<>(); + + /** + * voltageIndexes:母线索引集合 + */ + private List voltageIndexes = new ArrayList<>(); + + /** + * lineIndexes:监测点索引集合 + */ + private List lineIndexes = new ArrayList<>(); + @ApiModelProperty(name = "tail", value = "总数") + private Integer tail; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/TerminalTree.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/TerminalTree.java new file mode 100644 index 0000000..a3ce514 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/dto/TerminalTree.java @@ -0,0 +1,80 @@ +package com.njcn.product.device.ledger.pojo.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * pqs + * 终端树实体 + * @author cdf + * @date 2021/7/19 + */ +@ApiModel +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TerminalTree implements Serializable { + @ApiModelProperty(name = "index",value = "序号") + private Integer index; + + private String id; + @ApiModelProperty(name = "parentId",value = "父id") + private String pid; + @ApiModelProperty(name = "level",value = "等级") + private Integer level; + @ApiModelProperty(name = "name",value = "名称") + private String name; + @ApiModelProperty(name = "sort",value = "排序") + private Integer sort; + @ApiModelProperty(name = "comFlag",value = "设备状态") + private Integer comFlag; + + @ApiModelProperty(name = "children",value = "子节点") + private List children = new ArrayList<>(); + + private String pids; + + /** + * 终端厂家 + */ + private String manufacturer; + + /** + * 电压等级Id,字典表 + */ + private String scale; + + /** + * 干扰源类型,字典表 + */ + private String loadType; + + /** + * 接线方式 + */ + private Integer ptType; + + /** + * 电网标志(0-电网侧;1-非电网侧) + */ + private Integer powerFlag; + + /** + * 电网侧变电站 + */ + private String powerSubstationName; + + /** + * 电网侧变电站 + */ + private String objName; + + private String objId; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/LineBaseEnum.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/LineBaseEnum.java new file mode 100644 index 0000000..9d90119 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/LineBaseEnum.java @@ -0,0 +1,63 @@ +package com.njcn.product.device.ledger.pojo.enums; + +import lombok.Getter; + +import java.util.Arrays; + +/** + * pqs + * + * @author cdf + * @date 2022/1/4 + */ +@Getter +public enum LineBaseEnum { + + /** + * 系统拓扑各层级描述 + */ + PROJECT_LEVEL(0, "项目"), + PROVINCE_LEVEL(1, "省份"), + GD_LEVEL(2, "供电公司"), + SUB_LEVEL(3, "变电站"), + DEVICE_LEVEL(4, "终端"), + SUB_V_LEVEL(5, "母线"), + LINE_LEVEL(6, "监测点"), + USER_LEVEL(7,"用户"), + INVALID_LEVEL(-1, "非法拓扑等级"), + + + + /** + * 分布式光伏树层级 + */ + PV_UNIT_LEVEL(0,"单位"), + PV_SUB_LEVEL(1,"变电站"), + PV_SUB_AREA_LEVEL(2,"台区"), + + /** + * 电网标志 + */ + POWER_FLAG(0,"电网侧"), + POWER_FLAG_NOT(1,"非电网侧"), + + + + ; + + private final Integer code; + private final String message; + + LineBaseEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + public static LineBaseEnum getLineBaseEnumByCode(Integer code) { + return Arrays.stream(LineBaseEnum.values()) + .filter(lineBaseEnum -> lineBaseEnum.getCode().equals(code)) + .findAny() + .orElse(INVALID_LEVEL); + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/LineFlagEnum.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/LineFlagEnum.java new file mode 100644 index 0000000..5aaca1c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/LineFlagEnum.java @@ -0,0 +1,35 @@ +package com.njcn.product.device.ledger.pojo.enums; + +import lombok.Getter; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年02月23日 15:24 + */ +@Getter +public enum LineFlagEnum { + + /** + * 区分监测点的类型标志 + */ + //非网公司 + LINE_MONITOR_NOT_NET_COMPANY(0), + //网公司 + LINE_MONITOR_NET_COMPANY(1), + //所有公司 + LINE_MONITOR_ALL(2), + //电网侧 + LINE_POWER_GRID(0), + //非电网侧 + LINE_POWER(1), + //所有 + LINE_POWER_ALL(2); + + private final int flag; + + LineFlagEnum(int flag) { + this.flag = flag; + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/PowerFlagEnum.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/PowerFlagEnum.java new file mode 100644 index 0000000..36ed3d4 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/PowerFlagEnum.java @@ -0,0 +1,52 @@ +package com.njcn.product.device.ledger.pojo.enums; + +import lombok.Getter; + +import java.util.Arrays; + +/** + * pqs + * + * @author cdf + * @date 2022/1/4 + */ +@Getter +public enum PowerFlagEnum { + + /** + * 系统拓扑各层级描述 + */ + GRID_SIDE(0, "电网侧"), + NO_GRID_SIDE(1, "非电网侧"), + NEW_ENERGY(2, "电网侧(新能源)"), + NO_NEW_ENERGY(3, "非电网侧(新能源)"), + SEND_NETWORK(4, "上送国网"), + PCC(5, "PCC"), + + + VIRTUAL_DEVICE(0,"虚拟终端"), + REAL_DEVICE(1,"实际终端"), + OFFLINE_DEICE(2,"离线终端") + + + + + + ; + + private final Integer code; + private final String message; + + PowerFlagEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + public static PowerFlagEnum getPowerFlagEnumByCode(Integer code) { + return Arrays.stream(PowerFlagEnum.values()) + .filter(x -> x.getCode().equals(code)) + .findAny() + .orElse(GRID_SIDE); + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/StatisticsEnum.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/StatisticsEnum.java new file mode 100644 index 0000000..09b0e11 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/enums/StatisticsEnum.java @@ -0,0 +1,49 @@ +package com.njcn.product.device.ledger.pojo.enums; + + +import lombok.Getter; + +import java.util.Arrays; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年03月18日 13:27 + */ +@Getter +public enum StatisticsEnum { + + /** + * 统计类型字典枚举 + */ + POWER_NETWORK("网络拓扑", "Power_Network"), + VOLTAGE_LEVEL("电压等级", "Voltage_Level"), + LOAD_TYPE("干扰源类型", "Load_Type"), + MANUFACTURER("终端厂家", "Manufacturer"), + POWER_FLAG("监测点性质", "Power_Flag"), + REPORT_TYPE("上报类型", "Report_Type"); + + private final String name; + + private final String code; + + StatisticsEnum(String name, String code) { + this.name = name; + this.code = code; + } + + + /** + * 没有匹配到,则默认为网络拓扑 + * @param code 统计类型code + * @return 统计枚举实例 + */ + public static StatisticsEnum getStatisticsEnumByCode(String code) { + return Arrays.stream(StatisticsEnum.values()) + .filter(statisticsEnum -> statisticsEnum.getCode().equalsIgnoreCase(code)) + .findAny() + .orElse(POWER_NETWORK); + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/param/DeviceInfoParam.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/param/DeviceInfoParam.java new file mode 100644 index 0000000..0edefa1 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/param/DeviceInfoParam.java @@ -0,0 +1,219 @@ +package com.njcn.product.device.ledger.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.common.pojo.dto.SimpleDTO; + +import com.njcn.product.device.ledger.pojo.enums.LineFlagEnum; +import com.njcn.product.device.ledger.pojo.enums.PowerFlagEnum; +import com.njcn.web.constant.ValidMessage; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年02月23日 19:04 + */ +@Data +@ApiModel +@NoArgsConstructor +public class DeviceInfoParam implements Serializable { + + /** + * 统计类型 + */ + @ApiModelProperty(name = "statisticalType", value = "统计类型", required = true) + @NotNull(message = "统计类型不可为空") + private SimpleDTO statisticalType; + + @ApiModelProperty(name = "deptIndex", value = "部门索引", required = true) + @NotBlank(message = "部门索引不可为空") + private String deptIndex; + + @ApiModelProperty(name = "serverName", value = "服务名称") + private String serverName; + + + @ApiModelProperty(name = "scale", value = "电压等级") + private List scale; + + + @ApiModelProperty(name = "manufacturer", value = "终端厂家") + private List manufacturer; + + + @ApiModelProperty(name = "loadType", value = "干扰源类型") + private List loadType; + + /** + * xy添加 + * 默认true + * true statFlag = 1 + * false statFlag = 0 or 1 + */ + @ApiModelProperty(name = "statFlag", value = "人为干预是否参与统计") + private Boolean statFlag; + + /** + * 0-非网公司 + * 1-网公司 + * 2-全部数据 + */ + @ApiModelProperty("网公司标识") + @Range(min = 0, max = 2, message = "网公司标识" + ValidMessage.PARAM_FORMAT_ERROR) + private Integer monitorFlag; + + /** + * 0-电网侧 + * 1-非电网侧 + */ + @ApiModelProperty("电网侧标识") + @Range(min = 0, max = 2, message = "电网侧标识" + ValidMessage.PARAM_FORMAT_ERROR) + private Integer powerFlag; + + /** + * 0-极重要 + * 1-重要 + * 2-普通 + * 3-不重要 + */ + @ApiModelProperty("监测点等级") + private String lineGrade; + + /** + * 通讯状态(0:中断;1:正常) + */ + @ApiModelProperty("通讯状态") + @Range(min = 0, max = 2, message = "通讯状态" + ValidMessage.PARAM_FORMAT_ERROR) + private Integer comFlagStatus; + + + /** + * 监测点运行状态(0:运行;1:检修;2:停运;3:调试;4:退运) + */ + @ApiModelProperty("监测点运行状态") + @Range(min = 0, max = 2, message = "监测点运行状态" + ValidMessage.PARAM_FORMAT_ERROR) + private Integer lineRunFlag; + + /** + * 默认全部监测点 + * + * @param deptIndex 部门索引 + * @param serverName 服务名 + */ + public DeviceInfoParam(String deptIndex, String serverName) { + this.deptIndex = deptIndex; + this.serverName = serverName; + monitorFlag = LineFlagEnum.LINE_MONITOR_ALL.getFlag(); + powerFlag = LineFlagEnum.LINE_POWER_ALL.getFlag(); + } + + + /** + * 默认全部监测点 + * + * @param deptIndex 部门索引 + * @param serverName 服务名 + */ + public DeviceInfoParam(SimpleDTO statisticalType, String deptIndex, String serverName, List scale, List manufacturer, List loadType) { + this.statisticalType = statisticalType; + this.deptIndex = deptIndex; + this.serverName = serverName; + this.scale = scale; + this.manufacturer = manufacturer; + this.loadType = loadType; + monitorFlag = LineFlagEnum.LINE_MONITOR_ALL.getFlag(); + powerFlag = LineFlagEnum.LINE_POWER_ALL.getFlag(); + } + + /** + * 自定义上报方式、电网侧方式的统计 + */ + public DeviceInfoParam(SimpleDTO statisticalType, String deptIndex, String serverName, List scale, List manufacturer, List loadType, int monitorFlag, int powerFlag) { + this.statisticalType = statisticalType; + this.deptIndex = deptIndex; + this.serverName = serverName; + this.scale = scale; + this.manufacturer = manufacturer; + this.loadType = loadType; + this.monitorFlag = monitorFlag; + this.powerFlag = powerFlag; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class BusinessParam extends DeviceInfoParam { + + @ApiModelProperty("开始时间") + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String searchBeginTime; + + @ApiModelProperty("结束时间") + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String searchEndTime; + + @ApiModelProperty("时间范围标志 0.查询展示天 1.查询展示月") + @Deprecated + private Integer timeFlag; + + @ApiModelProperty("统计类型 1.年 2.季 3.月 4.周 5.天") + private String reportFlag; + + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class CompareBusinessParam extends BusinessParam { + + @ApiModelProperty("比较开始时间") + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String periodBeginTime; + + @ApiModelProperty("比较结束时间") + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String periodEndTime; + + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class CompareLimitParam extends BusinessParam { + + @ApiModelProperty("查询条数") + @NotNull(message = " 查询条数查询条数不能为空") + private Integer limit; + + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class GridDiagram extends BusinessParam { + + @ApiModelProperty("查询总数监测点") + private List coutList; + + @ApiModelProperty("查询告警监测点") + private List alarmList; + + @ApiModelProperty("是否是冀北电网一张图树 0:否 1:是") + private Integer type = 0; + } + + public Boolean isUserLedger() { + if (Objects.isNull(this.powerFlag) || !PowerFlagEnum.GRID_SIDE.getCode().equals(this.powerFlag)) { + return true; + } + return false; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/DeptLine.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/DeptLine.java new file mode 100644 index 0000000..521b78b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/DeptLine.java @@ -0,0 +1,31 @@ +package com.njcn.product.device.ledger.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@TableName("pq_dept_line") +public class DeptLine { + + private static final long serialVersionUID = 1L; + + /** + * 部门Id + */ + private String id; + + /** + * 监测点Id + */ + private String lineId; + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Device.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Device.java new file mode 100644 index 0000000..de49e7f --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Device.java @@ -0,0 +1,165 @@ +package com.njcn.product.device.ledger.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@TableName("pq_device") +public class Device implements Serializable{ + + private static final long serialVersionUID = 1L; + + /** + * 装置序号 + */ + @TableId + private String id; + + /** + * 装置模型(0:虚拟设备;1:实际设备;2:离线设备;)默认是实际设备 + */ + private Integer devModel; + + /** + * 数据类型(0:暂态系统;1:稳态系统;2:两个系统) + */ + private Integer devDataType; + + /** + * 终端运行状态(0:运行;1:检修;2:停运;3:调试;4:退运) + */ + private Integer runFlag; + + /** + * 通讯状态(0:中断;1:正常) + */ + private Integer comFlag; + + /** + * 设备制造商,字典表 + */ + private String manufacturer; + + /** + * 定检状态(0:未检 1:已检) + */ + private Integer checkFlag; + + /** + * 前置类型(MMS、CLD)字典表 + */ + private String frontType; + + /** + * 终端型号(570、580……)字典表 + */ + private String devType; + + /** + * 网络参数 + */ + private String ip; + + /** + * 召唤标志(0:周期触发;1:变为触发) + */ + private Integer callFlag; + + /** + * 端口 + */ + private Integer port; + + /** + * 装置识别码(3ds加密) + */ + private String series; + + /** + * 装置秘钥(3ds加密) + */ + private String devKey; + + /** + * 前置序号Id,前置表 + */ + private String nodeId; + + /** + * 投运时间 + */ + private LocalDate loginTime; + + /** + * 数据更新时间 + */ + private LocalDateTime updateTime; + + /** + * 本次定检时间,默认等于投运时间 + */ + private LocalDate thisTimeCheck; + + /** + * 下次定检时间,默认为投运时间后推3年,假如时间小于3个月则为待检 + */ + private LocalDate nextTimeCheck; + + /** + * 电度功能 0 关闭 1开启 + */ + private Integer electroplate; + + /** + * 对时功能 0 关闭, 1开启 + */ + private Integer onTime; + + /** + * 合同号 + */ + private String contract; + + /** + * 设备sim卡号 + */ + private String sim; + + + /** + * 装置系列 + */ + private String devSeries; + + + /** + * 监测装置安装位置 + */ + private String devLocation; + + + /** + * 监测厂家设备编号 + */ + private String devNo; + + + /** + * 告警功能 0:关闭 null、1:开启 + */ + private Integer isAlarm; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Line.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Line.java new file mode 100644 index 0000000..d27a36b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Line.java @@ -0,0 +1,66 @@ +package com.njcn.product.device.ledger.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("pq_line") +public class Line extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 监测点Id + */ + private String id; + + /** + * 父节点(0为根节点) + */ + private String pid; + + /** + * 上层所有节点 + */ + private String pids; + + /** + * 名称 + */ + private String name; + + /** + * 等级:0-项目名称;1- 工程名称;2-单位;3-部门;4-终端;5-母线;6-监测点 + */ + private Integer level; + + /** + * 排序(默认为0,有特殊排序需要时候人为输入) + */ + private Integer sort; + + /** + * 备注 + */ + private String remark; + + /** + * 状态 0-删除;1-正常;默认正常 + */ + private Integer state; + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/LineDetail.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/LineDetail.java new file mode 100644 index 0000000..dece4e1 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/LineDetail.java @@ -0,0 +1,219 @@ +package com.njcn.product.device.ledger.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@TableName("pq_line_detail") +public class LineDetail { + + private static final long serialVersionUID = 1L; + + /** + * 监测点序号 + */ + private String id; + + + @TableField(exist = false) + private String monitorName; + + /** + * 线路号(在同一台设备中的监测点号) + */ + private Integer num; + + /** + * PT一次变比 + */ + private Float pt1; + + /** + * PT二次变比 + */ + private Float pt2; + + /** + * CT一次变比 + */ + private Float ct1; + + /** + * CT二次变比 + */ + private Float ct2; + + /** + * 设备容量 + */ + private Float devCapacity; + + /** + * 短路容量 + */ + private Float shortCapacity; + + /** + * 基准容量 + */ + private Float standardCapacity; + + /** + * 协议容量 + */ + private Float dealCapacity; + + /** + * 接线类型(0:星型接法;1:三角型接法;2:开口三角型接法) + */ + private Integer ptType; + + /** + * 测量间隔(1-10分钟) + */ + private Integer timeInterval; + + /** + * 干扰源类型,字典表 + */ + private String loadType; + + /** + * 行业类型,字典表 + */ + private String businessType; + + /** + * 网公司谐波监测平台标志(0-否;1-是),默认否 + */ + private Integer monitorFlag; + + /** + * 电网标志(0-电网侧;1-非电网侧) + */ + private Integer powerFlag; + + /** + * 国网谐波监测平台监测点号 + */ + private String monitorId; + + /** + * 监测点对象名称 + */ + @Deprecated + private String objName; + + /** + * 监测点对象id + */ + private String objId; + + /** + * 监测对象大类 + */ + private String bigObjType; + + /** + * 监测对象小类 + */ + private String smallObjType; + + /** + * 人为干预 0 不参与统计 1 参与统计 + */ + private Integer statFlag; + + /** + * 关联字典的终端等级 + */ + private String lineGrade; + + /** + * 备注 + */ + private String remark; + + + + /** + * 电网侧变电站 + */ + private String powerSubstationName; + /** + * 分类等级 + */ + private String calssificationGrade; + + + /** + * 上级电站 + */ + @Deprecated + private String superiorsSubstation; + + /** + * 挂接线路 + */ + @Deprecated + private String hangLine; + + /** + * 监测点拥有者 + */ + @Deprecated + private String owner; + + /** + * 拥有者职务 + */ + @Deprecated + private String ownerDuty; + + /** + * 拥有者联系方式 + */ + @Deprecated + private String ownerTel; + + /** + * 接线图 + */ + private String wiringDiagram; + /** + * 监测点接线相别(0,单相,1,三相,默认三相) + */ + private Integer ptPhaseType; + + /** + * 监测点实际安装位置 + */ + private String actualArea; + + /** + * 监测点运行状态(0:运行;1:检修;2:停运;3:调试;4:退运) + */ + private Integer runFlag; + + /** + * 新能源场站信息ID + */ + @Deprecated + private String newStationId; + + /** + * 通讯状态 + */ + @TableField(exist = false) + private Integer comFlag; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Voltage.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Voltage.java new file mode 100644 index 0000000..66d4637 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/po/Voltage.java @@ -0,0 +1,42 @@ +package com.njcn.product.device.ledger.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@TableName("pq_voltage") +public class Voltage { + + private static final long serialVersionUID = 1L; + + /** + * 母线序号 + */ + private String id; + + /** + * 母线号(在同一台设备中的电压通道号) + */ + private Integer num; + + /** + * 电压等级Id,字典表 + */ + private String scale; + + /** + * 母线模型(0:虚拟母线;1:实际母线)默认是实际母线 + */ + private Integer model; + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineDetailDataVO.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineDetailDataVO.java new file mode 100644 index 0000000..e5adc54 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineDetailDataVO.java @@ -0,0 +1,130 @@ +package com.njcn.product.device.ledger.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * @author denghuajun + * @date 2022/2/23 + * 监测点信息 + */ +@Data +@ApiModel +public class LineDetailDataVO { + private String lineId; + @ApiModelProperty(name = "id",value = "监测点序号") + private Integer id; + + @ApiModelProperty(name = "lineName",value = "监测点名称") + private String lineName; + + @ApiModelProperty(name = "areaName",value = "工程名称") + private String areaName; + + @ApiModelProperty(name = "gdName",value = "单位") + private String gdName; + + @ApiModelProperty(name = "bdName",value = "部门") + private String bdName; + + @ApiModelProperty(name = "scale",value = "电压等级") + private String scale; + + @ApiModelProperty(name = "manufacturer",value = "厂家") + private String manufacturer; + + @ApiModelProperty(name = "devId",value = "终端Id") + private String devId; + + @ApiModelProperty(name = "devName",value = "终端名称") + private String devName; + + @ApiModelProperty(name = "ip",value = "网络参数") + private String ip; + + @ApiModelProperty(name = "runFlag",value = "终端运行状态") + private String runFlag; + + @ApiModelProperty(name = "comFlag",value = "通讯状态") + private String comFlag; + + @ApiModelProperty(name = "loadType",value = "干扰源类型") + private String loadType; + + @ApiModelProperty(name = "businessType",value = "行业类型") + private String businessType; + + @ApiModelProperty(name = "objName",value = "监测点对象名称") + private String objName; + + @ApiModelProperty(name = "ptType",value = "接线方式") + private String ptType; + + @ApiModelProperty(name = "pt",value = "PT变比") + private String pt; + + @ApiModelProperty(name = "ct",value = "CT变比") + private String ct; + + @ApiModelProperty(name = "standardCapacity",value = "基准容量(MVA)") + private Float standardCapacity; + + @ApiModelProperty(name = "shortCapacity",value = "最小短路容量(MVA)") + private Float shortCapacity; + + @ApiModelProperty(name = "devCapacity",value = "供电设备容量(MVA)") + private Float devCapacity; + + @ApiModelProperty(name = "dealCapacity",value = "用户协议容量(MVA)") + private Float dealCapacity; + + /** + * 测量间隔(1-10分钟) + */ + @ApiModelProperty(name = "timeInterval",value = "测量间隔(1-10分钟)") + private Integer timeInterval; + + /** + * 监测点拥有者 + */ + @ApiModelProperty(name = "owner",value = "监测点拥有者") + private String owner; + + /** + * 拥有者职务 + */ + @ApiModelProperty(name = "ownerDuty",value = "拥有者职务") + private String ownerDuty; + + /** + * 拥有者联系方式 + */ + @ApiModelProperty(name = "ownerTel",value = "拥有者联系方式") + private String ownerTel; + + /** + * 接线图 + */ + @ApiModelProperty(name = "wiringDiagram",value = "接线图") + private String wiringDiagram; + @ApiModelProperty(name = "ptPhaseType",value = "监测点接线相别(0,单相,1,三相,默认三相)") + private Integer ptPhaseType; + + @ApiModelProperty(name = "投运日期") + private LocalDate loginTime; + + @ApiModelProperty(name = "最新数据时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @ApiModelProperty(name = "监测对象信息ID") + private String objId; + + @ApiModelProperty(name = "对象类型大类") + private String bigObjType; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineDetailVO.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineDetailVO.java new file mode 100644 index 0000000..7da8fd7 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineDetailVO.java @@ -0,0 +1,109 @@ +package com.njcn.product.device.ledger.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @author denghuajun + * @version 1.0.0 + * @date 2022年05月06日 15:38 + */ +@Data +public class LineDetailVO implements Serializable { + + @ApiModelProperty("供电公司名称") + private String gdName; + + @ApiModelProperty("变电站名称") + private String subName; + + @ApiModelProperty("终端名称") + private String devName; + + @ApiModelProperty("网络参数") + private String ip; + + @ApiModelProperty("监测点名称") + private String lineName; + + @ApiModelProperty("母线名称") + private String volName; + + /** + * (0:运行;1:检修;2:停运;3:调试;4:退运) + */ + @ApiModelProperty("监测点运行状态") + private Integer runFlag; + @Data + public static class Detail extends LineDetailVO implements Serializable{ + + @ApiModelProperty("区域id") + private String areaId; + + @ApiModelProperty("区域名称") + private String areaName; + + @ApiModelProperty("终端id") + private String devId; + + @ApiModelProperty("监测点Id") + private String lineId; + + @ApiModelProperty("测量间隔(1-10分钟)") + private Integer timeInterval; + + @ApiModelProperty("接线类型") + private Integer ptType; + + @ApiModelProperty("电压等级") + private String voltageLevel; + + @ApiModelProperty("数据更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timeID; + + @ApiModelProperty("终端等级") + private String lineGrade; + + @ApiModelProperty("通讯状态(0:中断;1:正常)") + private Integer comFlag; + + @ApiModelProperty("PT一次变比") + private Double PT1; + + @ApiModelProperty("PT二次变比") + private Double PT2; + + @ApiModelProperty("CT一次变比") + private Double CT1; + + @ApiModelProperty("CT二次变比") + private Double CT2; + + @ApiModelProperty("套餐流量") + private Float flowMeal; + + @ApiModelProperty("已用流量") + private Float statisValue; + + @ApiModelProperty("已用流量占比") + private Float flowProportion; + } + + @Data + public static class noDataLineInfo extends LineDetailVO implements Serializable{ + + @ApiModelProperty("监测点Id") + private String lineId; + + @ApiModelProperty("终端id") + private String devId; + + @ApiModelProperty("最新数据时间") + private LocalDateTime updateTime; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineOverLimitVO.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineOverLimitVO.java new file mode 100644 index 0000000..852c68c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/pojo/vo/LineOverLimitVO.java @@ -0,0 +1,120 @@ +package com.njcn.product.device.ledger.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @author denghuajun + * @date 2022/2/23 + * + */ +@Data +@ApiModel +public class LineOverLimitVO { + + @ApiModelProperty(name = "freqDev",value = "频率限值") + private Float freqDev; + + @ApiModelProperty(name = "voltageDev",value = "电压上偏差限值") + private Float voltageDev; + + @ApiModelProperty(name = "uvoltageDev",value = "电压下偏差限值") + private Float uvoltageDev; + + @ApiModelProperty(name = "ubalance",value = "三相电压不平衡度限值") + private Float ubalance; + + @ApiModelProperty(name = "iNeg",value = "负序电流") + private Float iNeg; + + @ApiModelProperty(name = "flicker",value = "长时闪变限值") + private Float flicker; + + @ApiModelProperty(name = "uaberrance",value = "电压总谐波畸变率限值") + private Float uaberrance; + + @ApiModelProperty(name = "oddHarm",value = "奇次谐波含有率限值") + private Float oddHarm; + + @ApiModelProperty(name = "evenHarm",value = "偶次谐波含有率限值") + private Float evenHarm; + + @ApiModelProperty(name = "iharm2",value = "2次谐波电流幅值限值") + private Float iharm2; + + @ApiModelProperty(name = "iharm3",value = "3次谐波电流幅值限值") + private Float iharm3; + + @ApiModelProperty(name = "iharm4",value = "4次谐波电流幅值限值") + private Float iharm4; + + @ApiModelProperty(name = "iharm5",value = "5次谐波电流幅值限值") + private Float iharm5; + + @ApiModelProperty(name = "iharm6",value = "6次谐波电流幅值限值") + private Float iharm6; + + @ApiModelProperty(name = "iharm7",value = "7次谐波电流幅值限值") + private Float iharm7; + + @ApiModelProperty(name = "iharm8",value = "8次谐波电流幅值限值") + private Float iharm8; + + @ApiModelProperty(name = "iharm9",value = "9次谐波电流幅值限值") + private Float iharm9; + + @ApiModelProperty(name = "iharm10",value = "10次谐波电流幅值限值") + private Float iharm10; + + @ApiModelProperty(name = "iharm11",value = "11次谐波电流幅值限值") + private Float iharm11; + + @ApiModelProperty(name = "iharm12",value = "12次谐波电流幅值限值") + private Float iharm12; + + @ApiModelProperty(name = "iharm13",value = "13次谐波电流幅值限值") + private Float iharm13; + + @ApiModelProperty(name = "iharm14",value = "14次谐波电流幅值限值") + private Float iharm14; + + @ApiModelProperty(name = "iharm15",value = "15次谐波电流幅值限值") + private Float iharm15; + + @ApiModelProperty(name = "iharm16",value = "16次谐波电流幅值限值") + private Float iharm16; + + @ApiModelProperty(name = "iharm17",value = "17次谐波电流幅值限值") + private Float iharm17; + + @ApiModelProperty(name = "iharm18",value = "18次谐波电流幅值限值") + private Float iharm18; + + @ApiModelProperty(name = "iharm19",value = "19次谐波电流幅值限值") + private Float iharm19; + + @ApiModelProperty(name = "iharm20",value = "20次谐波电流幅值限值") + private Float iharm20; + + @ApiModelProperty(name = "iharm21",value = "21次谐波电流幅值限值") + private Float iharm21; + + @ApiModelProperty(name = "iharm22",value = "22次谐波电流幅值限值") + private Float iharm22; + + @ApiModelProperty(name = "iharm23",value = "23次谐波电流幅值限值") + private Float iharm23; + + @ApiModelProperty(name = "iharm24",value = "24次谐波电流幅值限值") + private Float iharm24; + + @ApiModelProperty(name = "iharm25",value = "25次谐波电流幅值限值") + private Float iharm25; + + @ApiModelProperty(name = "inUharm",value = "0.5-1.5次间谐波电压幅值限值") + private Float inUharm; + + @ApiModelProperty(name = "inUharm16",value = "2.5-15.5次间谐波电压幅值限值") + private Float inUharm16; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/DeptLineService.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/DeptLineService.java new file mode 100644 index 0000000..a3f218c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/DeptLineService.java @@ -0,0 +1,64 @@ +package com.njcn.product.device.ledger.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.device.ledger.pojo.po.DeptLine; +import com.njcn.web.pojo.param.DeptLineParam; + +import java.util.List; +import java.util.Map; + +/** + * @author denghuajun + * @date 2022/1/12 17:30 + * + */ +public interface DeptLineService extends IService { + + /** + * 部门绑定监测点 + * @param deptLineParam 部门监测点的实体类 + * @return 绑定结果 + */ + void deptBindLine(DeptLineParam deptLineParam); + + + /** + * 部门解绑监测点 + * @param deptLineParam 部门监测点的实体类 + * @return 解绑结果 + */ + void deptDeleteBindLine(DeptLineParam deptLineParam); + + + /** + * 根据部门ids集合查询是否绑定监测点 + * @param ids 部门ids + * @return 查询结果 + */ + List selectDeptBindLines(List ids); + + /** + * 部门解除绑定监测点 + * @param id 部门id + * @return 解绑结果 + */ + int removeBind(String id); + + /** + * 功能描述: 根据部门id获取绑定的监测点 + * + * @param id + * @return java.util.List + * @author xy + * @date 2022/1/25 9:28 + */ + List getLineByDeptId(String id); + /** + * @Description: 获取部门和监测点的关系(分稳态暂态) + * @Param: [devDataType] + * @return: java.util.Map> + * @Author: clam + * @Date: 2022/10/19 + */ + Map> getLineByDeptRelation(Integer devDataType); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/LineService.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/LineService.java new file mode 100644 index 0000000..0d45717 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/LineService.java @@ -0,0 +1,33 @@ +package com.njcn.product.device.ledger.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; + +import com.njcn.product.device.ledger.pojo.po.Line; +import com.njcn.product.device.ledger.pojo.vo.LineDetailDataVO; +import com.njcn.product.device.ledger.pojo.vo.LineOverLimitVO; +import com.njcn.product.device.overlimit.pojo.Overlimit; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; +import java.util.Map; + +/** + * 监测点类 + * @author denghuajun + * @date 2022/2/23 + * + */ +public interface LineService extends IService { + /** + * 获取监测点详情 + * @param id 监测点id + * @return 结果 + */ + LineDetailDataVO getLineDetailData(String id); + + + + + Overlimit getOverLimitData(String id); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/TerminalBaseService.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/TerminalBaseService.java new file mode 100644 index 0000000..b262dc5 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/TerminalBaseService.java @@ -0,0 +1,119 @@ +package com.njcn.product.device.ledger.service; + +import com.njcn.common.pojo.dto.SimpleDTO; + +import com.njcn.product.device.ledger.pojo.dto.DeviceType; +import com.njcn.product.device.ledger.pojo.param.DeviceInfoParam; +import com.njcn.product.device.ledger.pojo.po.Line; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2022/1/4 + */ +public interface TerminalBaseService { + + + + + /** + * 根据监测点id,获取所有监测点 + * + * @param lineIds 监测点id + * @return 监测点数据 + */ + List getLineById(List lineIds); + + /** + * 根据监测点id,获取所有监测点 + * + * @param lineIds 监测点id + * @param deviceInfoParam 监测点查询条件 + * @return 监测点数据 + */ + List getLineByCondition(List lineIds, DeviceInfoParam deviceInfoParam); + + + + + + + + + + /** + * 查询终端信息 + * + * @param devIds 终端索引 + * @param deviceType 终端筛选条件 + * @param manufacturer 终端厂家 + */ + List getDeviceByCondition(List devIds, DeviceType deviceType, List manufacturer); + + /** + * 查询母线信息 + * + * @param voltageIds 母线索引 + * @param scale 电压等级 + */ + List getVoltageByCondition(List voltageIds, List scale); + + /** + * 查询变电站信息 + * + * @param subIds 变电站索引 + * @param scale 电压等级 + */ + List getSubByCondition(List subIds, List scale); + + /** + * 根据指定电压等级查询母线id + * + * @param voltageIds 母线id + * @param scale 电压等级 + */ + List getVoltageIdByScale(List voltageIds, String scale); + + /** + * 根据指定电压等级查询母线id + * @param subIds + * @param scale + * @return: java.util.List + * @Author: wr + * @Date: 2024/10/12 15:58 + */ + List getSubIdByScale(List subIds, String scale); + + /** + * 根据干扰源获取对应的监测点id + * + * @param lineIds 监测点id + * @param loadType 干扰源类型 + */ + List getLineIdByLoadType(List lineIds, String loadType); + + /** + * 根据终端厂家获取对应的终端id + * + * @param deviceIds 终端id + * @param manufacturer 终端厂家 + */ + List getDeviceIdByManufacturer(List deviceIds, String manufacturer); + /** + * 根据监测点性质获取监测信息 + * + * @param lineIds 监测点id + * @param manufacturer 监测点性质 + */ + List getDeviceIdByPowerFlag(List lineIds, Integer manufacturer); + + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/TerminalTreeService.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/TerminalTreeService.java new file mode 100644 index 0000000..e2ab053 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/TerminalTreeService.java @@ -0,0 +1,30 @@ +package com.njcn.product.device.ledger.service; + + + + +import com.njcn.product.device.ledger.pojo.dto.TerminalTree; +import com.njcn.product.device.ledger.pojo.param.DeviceInfoParam; + +import java.util.List; + +/** + * pqs + * 终端设备树业务 + * + * @author cdf + * @date 2021/7/19 + */ +public interface TerminalTreeService { + + + /** + * 5层树排除设备 母线监测点合并 + * + * @author cdf + * @date 2022/1/13 + */ + List getTerminalTreeForFive(DeviceInfoParam deviceInfoParam); + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/DeptLineServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/DeptLineServiceImpl.java new file mode 100644 index 0000000..3aaecd2 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/DeptLineServiceImpl.java @@ -0,0 +1,91 @@ +package com.njcn.product.device.ledger.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.product.device.ledger.mapper.DeptLineMapper; +import com.njcn.product.device.ledger.pojo.po.DeptLine; +import com.njcn.product.device.ledger.service.DeptLineService; +import com.njcn.web.pojo.param.DeptLineParam; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author denghuajun + * @date 2022/1/12 17:32 + * + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DeptLineServiceImpl extends ServiceImpl implements DeptLineService { + + private final DeptLineMapper deptLineMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public void deptBindLine(DeptLineParam deptLineParam) { + //先解绑,再进行绑定 + QueryWrapper deptLineQueryWrapper = new QueryWrapper<>(); + deptLineQueryWrapper.eq("pq_dept_line.id", deptLineParam.getId()); + this.baseMapper.delete(deptLineQueryWrapper); + List deptLines = deptLineParam.getIds().stream().map(id -> { + DeptLine deptLine = new DeptLine(); + deptLine.setId(deptLineParam.getId()); + deptLine.setLineId(id); + return deptLine; + }).collect(Collectors.toList()); + this.saveBatch(deptLines); + } + + @Override + public void deptDeleteBindLine(DeptLineParam deptLineParam) { + for (int i = 0; i < deptLineParam.getIds().size(); i++) { + QueryWrapper deptLineQueryWrapper = new QueryWrapper<>(); + deptLineQueryWrapper.eq("pq_dept_line.id", deptLineParam.getId()); + deptLineQueryWrapper.eq("pq_dept_line.Line_Id", deptLineParam.getIds().get(i)); + this.baseMapper.delete(deptLineQueryWrapper); + } + } + + @Override + public List selectDeptBindLines(List ids) { + return this.lambdaQuery().in(DeptLine::getId, ids).list(); + } + + @Override + public int removeBind(String id) { + QueryWrapper deptLineQueryWrapper = new QueryWrapper<>(); + deptLineQueryWrapper.eq("pq_dept_line.id", id); + return this.baseMapper.delete(deptLineQueryWrapper); + } + + @Override + public List getLineByDeptId(String id) { + return this.lambdaQuery().in(DeptLine::getId, id).list().stream().map(DeptLine::getLineId).distinct().collect(Collectors.toList()); + } + + /** + * @param devDataType + * @Description: 获取部门和监测点的关系(分稳态暂态) + * @Param: [devDataType] + * @return: java.util.Map> + * @Author: clam + * @Date: 2022/10/19 + */ + @Override + public Map> getLineByDeptRelation(Integer devDataType) { + List deptLines = deptLineMapper.getLineByDeptRelation(devDataType); + Map> collect = deptLines.stream ( ).collect (Collectors.groupingBy (DeptLine::getId, Collectors.mapping (DeptLine::getLineId,Collectors.toList ()))); + + return collect; + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/GeneralDeviceService.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/GeneralDeviceService.java new file mode 100644 index 0000000..0b0aba5 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/GeneralDeviceService.java @@ -0,0 +1,434 @@ +package com.njcn.product.device.ledger.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.njcn.common.pojo.dto.SimpleDTO; +import com.njcn.common.pojo.enums.common.ServerEnum; +import com.njcn.common.utils.EnumUtils; + +import com.njcn.product.device.ledger.pojo.dto.DeviceType; +import com.njcn.product.device.ledger.pojo.dto.GeneralDeviceDTO; +import com.njcn.product.device.ledger.pojo.enums.LineBaseEnum; +import com.njcn.product.device.ledger.pojo.enums.PowerFlagEnum; +import com.njcn.product.device.ledger.pojo.enums.StatisticsEnum; +import com.njcn.product.device.ledger.pojo.param.DeviceInfoParam; +import com.njcn.product.device.ledger.pojo.po.DeptLine; +import com.njcn.product.device.ledger.pojo.po.Line; +import com.njcn.product.device.ledger.service.DeptLineService; +import com.njcn.product.device.ledger.service.TerminalBaseService; +import com.njcn.product.system.dept.pojo.dto.DeptDTO; +import com.njcn.product.system.dept.service.IDeptService; +import com.njcn.product.system.dict.enums.DicDataTypeEnum; +import com.njcn.product.system.dict.pojo.po.DictData; +import com.njcn.product.system.dict.service.IDictDataService; +import com.njcn.web.utils.WebUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 终端信息处理器,根据需求返回笼统的台账信息。 + * ii * 包括:类别名称、类别索引、监测点索引集合、终端索引集合、变电站索引集bb合、供电公司索引集合。 + * PS:若后期需要比如:省会、项目时再动态添加。 + * + * @author hongawen + * @version 1.0.0 + * @date 2022年02月11日 09:29 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class GeneralDeviceService { + + private final IDeptService deptService; + + private final DeptLineService deptLineService; + private final IDictDataService iDictDataService; + + private final TerminalBaseService terminalBaseService; + + + + /** + * 根据部门id、远程服务名、远程客户端类型,以部门的方式 + * + * @param deviceInfoParam 终端查询条件 + * @param runFlag 终端状态 + * @param devModel 终端模型 + * @return 部门分类终端信息 + */ + public List getDeviceInfo(DeviceInfoParam deviceInfoParam, + List runFlag, + List devModel) { + //定义待返回终端信息 + List deviceInfos = new ArrayList<>(); + //初始化终端查询条件 + DeviceType deviceType = new DeviceType(); + if (CollectionUtil.isEmpty(devModel)) { + /** + * 终端模型(0:虚拟设备;1:实际设备;2:离线设备;)默认是实际设备 + */ + deviceType.setDevModel(null); + } else { + deviceType.setDevModel(devModel); + } + if (CollectionUtil.isEmpty(runFlag)) { + /** + * 终端状态(0:投运;1:热备用;2:停运) + */ + deviceType.setRunFlag(null); + } else { + deviceType.setRunFlag(runFlag); + } + if(ObjectUtil.isNotNull(deviceInfoParam.getComFlagStatus())){ + deviceType.setComFlag(Arrays.asList(deviceInfoParam.getComFlagStatus())); + } + filterDataType(deviceType, deviceInfoParam.getServerName()); + + // 初始化部门筛选条件 + List deptType = WebUtil.filterDeptType(); + // 获取包括当前部门的后代所有部门信息 + List deptInfos = deptService.getDeptDescendantIndexes(deviceInfoParam.getDeptIndex(), deptType); + // 过滤非直接后代部门,集合直接子部门 + List directDeptInfos = deptInfos.stream() + .filter(deptDTO -> deptDTO.getPid().equals(deviceInfoParam.getDeptIndex())).sorted(Comparator.comparing(DeptDTO::getSort)) + .collect(Collectors.toList()); + if (CollectionUtil.isEmpty(directDeptInfos)) { + // 没有直接子部门(树的最底层),获取当前部门所有信息 + List dept = deptInfos.stream() + .filter(deptDTO -> deptDTO.getId().equals(deviceInfoParam.getDeptIndex())) + .collect(Collectors.toList()); + deviceInfos.add(getGeneralDeviceInfo( + dept.get(0), + deviceType, + Collections.singletonList(deviceInfoParam.getDeptIndex()), + deviceInfoParam)); + } else { + for (DeptDTO directDeptDTO : directDeptInfos) { + //筛选pids包含该id的所有部门 直接子部门下属所有部门 + List descendantDeptDTO = deptInfos.stream() + .filter(d -> d.getPids().contains(directDeptDTO.getId())) + .collect(Collectors.toList()); + //形成需要查询监测点的部门索引 + List indexes = descendantDeptDTO.stream() + .map(DeptDTO::getId) + .distinct() + .collect(Collectors.toList()); + indexes.add(directDeptDTO.getId()); + GeneralDeviceDTO generalDeviceInfo = getGeneralDeviceInfo(directDeptDTO, deviceType, indexes, deviceInfoParam); + deviceInfos.add(generalDeviceInfo); + } + } + + + //判断统计类型 + if (deviceInfoParam.getStatisticalType() == null) { + deviceInfoParam.setStatisticalType(new SimpleDTO()); + } + StatisticsEnum statisticsEnum = StatisticsEnum.getStatisticsEnumByCode(deviceInfoParam.getStatisticalType().getCode()); + switch (statisticsEnum) { + case VOLTAGE_LEVEL: + return filterDataByScale(deviceInfos, deviceInfoParam.getScale()); + case LOAD_TYPE: + return filterDataByLoadType(deviceInfos, deviceInfoParam.getLoadType()); + case MANUFACTURER: + return filterDataByManufacturer(deviceInfos, deviceInfoParam.getManufacturer()); + case POWER_FLAG: + return filterDataByPowerFlag(deviceInfos, deviceInfoParam.getManufacturer()); + default: + return deviceInfos; + } + } + + /** + * 根据部门id集合获取监测点信息 + * + * @param directDeptDTO 入参deptIndex的直接子部门 + * @param deviceType + * @param ids 直接子部门以及后代部门id集合 + * @param deviceInfoParam + * @return + */ + private GeneralDeviceDTO getGeneralDeviceInfo(DeptDTO directDeptDTO, + DeviceType deviceType, + List ids, + DeviceInfoParam deviceInfoParam) { + GeneralDeviceDTO generalDeviceDTO = new GeneralDeviceDTO(); + generalDeviceDTO.setIndex(directDeptDTO.getId()); + // type:部门类型 0-非自定义;1-web自定义;2-App自定义;3-web测试 + if (directDeptDTO.getType() == 0) { + generalDeviceDTO.setName(directDeptDTO.getArea()); + } else { + generalDeviceDTO.setName(directDeptDTO.getName()); + } + // 根据部门ids集合查询是否绑定监测点 部门和监测点关联关系中间表:pq_dept_line 可以一对多 + List deptLines = deptLineService.selectDeptBindLines(ids); + // 返回空数据 + if (CollectionUtil.isEmpty(deptLines)) { + return generalDeviceDTO; + } + // 提取该部门及其子部门所有监测点id + List lineIds = deptLines.stream().map(DeptLine::getLineId).collect(Collectors.toList()); + // 获取line详细数据 :根据监测点id,获取所有监测点 联查 pq_line、pq_line_detail + List lines = terminalBaseService.getLineByCondition(lineIds, deviceInfoParam); + // 返回空数据 + if (CollectionUtil.isEmpty(lines)) { + return generalDeviceDTO; + } + + //1.筛选出母线id,理论上监测点的pids中第六个id为母线id 联查: pq_line t1 ,pq_voltage t2 + List voltageIds=lines.stream().map(Line::getPid).collect(Collectors.toList()); + //再根据电压等级筛选合法母线信息 + List voltages = terminalBaseService.getVoltageByCondition(voltageIds, deviceInfoParam.getScale()); + + //2.筛选出终端id,理论上监测点的pids中第五个id为终端id + List devIds=voltages.stream().map(Line::getPid).collect(Collectors.toList()); + // 再根据终端条件筛选合法终端信息 联查:pq_line t1,pq_device t2 + List devices = terminalBaseService.getDeviceByCondition(devIds, deviceType, deviceInfoParam.getManufacturer()); + + //3.筛选出变电站id,理论上监测点的pids中第四个id为变电站id 联查: pq_line t1 ,pq_substation t2 + List subIds=devices.stream().map(Line::getPid).collect(Collectors.toList()); + List sub = terminalBaseService.getSubByCondition(subIds, new ArrayList<>()); + + //筛选最终的数据 + dealDeviceData(generalDeviceDTO, lines, devices, voltages, sub); + return generalDeviceDTO; + } + /** + * 取多条件筛选后的交集索引,填充到部门统计中 + * + * @param generalDeviceDTO 部门信息 + * @param lines 筛选后的监测点信息 + * @param devices 筛选后的终端信息 + * @param voltages 筛选后的母线信息 + */ + private void dealDeviceData(GeneralDeviceDTO generalDeviceDTO, List lines, List devices, List voltages, List sub) { + List gdIndexes = new ArrayList<>(), subIndexes = new ArrayList<>(), deviceIndexes = new ArrayList<>(), voltageIndexes = new ArrayList<>(), lineIndexes = new ArrayList<>(); + List devIds = devices.stream().map(Line::getId).distinct().collect(Collectors.toList()); + List volIds = voltages.stream().map(Line::getId).distinct().collect(Collectors.toList()); + List subIds = sub.stream().map(Line::getId).distinct().collect(Collectors.toList()); + for (Line line : lines) { + String[] idsArray = line.getPids().split(","); + //监测点同时满足条件筛选后的终端、母线信息,才是最终的结果 + if (devIds.contains(idsArray[LineBaseEnum.DEVICE_LEVEL.getCode()]) && + volIds.contains(idsArray[LineBaseEnum.SUB_V_LEVEL.getCode()])&& + subIds.contains(idsArray[LineBaseEnum.SUB_LEVEL.getCode()]) + ) { + gdIndexes.add(idsArray[LineBaseEnum.GD_LEVEL.getCode()]); + subIndexes.add(idsArray[LineBaseEnum.SUB_LEVEL.getCode()]); + deviceIndexes.add(idsArray[LineBaseEnum.DEVICE_LEVEL.getCode()]); + voltageIndexes.add(idsArray[LineBaseEnum.SUB_V_LEVEL.getCode()]); + lineIndexes.add(line.getId()); + } + } + //排重,入参到终端综合体 + generalDeviceDTO.setGdIndexes(gdIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setSubIndexes(subIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setDeviceIndexes(deviceIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setVoltageIndexes(voltageIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setLineIndexes(lineIndexes.stream().distinct().collect(Collectors.toList())); + } + + /** + * 筛选数据类型 + */ + private void filterDataType(DeviceType deviceType, String serverName) { + ServerEnum serverEnum = EnumUtils.getServerEnumByName(serverName); + List dataType = new ArrayList<>(); + dataType.add(2); + switch (serverEnum) { + case EVENT: + dataType.add(0); + break; + case HARMONIC: + dataType.add(1); + break; + default: + dataType.add(0); + dataType.add(1); + break; + } + /** + * 数据类型(0:暂态系统;1:稳态系统;2:两个系统) + */ + deviceType.setDataType(dataType); + } + + private List filterDataByScale(List deviceInfos, List scales) { + List generalDeviceDTOS = new ArrayList<>(); + List subIds = new ArrayList<>(), lineIds = new ArrayList<>(); + for (GeneralDeviceDTO generalDeviceDTO : deviceInfos) { + subIds.addAll(generalDeviceDTO.getSubIndexes()); + lineIds.addAll(generalDeviceDTO.getLineIndexes()); + } + //如果电压等级集合为空,则查询所有的电压等级 + if (CollectionUtil.isEmpty(scales)) { + List scaleDictData = iDictDataService.getDicDataByTypeName(DicDataTypeEnum.DEV_VOLTAGE_STAND.getName()); + scales = scaleDictData.stream().map(dictData -> { + SimpleDTO simpleDTO = new SimpleDTO(); + BeanUtil.copyProperties(dictData, simpleDTO); + return simpleDTO; + }).collect(Collectors.toList()); + } + //监测点为空,则返回空的分类数据 + if (CollectionUtil.isEmpty(lineIds)) { + return assembleCommonData(scales); + } + List lines = terminalBaseService.getLineById(lineIds); + for (SimpleDTO simpleDTO : scales) { + List voltageScaleIds = terminalBaseService.getSubIdByScale(subIds, simpleDTO.getId()); + generalDeviceDTOS.add(assembleDataByLine(simpleDTO, lines, voltageScaleIds, LineBaseEnum.SUB_LEVEL.getCode())); + } + return generalDeviceDTOS; + } + + + private List filterDataByLoadType(List deviceInfos, List loadType) { + List generalDeviceDTOS = new ArrayList<>(); + List lineIds = new ArrayList<>(); + for (GeneralDeviceDTO generalDeviceDTO : deviceInfos) { + lineIds.addAll(generalDeviceDTO.getLineIndexes()); + } + //如果干扰源集合为空,则查询所有的电压等级 + if (CollectionUtil.isEmpty(loadType)) { + List scaleDictData = iDictDataService.getDicDataByTypeName(DicDataTypeEnum.INTERFERENCE_SOURCE_TYPE.getName()); + loadType = scaleDictData.stream().map(dictData -> { + SimpleDTO simpleDTO = new SimpleDTO(); + BeanUtil.copyProperties(dictData, simpleDTO); + return simpleDTO; + }).collect(Collectors.toList()); + } + //监测点为空,则返回空的分类数据 + if (CollectionUtil.isEmpty(lineIds)) { + return assembleCommonData(loadType); + } + List lines = terminalBaseService.getLineById(lineIds); + for (SimpleDTO simpleDTO : loadType) { + List lineLoadTypeIds = terminalBaseService.getLineIdByLoadType(lineIds, simpleDTO.getId()); + generalDeviceDTOS.add(assembleDataByLine(simpleDTO, lines, lineLoadTypeIds, LineBaseEnum.LINE_LEVEL.getCode())); + } + return generalDeviceDTOS; + } + + private List filterDataByManufacturer(List deviceInfos, List manufacturer) { + List generalDeviceDTOS = new ArrayList<>(); + List deviceIds = new ArrayList<>(), lineIds = new ArrayList<>(); + for (GeneralDeviceDTO generalDeviceDTO : deviceInfos) { + deviceIds.addAll(generalDeviceDTO.getDeviceIndexes()); + lineIds.addAll(generalDeviceDTO.getLineIndexes()); + } + //如果终端厂家集合为空,则查询所有的电压等级 + if (CollectionUtil.isEmpty(manufacturer)) { + List scaleDictData = iDictDataService.getDicDataByTypeName(DicDataTypeEnum.DEV_MANUFACTURER.getName()); + manufacturer = scaleDictData.stream().map(dictData -> { + SimpleDTO simpleDTO = new SimpleDTO(); + BeanUtil.copyProperties(dictData, simpleDTO); + return simpleDTO; + }).collect(Collectors.toList()); + } + //监测点为空,则返回空的分类数据 + if (CollectionUtil.isEmpty(lineIds)) { + return assembleCommonData(manufacturer); + } + List lines = terminalBaseService.getLineById(lineIds); + for (SimpleDTO simpleDTO : manufacturer) { + List voltageScaleIds = terminalBaseService.getDeviceIdByManufacturer(deviceIds, simpleDTO.getId()); + generalDeviceDTOS.add(assembleDataByLine(simpleDTO, lines, voltageScaleIds, LineBaseEnum.DEVICE_LEVEL.getCode())); + } + return generalDeviceDTOS; + } + + private List filterDataByPowerFlag(List deviceInfos, List manufacturer) { + List generalDeviceDTOS = new ArrayList<>(); + List deviceIds = deviceInfos.stream().flatMap(x->x.getLineIndexes().stream()).collect(Collectors.toList()); + List lineIds = deviceInfos.stream().flatMap(x->x.getLineIndexes().stream()).collect(Collectors.toList()); + //监测点为空,则返回空的分类数据 + if (CollectionUtil.isEmpty(lineIds)) { + return assembleCommonData(manufacturer); + } + SimpleDTO dto; + List lines = terminalBaseService.getLineById(lineIds); + for (int i = 0; i < 6; i++) { + List powerFlagIds = terminalBaseService.getDeviceIdByPowerFlag(deviceIds, i); + dto=new SimpleDTO(); + PowerFlagEnum enumByCode = PowerFlagEnum.getPowerFlagEnumByCode(i); + dto.setId(enumByCode.getCode().toString()); + dto.setName(enumByCode.getMessage()); + generalDeviceDTOS.add(assembleDataByLine(dto, lines, powerFlagIds, LineBaseEnum.LINE_LEVEL.getCode())); + } + + return generalDeviceDTOS; + } + + + /** + * 筛选对应等级的id + * + * @param simpleDTO 分类信息 + * @param lines 所有监测点 + * @param keyIds 待筛选的id + * @param level 待筛选的层级 + */ + private GeneralDeviceDTO assembleDataByLine(SimpleDTO simpleDTO, List lines, List keyIds, Integer level) { + GeneralDeviceDTO generalDeviceDTO = assembleData(simpleDTO); + if (CollectionUtil.isNotEmpty(keyIds)) { + List tempLines = lines.stream().filter(line -> { + String[] idsArray = line.getPids().split(","); + if (level.equals(LineBaseEnum.LINE_LEVEL.getCode())) { + return keyIds.contains(line.getId()); + } else { + return keyIds.contains(idsArray[level]); + } + }).collect(Collectors.toList()); + List gdIndexes = new ArrayList<>(), subIndexes = new ArrayList<>(), deviceIndexes = new ArrayList<>(), voltageIndexes = new ArrayList<>(), lineIndexes = new ArrayList<>(); + for (Line line : tempLines) { + String[] idsArray = line.getPids().split(","); + gdIndexes.add(idsArray[LineBaseEnum.GD_LEVEL.getCode()]); + subIndexes.add(idsArray[LineBaseEnum.SUB_LEVEL.getCode()]); + deviceIndexes.add(idsArray[LineBaseEnum.DEVICE_LEVEL.getCode()]); + voltageIndexes.add(idsArray[LineBaseEnum.SUB_V_LEVEL.getCode()]); + lineIndexes.add(line.getId()); + } + //排重,入参到终端综合体 + generalDeviceDTO.setGdIndexes(gdIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setSubIndexes(subIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setDeviceIndexes(deviceIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setVoltageIndexes(voltageIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setLineIndexes(lineIndexes.stream().distinct().collect(Collectors.toList())); + } + return generalDeviceDTO; + } + + + /** + * 当该部门不存在监测点时,返回空的分类数据 + * + * @param simpleDTO 基础数据 + * @return . + */ + private GeneralDeviceDTO assembleData(SimpleDTO simpleDTO) { + GeneralDeviceDTO generalDeviceDTO = new GeneralDeviceDTO(); + generalDeviceDTO.setName(simpleDTO.getName()); + generalDeviceDTO.setIndex(simpleDTO.getId()); + return generalDeviceDTO; + } + + /** + * 当该部门不存在监测点时,返回空的分类数据 + * + * @param simpleDTOS 分类类别 + * @return . + */ + private List assembleCommonData(List simpleDTOS) { + return simpleDTOS.stream().map(this::assembleData).collect(Collectors.toList()); + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/LineServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/LineServiceImpl.java new file mode 100644 index 0000000..4fcf198 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/LineServiceImpl.java @@ -0,0 +1,134 @@ +package com.njcn.product.device.ledger.service.impl; + + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + + +import com.njcn.product.carrycapacity.util.PubUtils; +import com.njcn.product.device.ledger.mapper.*; +import com.njcn.product.device.ledger.pojo.po.Device; +import com.njcn.product.device.ledger.pojo.po.Line; +import com.njcn.product.device.ledger.pojo.po.LineDetail; +import com.njcn.product.device.ledger.pojo.vo.LineDetailDataVO; +import com.njcn.product.device.ledger.pojo.vo.LineOverLimitVO; +import com.njcn.product.device.ledger.service.LineService; +import com.njcn.product.device.overlimit.pojo.Overlimit; +import com.njcn.product.system.dict.service.IDictDataService; +import com.njcn.web.pojo.vo.LineDataVO; +import com.njcn.web.utils.GeneralUtil; +import com.njcn.web.utils.RequestUtil; +import com.njcn.web.utils.WebUtil; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 监测点类 + * + * @author denghuajun + * @date 2022/2/23 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class LineServiceImpl extends ServiceImpl implements LineService { + + + private final IDictDataService iDictDataService; + + private final VoltageMapper voltageMapper; + + private final LineDetailMapper lineDetailMapper; + + private final DeviceMapper deviceMapper; + + private final OverlimitMapper overlimitMapper; + + @Override + public Overlimit getOverLimitData(String id) { + return overlimitMapper.selectById(id); + } + + + @Override + public LineDetailDataVO getLineDetailData(String id) { + if (StringUtils.isEmpty(id)) { + return new LineDetailDataVO(); + } else { + //根据id查询当前信息的pids + List pids = Arrays.asList(this.baseMapper.selectById(id).getPids().split(",")); + List list = new ArrayList(pids); + list.add(id); + List lineDataVOList = this.baseMapper.getLineDetail(list); + LineDetailDataVO lineDetailDataVO = new LineDetailDataVO(); + String areaId = "", devId = "", voId = ""; + for (LineDataVO lineDataVO : lineDataVOList) { + switch (lineDataVO.getLevel()) { + case 1: + areaId = lineDataVO.getName(); + break; + case 2: + lineDetailDataVO.setGdName(lineDataVO.getName()); + break; + case 3: + lineDetailDataVO.setBdName(lineDataVO.getName()); + break; + case 4: + devId = lineDataVO.getId(); + lineDetailDataVO.setDevName(lineDataVO.getName()); + break; + case 5: + voId = lineDataVO.getId(); + break; + case 6: + lineDetailDataVO.setLineName(lineDataVO.getName()); + break; + default: + break; + } + } + lineDetailDataVO.setScale(iDictDataService.getDicDataById(voltageMapper.selectById(voId).getScale()).getName()); + LineDetail lineDetail = lineDetailMapper.selectById(id); + Device device = deviceMapper.selectById(devId); + lineDetailDataVO.setManufacturer(iDictDataService.getDicDataById(device.getManufacturer()).getName()); + lineDetailDataVO.setComFlag(PubUtils.comFlag(device.getComFlag())); + lineDetailDataVO.setRunFlag(PubUtils.lineRunFlag(lineDetail.getRunFlag())); + lineDetailDataVO.setIp(device.getIp()); + lineDetailDataVO.setLoginTime(device.getLoginTime()); + lineDetailDataVO.setDevId(device.getId()); + lineDetailDataVO.setBusinessType(iDictDataService.getDicDataById(lineDetail.getBusinessType()).getName()); + lineDetailDataVO.setLoadType(iDictDataService.getDicDataById(lineDetail.getLoadType()).getName()); + lineDetailDataVO.setObjName(lineDetail.getObjName()); + lineDetailDataVO.setId(lineDetail.getNum()); + lineDetailDataVO.setPtType(PubUtils.ptType(lineDetail.getPtType())); + lineDetailDataVO.setPt(lineDetail.getPt1() + "/" + lineDetail.getPt2()); + lineDetailDataVO.setCt(lineDetail.getCt1() + "/" + lineDetail.getCt2()); + lineDetailDataVO.setDealCapacity(lineDetail.getDealCapacity()); + lineDetailDataVO.setDevCapacity(lineDetail.getDevCapacity()); + lineDetailDataVO.setShortCapacity(lineDetail.getShortCapacity()); + lineDetailDataVO.setStandardCapacity(lineDetail.getStandardCapacity()); + lineDetailDataVO.setTimeInterval(lineDetail.getTimeInterval()); + lineDetailDataVO.setOwner(lineDetail.getOwner()); + lineDetailDataVO.setOwnerDuty(lineDetail.getOwnerDuty()); + lineDetailDataVO.setOwnerTel(lineDetail.getOwnerTel()); + lineDetailDataVO.setWiringDiagram(lineDetail.getWiringDiagram()); + lineDetailDataVO.setPtPhaseType(lineDetail.getPtPhaseType()); + lineDetailDataVO.setUpdateTime(device.getUpdateTime()); + return lineDetailDataVO; + } + + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/TerminalBaseServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/TerminalBaseServiceImpl.java new file mode 100644 index 0000000..cdfe264 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/TerminalBaseServiceImpl.java @@ -0,0 +1,135 @@ +package com.njcn.product.device.ledger.service.impl; + +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.dto.SimpleDTO; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.LogUtil; +import com.njcn.common.utils.PubUtils; + +import com.njcn.product.device.ledger.mapper.*; +import com.njcn.product.device.ledger.pojo.dto.DeviceType; +import com.njcn.product.device.ledger.pojo.enums.LineBaseEnum; +import com.njcn.product.device.ledger.pojo.param.DeviceInfoParam; +import com.njcn.product.device.ledger.pojo.po.Device; +import com.njcn.product.device.ledger.pojo.po.Line; +import com.njcn.product.device.ledger.pojo.po.LineDetail; +import com.njcn.product.device.ledger.pojo.po.Voltage; +import com.njcn.product.device.ledger.service.TerminalBaseService; +import com.njcn.product.device.overlimit.pojo.Overlimit; +import com.njcn.product.system.dict.enums.DicDataTypeEnum; +import com.njcn.product.system.dict.pojo.po.DictData; +import com.njcn.redis.utils.RedisUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.net.HttpURLConnection; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * pqs + * + * @author cdf + * @date 2022/1/4 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TerminalBaseServiceImpl extends ServiceImpl implements TerminalBaseService { + + + + @Override + public List getLineById(List lineIds) { + return this.lambdaQuery() + .in(!CollectionUtils.isEmpty(lineIds),Line::getId, lineIds) + .eq(Line::getLevel, 6) + .eq(Line::getState, DataStateEnum.ENABLE.getCode()) + .list(); + } + + @Override + public List getLineByCondition(List ids, DeviceInfoParam deviceInfoParam) { + return this.baseMapper.getLineByCondition(ids, deviceInfoParam); + } + + + + + + @Override + public List getDeviceByCondition(List devIds, DeviceType deviceType, List manufacturer) { + return this.baseMapper.getDeviceByCondition(devIds, deviceType, manufacturer); + } + + @Override + public List getVoltageByCondition(List voltageIds, List scale) { + return this.baseMapper.getVoltageByCondition(voltageIds, scale); + } + + @Override + public List getSubByCondition(List subIds, List scale) { + return this.baseMapper.getSubByCondition(subIds, scale); + } + + @Override + public List getVoltageIdByScale(List voltageIds, String scale) { + return this.baseMapper.getVoltageIdByScale(voltageIds, scale); + } + + @Override + public List getSubIdByScale(List subIds, String scale) { + return this.baseMapper.getSubIdByScale(subIds, scale); + } + + @Override + public List getLineIdByLoadType(List lineIds, String loadType) { + return this.baseMapper.getLineIdByLoadType(lineIds, loadType); + } + + @Override + public List getDeviceIdByManufacturer(List deviceIds, String manufacturer) { + return this.baseMapper.getDeviceIdByManufacturer(deviceIds, manufacturer); + } + + @Override + public List getDeviceIdByPowerFlag(List lineIds, Integer manufacturer) { + return this.baseMapper.getDeviceIdByPowerFlag(lineIds, manufacturer); + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/TerminalTreeServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/TerminalTreeServiceImpl.java new file mode 100644 index 0000000..db6b511 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/ledger/service/impl/TerminalTreeServiceImpl.java @@ -0,0 +1,158 @@ +package com.njcn.product.device.ledger.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.njcn.common.pojo.enums.common.ServerEnum; +import com.njcn.common.pojo.response.HttpResult; + +import com.njcn.product.device.ledger.mapper.TreeMapper; +import com.njcn.product.device.ledger.pojo.dto.GeneralDeviceDTO; +import com.njcn.product.device.ledger.pojo.dto.TerminalTree; +import com.njcn.product.device.ledger.pojo.enums.LineBaseEnum; +import com.njcn.product.device.ledger.pojo.enums.StatisticsEnum; +import com.njcn.product.device.ledger.pojo.param.DeviceInfoParam; +import com.njcn.product.device.ledger.service.TerminalTreeService; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * pqs + * 终端设备树 + * + * @author cdf + * @date 2021/7/19 + */ +@Service +@AllArgsConstructor +public class TerminalTreeServiceImpl implements TerminalTreeService { + + + + private final GeneralDeviceService generalDeviceService; + + private final TreeMapper treeMapper; + + + /** + * 5层树排除设备 母线监测点合并 + * + * @author cdf + * @date 2022/1/13 + */ + /** + * 5层树排除设备 母线监测点合并 + * + * @author cdf + * @date 2022/1/13 + */ + @Override + public List getTerminalTreeForFive(DeviceInfoParam deviceInfoParam) { + //deviceInfoParam.setDeptIndex(RequestUtil.getDeptIndex()); + // 获取所有数据 + List generalDeviceDTOList = generalDeviceService.getDeviceInfo(deviceInfoParam, Stream.of(0).collect(Collectors.toList()), Stream.of(1).collect(Collectors.toList())); + // 判断所有数据集合状态 + if (CollectionUtil.isNotEmpty(generalDeviceDTOList)) { + // 创建集合 + List taiZhang = new ArrayList<>(); + // 遍历集合 + for (GeneralDeviceDTO generalDeviceDTO : generalDeviceDTOList) { + // 创建实体类 + TerminalTree terminalTree = new TerminalTree(); + // 判断监测点索引集合状态 + if (CollectionUtils.isEmpty(generalDeviceDTO.getLineIndexes())) { + continue; + } + // 通过供电公司索引查询省会 + List proList = treeMapper.getProvinceList(generalDeviceDTO.getGdIndexes()); + // 通过供电公司索引查询供电公司信息 + List gdList = treeMapper.getGdList(generalDeviceDTO.getGdIndexes()); + // 通过供电站索引查询供电站信息 + List subList = treeMapper.getSubList(generalDeviceDTO.getSubIndexes()); + // 通过监测点索引查询监测点信息 + List lineList = treeMapper.getLineList(generalDeviceDTO.getLineIndexes()); + + //处理变电站 + dealChildrenData(subList, lineList, true); + + //监测点前面加序号,后面不需要删除下面两行就行 + //Integer[] arr = {1}; + //subList.forEach(item->item.getChildren().forEach(it->it.setName((arr[0]++ +"_"+it.getName())))); + //处理供电公司 + dealChildrenData(gdList, subList, false); + + if (deviceInfoParam.getStatisticalType().getCode().equalsIgnoreCase(StatisticsEnum.POWER_NETWORK.getCode())) { + terminalTree.setChildren(gdList); + } else { + //还需要额外处理省会 + dealChildrenData(proList, gdList, false); + terminalTree.setChildren(proList); + } + terminalTree.setId(generalDeviceDTO.getIndex()); + terminalTree.setName(generalDeviceDTO.getName()); + terminalTree.setLevel(0); + taiZhang.add(terminalTree); + } + return taiZhang; + } else { + return new ArrayList<>(); + } + } + + /** + * 处理变电站 + * + * @param targetData + * @param childrenData + * @param isLine + */ + private void dealChildrenData(List targetData, List childrenData, boolean isLine) { + // 创建一个map集合,用于封装对象 + Map> groupLine; + if (isLine) { + // 通过stream流分组 + groupLine = childrenData.stream().collect(Collectors.groupingBy(terminalTree -> { + // 获取父id字符串,通过 逗号 分割 成一个数组 + String[] pid = terminalTree.getPids().split(","); + return pid[LineBaseEnum.SUB_LEVEL.getCode()]; + })); + } else { + groupLine = childrenData.stream().collect(Collectors.groupingBy(TerminalTree::getPid)); + } + //变电站 + targetData = targetData.stream().peek(terminalTree -> { + System.out.println(groupLine.get(terminalTree.getId())); + System.out.println(terminalTree.getId()); + List terminalTrees = groupLine.get(terminalTree.getId()).stream().sorted(Comparator.comparing(TerminalTree::getSort)).collect(Collectors.toList()); + if (isLine) { + //变电站集合 + int size = terminalTrees.stream().map(x -> { + // 获取父id字符串,通过 逗号 分割 成一个数组 + String[] pid = x.getPids().split(","); + return pid[LineBaseEnum.DEVICE_LEVEL.getCode()]; + }).distinct().collect(Collectors.toList()).size(); + + terminalTree.setName(terminalTree.getName() + "(" + size + "台装置)"); + + terminalTree.setChildren(terminalTrees); + } else { + terminalTree.setChildren(groupLine.get(terminalTree.getId())); + } + }).collect(Collectors.toList()); + } + + + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/overlimit/pojo/Overlimit.java b/carry_capacity/src/main/java/com/njcn/product/device/overlimit/pojo/Overlimit.java new file mode 100644 index 0000000..61e7099 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/overlimit/pojo/Overlimit.java @@ -0,0 +1,952 @@ +package com.njcn.product.device.overlimit.pojo; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@TableName("pq_overlimit") +public class Overlimit implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 监测点序号 + */ + private String id; + + /** + * 频率限值 + */ + private Float freqDev; + + /** + * 电压波动 + */ + private Float voltageFluctuation; + + /** + * 电压上偏差限值 + */ + private Float voltageDev; + + /** + * 电压下偏差限值 + */ + private Float uvoltageDev; + + /** + * 三相电压不平衡度限值 + */ + private Float ubalance; + + /** + * 短时电压不平衡度限值 + */ + private Float shortUbalance; + + /** + * 闪变限值 + */ + private Float flicker; + + /** + * 电压总谐波畸变率限值 + */ + private Float uaberrance; + + /** + * 负序电流限值 + */ + private Float iNeg; + + /** + * 2次谐波电压限值 + */ + @TableField("uharm_2") + private Float uharm2; + + /** + * 3次谐波电压限值 + */ + @TableField("uharm_3") + private Float uharm3; + + /** + * 4次谐波电压限值 + */ + @TableField("uharm_4") + private Float uharm4; + + /** + * 5次谐波电压限值 + */ + @TableField("uharm_5") + private Float uharm5; + + /** + * 6次谐波电压限值 + */ + @TableField("uharm_6") + private Float uharm6; + + /** + * 7次谐波电压限值 + */ + @TableField("uharm_7") + private Float uharm7; + + /** + * 8次谐波电压限值 + */ + @TableField("uharm_8") + private Float uharm8; + + /** + * 9次谐波电压限值 + */ + @TableField("uharm_9") + private Float uharm9; + + /** + * 10次谐波电压限值 + */ + @TableField("uharm_10") + private Float uharm10; + + /** + * 11次谐波电压限值 + */ + @TableField("uharm_11") + private Float uharm11; + + /** + * 12次谐波电压限值 + */ + @TableField("uharm_12") + private Float uharm12; + + /** + * 13次谐波电压限值 + */ + @TableField("uharm_13") + private Float uharm13; + + /** + * 14次谐波电压限值 + */ + @TableField("uharm_14") + private Float uharm14; + + /** + * 15次谐波电压限值 + */ + @TableField("uharm_15") + private Float uharm15; + + /** + * 16次谐波电压限值 + */ + @TableField("uharm_16") + private Float uharm16; + + /** + * 17次谐波电压限值 + */ + @TableField("uharm_17") + private Float uharm17; + + /** + * 18次谐波电压限值 + */ + @TableField("uharm_18") + private Float uharm18; + + /** + * 19次谐波电压限值 + */ + @TableField("uharm_19") + private Float uharm19; + + /** + * 20次谐波电压限值 + */ + @TableField("uharm_20") + private Float uharm20; + + /** + * 21次谐波电压限值 + */ + @TableField("uharm_21") + private Float uharm21; + + /** + * 22次谐波电压限值 + */ + @TableField("uharm_22") + private Float uharm22; + + /** + * 23次谐波电压限值 + */ + @TableField("uharm_23") + private Float uharm23; + + /** + * 24次谐波电压限值 + */ + @TableField("uharm_24") + private Float uharm24; + + /** + * 25次谐波电压限值 + */ + @TableField("uharm_25") + private Float uharm25; + + /** + * 2次谐波电压限值 + */ + @TableField("uharm_26") + private Float uharm26; + + /** + * 3次谐波电压限值 + */ + @TableField("uharm_27") + private Float uharm27; + + /** + * 4次谐波电压限值 + */ + @TableField("uharm_28") + private Float uharm28; + + /** + * 5次谐波电压限值 + */ + @TableField("uharm_29") + private Float uharm29; + + /** + * 6次谐波电压限值 + */ + @TableField("uharm_30") + private Float uharm30; + + /** + * 7次谐波电压限值 + */ + @TableField("uharm_31") + private Float uharm31; + + /** + * 8次谐波电压限值 + */ + @TableField("uharm_32") + private Float uharm32; + + /** + * 9次谐波电压限值 + */ + @TableField("uharm_33") + private Float uharm33; + + /** + * 10次谐波电压限值 + */ + @TableField("uharm_34") + private Float uharm34; + + /** + * 11次谐波电压限值 + */ + @TableField("uharm_35") + private Float uharm35; + + /** + * 12次谐波电压限值 + */ + @TableField("uharm_36") + private Float uharm36; + + /** + * 13次谐波电压限值 + */ + @TableField("uharm_37") + private Float uharm37; + + /** + * 14次谐波电压限值 + */ + @TableField("uharm_38") + private Float uharm38; + + /** + * 15次谐波电压限值 + */ + @TableField("uharm_39") + private Float uharm39; + + /** + * 16次谐波电压限值 + */ + @TableField("uharm_40") + private Float uharm40; + + /** + * 17次谐波电压限值 + */ + @TableField("uharm_41") + private Float uharm41; + + /** + * 18次谐波电压限值 + */ + @TableField("uharm_42") + private Float uharm42; + + /** + * 19次谐波电压限值 + */ + @TableField("uharm_43") + private Float uharm43; + + /** + * 20次谐波电压限值 + */ + @TableField("uharm_44") + private Float uharm44; + + /** + * 21次谐波电压限值 + */ + @TableField("uharm_45") + private Float uharm45; + + /** + * 22次谐波电压限值 + */ + @TableField("uharm_46") + private Float uharm46; + + /** + * 23次谐波电压限值 + */ + @TableField("uharm_47") + private Float uharm47; + + /** + * 24次谐波电压限值 + */ + @TableField("uharm_48") + private Float uharm48; + + /** + * 25次谐波电压限值 + */ + @TableField("uharm_49") + private Float uharm49; + + /** + * 50次谐波电压限值 + */ + @TableField("uharm_50") + private Float uharm50; + + + + /** + * 2次谐波电流限值 + */ + @TableField("iharm_2") + private Float iharm2; + + /** + * 3次谐波电流限值 + */ + @TableField("iharm_3") + private Float iharm3; + + /** + * 4次谐波电流限值 + */ + @TableField("iharm_4") + private Float iharm4; + + /** + * 5次谐波电流限值 + */ + @TableField("iharm_5") + private Float iharm5; + + /** + * 6次谐波电流限值 + */ + @TableField("iharm_6") + private Float iharm6; + + /** + * 7次谐波电流限值 + */ + @TableField("iharm_7") + private Float iharm7; + + /** + * 8次谐波电流限值 + */ + @TableField("iharm_8") + private Float iharm8; + + /** + * 9次谐波电流限值 + */ + @TableField("iharm_9") + private Float iharm9; + + /** + * 10次谐波电流限值 + */ + @TableField("iharm_10") + private Float iharm10; + + /** + * 11次谐波电流限值 + */ + @TableField("iharm_11") + private Float iharm11; + + /** + * 12次谐波电流限值 + */ + @TableField("iharm_12") + private Float iharm12; + + /** + * 13次谐波电流限值 + */ + @TableField("iharm_13") + private Float iharm13; + + /** + * 14次谐波电流限值 + */ + @TableField("iharm_14") + private Float iharm14; + + /** + * 15次谐波电流限值 + */ + @TableField("iharm_15") + private Float iharm15; + + /** + * 16次谐波电流限值 + */ + @TableField("iharm_16") + private Float iharm16; + + /** + * 17次谐波电流限值 + */ + @TableField("iharm_17") + private Float iharm17; + + /** + * 18次谐波电流限值 + */ + @TableField("iharm_18") + private Float iharm18; + + /** + * 19次谐波电流限值 + */ + @TableField("iharm_19") + private Float iharm19; + + /** + * 20次谐波电流限值 + */ + @TableField("iharm_20") + private Float iharm20; + + /** + * 21次谐波电流限值 + */ + @TableField("iharm_21") + private Float iharm21; + + /** + * 22次谐波电流限值 + */ + @TableField("iharm_22") + private Float iharm22; + + /** + * 23次谐波电流限值 + */ + @TableField("iharm_23") + private Float iharm23; + + /** + * 24次谐波电流限值 + */ + @TableField("iharm_24") + private Float iharm24; + + /** + * 25次谐波电流限值 + */ + @TableField("iharm_25") + private Float iharm25; + + /** + * 2次谐波电压限值 + */ + @TableField("iharm_26") + private Float iharm26; + + /** + * 3次谐波电压限值 + */ + @TableField("iharm_27") + private Float iharm27; + + /** + * 4次谐波电压限值 + */ + @TableField("iharm_28") + private Float iharm28; + + /** + * 5次谐波电压限值 + */ + @TableField("iharm_29") + private Float iharm29; + + /** + * 6次谐波电压限值 + */ + @TableField("iharm_30") + private Float iharm30; + + /** + * 7次谐波电压限值 + */ + @TableField("iharm_31") + private Float iharm31; + + /** + * 8次谐波电压限值 + */ + @TableField("iharm_32") + private Float iharm32; + + /** + * 9次谐波电压限值 + */ + @TableField("iharm_33") + private Float iharm33; + + /** + * 10次谐波电压限值 + */ + @TableField("iharm_34") + private Float iharm34; + + /** + * 11次谐波电压限值 + */ + @TableField("iharm_35") + private Float iharm35; + + /** + * 12次谐波电压限值 + */ + @TableField("iharm_36") + private Float iharm36; + + /** + * 13次谐波电压限值 + */ + @TableField("iharm_37") + private Float iharm37; + + /** + * 14次谐波电压限值 + */ + @TableField("iharm_38") + private Float iharm38; + + /** + * 15次谐波电压限值 + */ + @TableField("iharm_39") + private Float iharm39; + + /** + * 16次谐波电压限值 + */ + @TableField("iharm_40") + private Float iharm40; + + /** + * 17次谐波电压限值 + */ + @TableField("iharm_41") + private Float iharm41; + + /** + * 18次谐波电压限值 + */ + @TableField("iharm_42") + private Float iharm42; + + /** + * 19次谐波电压限值 + */ + @TableField("iharm_43") + private Float iharm43; + + /** + * 20次谐波电压限值 + */ + @TableField("iharm_44") + private Float iharm44; + + /** + * 21次谐波电压限值 + */ + @TableField("iharm_45") + private Float iharm45; + + /** + * 22次谐波电压限值 + */ + @TableField("iharm_46") + private Float iharm46; + + /** + * 23次谐波电压限值 + */ + @TableField("iharm_47") + private Float iharm47; + + /** + * 24次谐波电压限值 + */ + @TableField("iharm_48") + private Float iharm48; + + /** + * 25次谐波电压限值 + */ + @TableField("iharm_49") + private Float iharm49; + + /** + * 50次谐波电压限值 + */ + @TableField("iharm_50") + private Float iharm50; + + + + /** + * 0.5次间谐波电压限值 + */ + @TableField("inuharm_1") + private Float inuharm1; + + /** + * 1.5次间谐波电压限值 + */ + @TableField("inuharm_2") + private Float inuharm2; + + /** + * 2.5次间谐波电压限值 + */ + @TableField("inuharm_3") + private Float inuharm3; + + /** + * 3.5次间谐波电压限值 + */ + @TableField("inuharm_4") + private Float inuharm4; + + /** + * 4.5次间谐波电压限值 + */ + @TableField("inuharm_5") + private Float inuharm5; + + /** + * 5.5次间谐波电压限值 + */ + @TableField("inuharm_6") + private Float inuharm6; + + /** + * 6.5次间谐波电压限值 + */ + @TableField("inuharm_7") + private Float inuharm7; + + /** + * 7.5次间谐波电压限值 + */ + @TableField("inuharm_8") + private Float inuharm8; + + /** + * 8.5次间谐波电压限值 + */ + @TableField("inuharm_9") + private Float inuharm9; + + /** + * 9.5次间谐波电压限值 + */ + @TableField("inuharm_10") + private Float inuharm10; + + /** + * 10.5次间谐波电压限值 + */ + @TableField("inuharm_11") + private Float inuharm11; + + /** + * 11.5次间谐波电压限值 + */ + @TableField("inuharm_12") + private Float inuharm12; + + /** + * 12.5次间谐波电压限值 + */ + @TableField("inuharm_13") + private Float inuharm13; + + /** + * 13.5次间谐波电压限值 + */ + @TableField("inuharm_14") + private Float inuharm14; + + /** + * 14.5次间谐波电压限值 + */ + @TableField("inuharm_15") + private Float inuharm15; + + /** + * 15.5次间谐波电压限值 + */ + @TableField("inuharm_16") + private Float inuharm16; + + public Overlimit(){} + + +// public Overlimit(String lineId, String scaTmp, float fDLRL, float fJZRL, float fXYRL, float fSBRL){ +// float[] fLimit = COverlimit.GetOverLimit(scaTmp, fDLRL, fJZRL, fXYRL, fSBRL); +// this.id=lineId; +// this.freqDev=fLimit[0]; +// this.voltageDev=fLimit[1]; +// this.ubalance=fLimit[2]; +// this.flicker=fLimit[3]; +// this.uaberrance=fLimit[4]; +// this.uharm2=fLimit[5]; +// this.uharm3=fLimit[6]; +// this.uharm4=fLimit[7]; +// this.uharm5=fLimit[8]; +// this.uharm6=fLimit[9]; +// this.uharm7=fLimit[10]; +// this.uharm8=fLimit[11]; +// this.uharm9=fLimit[12]; +// this.uharm10=fLimit[13]; +// this.uharm11=fLimit[14]; +// this.uharm12=fLimit[15]; +// this.uharm13=fLimit[16]; +// this.uharm14=fLimit[17]; +// this.uharm15=fLimit[18]; +// this.uharm16=fLimit[19]; +// this.uharm17=fLimit[20]; +// this.uharm18=fLimit[21]; +// this.uharm19=fLimit[22]; +// this.uharm20=fLimit[23]; +// this.uharm21=fLimit[24]; +// this.uharm22=fLimit[25]; +// this.uharm23=fLimit[26]; +// this.uharm24=fLimit[27]; +// this.uharm25=fLimit[28]; +// this.iharm2=fLimit[29]; +// this.iharm3=fLimit[30]; +// this.iharm4=fLimit[31]; +// this.iharm5=fLimit[32]; +// this.iharm6=fLimit[33]; +// this.iharm7=fLimit[34]; +// this.iharm8=fLimit[35]; +// this.iharm9=fLimit[36]; +// this.iharm10=fLimit[37]; +// this.iharm11=fLimit[38]; +// this.iharm12=fLimit[39]; +// this.iharm13=fLimit[40]; +// this.iharm14=fLimit[41]; +// this.iharm15=fLimit[42]; +// this.iharm16=fLimit[43]; +// this.iharm17=fLimit[44]; +// this.iharm18=fLimit[45]; +// this.iharm19=fLimit[46]; +// this.iharm20=fLimit[47]; +// this.iharm21=fLimit[48]; +// this.iharm22=fLimit[49]; +// this.iharm23=fLimit[50]; +// this.iharm24=fLimit[51]; +// this.iharm25=fLimit[52]; +// this.uvoltageDev=fLimit[53]; +// this.iNeg=fLimit[54]; +// this.inuharm1=fLimit[55]; +// this.inuharm2=fLimit[56]; +// this.inuharm3=fLimit[57]; +// this.inuharm4=fLimit[58]; +// this.inuharm5=fLimit[59]; +// this.inuharm6=fLimit[60]; +// this.inuharm7=fLimit[61]; +// this.inuharm8=fLimit[62]; +// this.inuharm9=fLimit[63]; +// this.inuharm10=fLimit[64]; +// this.inuharm11=fLimit[65]; +// this.inuharm12=fLimit[66]; +// this.inuharm13=fLimit[67]; +// this.inuharm14=fLimit[68]; +// this.inuharm15=fLimit[69]; +// this.inuharm16=fLimit[70]; +// } + + public void buildIHarm(Float[] iHarmTem){ + this.iharm2= iHarmTem[0]; + this.iharm4= iHarmTem[2]; + this.iharm6= iHarmTem[4]; + this.iharm8= iHarmTem[6]; + this.iharm10= iHarmTem[8]; + this.iharm12= iHarmTem[10]; + this.iharm14= iHarmTem[12]; + this.iharm16= iHarmTem[14]; + this.iharm18= iHarmTem[16]; + this.iharm20= iHarmTem[18]; + this.iharm22= iHarmTem[20]; + this.iharm24= iHarmTem[22]; + this.iharm26= iHarmTem[24]; + this.iharm28= iHarmTem[26]; + this.iharm30= iHarmTem[28]; + this.iharm32= iHarmTem[30]; + this.iharm34= iHarmTem[32]; + this.iharm36= iHarmTem[34]; + this.iharm38= iHarmTem[36]; + this.iharm40= iHarmTem[38]; + this.iharm42= iHarmTem[40]; + this.iharm44= iHarmTem[42]; + this.iharm46= iHarmTem[44]; + this.iharm48= iHarmTem[46]; + this.iharm50= iHarmTem[48]; + + + + this.iharm3= iHarmTem[1]; + this.iharm5= iHarmTem[3]; + this.iharm7= iHarmTem[5]; + this.iharm9= iHarmTem[7]; + this.iharm11= iHarmTem[9]; + this.iharm13= iHarmTem[11]; + this.iharm15= iHarmTem[13]; + this.iharm17= iHarmTem[15]; + this.iharm19= iHarmTem[17]; + this.iharm21= iHarmTem[19]; + this.iharm23= iHarmTem[21]; + this.iharm25= iHarmTem[23]; + this.iharm27= iHarmTem[25]; + this.iharm29= iHarmTem[27]; + this.iharm31= iHarmTem[29]; + this.iharm33= iHarmTem[31]; + this.iharm35= iHarmTem[33]; + this.iharm37= iHarmTem[35]; + this.iharm39= iHarmTem[37]; + this.iharm41= iHarmTem[39]; + this.iharm43= iHarmTem[41]; + this.iharm45= iHarmTem[43]; + this.iharm47= iHarmTem[45]; + this.iharm49= iHarmTem[47]; + } + + public void buildUharm(Float resultEven,Float resultOdd){ + this.uharm2=resultEven; + this.uharm4=resultEven; + this.uharm6=resultEven; + this.uharm8=resultEven; + this.uharm10=resultEven; + this.uharm12=resultEven; + this.uharm14=resultEven; + this.uharm16=resultEven; + this.uharm18=resultEven; + this.uharm20=resultEven; + this.uharm22=resultEven; + this.uharm24=resultEven; + this.uharm26=resultEven; + this.uharm28=resultEven; + this.uharm30=resultEven; + this.uharm32=resultEven; + this.uharm34=resultEven; + this.uharm36=resultEven; + this.uharm38=resultEven; + this.uharm40=resultEven; + this.uharm42=resultEven; + this.uharm44=resultEven; + this.uharm46=resultEven; + this.uharm48=resultEven; + this.uharm50=resultEven; + + + this.uharm3=resultOdd; + this.uharm5=resultOdd; + this.uharm7=resultOdd; + this.uharm9=resultOdd; + this.uharm11=resultOdd; + this.uharm13=resultOdd; + this.uharm15=resultOdd; + this.uharm17=resultOdd; + this.uharm19=resultOdd; + this.uharm21=resultOdd; + this.uharm23=resultOdd; + this.uharm25=resultOdd; + this.uharm27=resultOdd; + this.uharm29=resultOdd; + this.uharm31=resultOdd; + this.uharm33=resultOdd; + this.uharm35=resultOdd; + this.uharm37=resultOdd; + this.uharm39=resultOdd; + this.uharm41=resultOdd; + this.uharm43=resultOdd; + this.uharm45=resultOdd; + this.uharm47=resultOdd; + this.uharm49=resultOdd; + } + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/device/overlimit/util/COverlimitUtil.java b/carry_capacity/src/main/java/com/njcn/product/device/overlimit/util/COverlimitUtil.java new file mode 100644 index 0000000..db319b9 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/device/overlimit/util/COverlimitUtil.java @@ -0,0 +1,382 @@ +package com.njcn.product.device.overlimit.util; + + + +import com.njcn.product.device.overlimit.pojo.Overlimit; +import com.njcn.product.system.dict.enums.DicDataEnum; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * pqs + * 限值计算工具类 + * + * @author cdf + * @date 2023/5/15 + */ +public class COverlimitUtil { + + + /** + * 谐波电流系数 + */ + private static final double[][] ARR = { + {78, 62, 39, 62, 26, 44, 19, 21, 16, 28, 13, 24, 11, 12, 9.7, 18, 8.6, 16, 7.8, 8.9, 7.1, 14, 6.5, 12, 6.0, 6.9, 5.6, 11, 5.2, 10, 4.9, 5.6, 4.6, 8.9, 4.3, 8.4, 4.1, 4.8, 3.9, 7.6, 3.7, 7.2, 3.5, 4.1, 3.4, 6.6, 3.3, 6.3, 3.1}, + {43, 34, 21, 34, 14, 24, 11, 11, 8.5, 16, 7.1, 13, 6.1, 6.8, 5.3, 10, 4.7, 9, 4.3, 4.9, 3.9, 7.4, 3.6, 6.8, 3.3, 3.8, 3.1, 5.9, 2.9, 5.5, 2.7, 3.1, 2.5, 4.9, 2.4, 4.6, 2.3, 2.6, 2.2, 4.1, 2.0, 4.0, 2.0, 2.3, 1.9, 3.6, 1.8, 3.5, 1.7}, + {26, 20, 13, 20, 8.5, 15, 6.4, 6.8, 5.1, 9.3, 4.3, 7.9, 3.7, 4.1, 3.2, 6, 2.8, 5.4, 2.6, 2.9, 2.3, 4.5, 2.1, 4.1, 2.0, 2.2, 1.9, 3.4, 1.7, 3.2, 1.6, 1.8, 1.5, 2.9, 1.4, 2.7, 1.4, 1.5, 1.3, 2.4, 1.2, 2.3, 1.2, 1.3, 1.1, 2.1, 1.1, 2.0, 1.0}, + {15, 12, 7.7, 12, 5.1, 8.8, 3.8, 4.1, 3.1, 5.6, 2.6, 4.7, 2.2, 2.5, 1.9, 3.6, 1.7, 3.2, 1.5, 1.8, 1.4, 2.7, 1.3, 2.5, 1.2, 1.3, 1.1, 2.1, 1.0, 1.9, 0.9, 1.1, 0.9, 1.7, 0.8, 1.6, 0.8, 0.9, 0.8, 1.5, 0.7, 1.4, 0.7, 0.8, 0.7, 1.3, 0.6, 1.2, 0.6}, + {16, 13, 8.1, 13, 5.4, 9.3, 4.1, 4.3, 3.3, 5.9, 2.7, 5, 2.3, 2.6, 2, 3.8, 1.8, 3.4, 1.6, 1.9, 1.5, 2.8, 1.4, 2.6, 1.2, 1.4, 1.1, 2.2, 1.1, 2.1, 1.0, 1.2, 0.9, 1.9, 0.9, 1.8, 0.8, 1.0, 0.8, 1.6, 0.8, 1.5, 0.7, 0.9, 0.7, 1.4, 0.7, 1.3, 0.6}, + {12, 9.6, 6, 9.6, 4, 6.8, 3, 3.2, 2.4, 4.3, 2, 3.7, 1.7, 1.9, 1.5, 2.8, 1.3, 2.5, 1.2, 1.4, 1.1, 2.1, 1, 1.9, 0.9, 1.1, 0.9, 1.7, 0.8, 1.5, 0.8, 0.9, 0.7, 1.4, 0.7, 1.3, 0.6, 0.7, 0.6, 1.2, 0.6, 1.1, 0.5, 0.6, 0.5, 1.0, 0.5, 1.0, 0.5} + }; + + + /** + * 计算监测点限值 + * @param voltageLevel 电压等级(10kV = 10 220kV = 220 ) + * @param protocolCapacity 协议容量 + * @param devCapacity 设备容量 + * @param shortCapacity 短路容量 + * @param powerFlag 0.用户侧 1.电网侧 + * @param lineType 0.主网 1.配网 需要注意配网目前没有四种容量,谐波电流幅值限值,负序电流限值无法计算默认-3.14159 + */ + public static Overlimit globalAssemble(Float voltageLevel, Float protocolCapacity, Float devCapacity, + Float shortCapacity, Integer powerFlag, Integer lineType) { + Overlimit overlimit = new Overlimit(); + voltageDeviation(overlimit,voltageLevel); + frequency(overlimit); + voltageFluctuation(overlimit,voltageLevel); + voltageFlicker(overlimit,voltageLevel); + totalHarmonicDistortion(overlimit,voltageLevel); + uHarm(overlimit,voltageLevel); + threeVoltageUnbalance(overlimit); + interharmonicCurrent(overlimit,voltageLevel); + + if(lineType == 1) { + //配网 + Float[] iHarmTem = new Float[49]; + for (int i = 0; i <= 48; i++) { + + iHarmTem[i] = -3.14159f; + } + overlimit.buildIHarm(iHarmTem); + overlimit.setINeg(-3.14159f); + }else { + //主网 + iHarm(overlimit, voltageLevel, protocolCapacity, devCapacity, shortCapacity); + negativeSequenceCurrent(overlimit, voltageLevel, shortCapacity); + } + return overlimit; + } + + + /** + * 电压偏差限值 + * + */ + public static void voltageDeviation(Overlimit overlimit,Float voltageLevel) { + float voltageDev = 3.14159f,uvoltageDev = 3.14159f; + if(voltageLevel <= Float.parseFloat(DicDataEnum.V220.getCode())){ + voltageDev = 7.0f; + uvoltageDev=-10.0f; + }else if(voltageLevel>Float.parseFloat(DicDataEnum.V220.getCode())&&voltageLevel=Float.parseFloat(DicDataEnum.KV20.getCode())&&voltageLevel=Float.parseFloat(DicDataEnum.KV35.getCode())&&voltageLevel=Float.parseFloat(DicDataEnum.KV66.getCode())&&voltageLevel<=Float.parseFloat(DicDataEnum.KV110.getCode())){ + voltageDev = 7.0f; + uvoltageDev=-3.0f; + }else if(voltageLevel>Float.parseFloat(DicDataEnum.KV110.getCode())){ + voltageDev = 10.0f; + uvoltageDev=-10.0f; + } + overlimit.setVoltageDev(voltageDev); + overlimit.setUvoltageDev(uvoltageDev); + } + + + /** + * 频率偏差 + * 默认限值:±0.2Hz(即:-0.2 Hz≤限值≤0.2 Hz) + */ + public static void frequency(Overlimit overlimit) { + overlimit.setFreqDev(0.2f); + } + + + /** + * 电压波动 + * 对LV、MV:0≤限值≤3%;对HV:0≤限值≤2.5%。 + * LV、MV、HV的定义: + * 低压(LV) UN≤1kV + * 中压(MV) 1kV<UN≤35kV + * 高压(HV) 35kV<UN≤220kV + * 超高压(EHV),220kV<UN,参照HV执行 + */ + public static void voltageFluctuation(Overlimit overlimit, Float voltageLevel) { + if (voltageLevel < Float.parseFloat(DicDataEnum.KV35.getCode())) { + overlimit.setVoltageFluctuation(3.0f); + } else { + overlimit.setVoltageFluctuation(2.5f); + } + } + + + + /** + * 电压闪变 + * ≤110kV 1 + * >110kV 0.8 + */ + public static void voltageFlicker(Overlimit overlimit, Float voltageLevel) { + if (voltageLevel <= Float.parseFloat(DicDataEnum.KV110.getCode())) { + overlimit.setFlicker(1.0f); + } else { + overlimit.setFlicker(0.8f); + } + } + + + /** + * 总谐波电压畸变率 + * + * + */ + public static void totalHarmonicDistortion(Overlimit overlimit, Float voltageLevel) { + float result = 3.14159f; + if (voltageLevel < Float.parseFloat(DicDataEnum.KV6.getCode())) { + result = 5.0f; + } else if(voltageLevel >= Float.parseFloat(DicDataEnum.KV6.getCode()) && voltageLevel <= Float.parseFloat(DicDataEnum.KV20.getCode())){ + result = 4.0f; + } else if(voltageLevel >= Float.parseFloat(DicDataEnum.KV35.getCode()) && voltageLevel <= Float.parseFloat(DicDataEnum.KV66.getCode())){ + result = 3.0f; + } else if(voltageLevel >= Float.parseFloat(DicDataEnum.KV110.getCode()) && voltageLevel <= Float.parseFloat(DicDataEnum.KV1000.getCode())){ + result = 2.0f; + } + overlimit.setUaberrance(result); + } + + + + /** + * 谐波电压含有率 + */ + public static void uHarm(Overlimit overlimit, Float voltageLevel) { + float resultOdd = 3.14159f,resultEven = 3.14159f; + if (voltageLevel < Float.parseFloat(DicDataEnum.KV6.getCode())) { + resultOdd = 4.0f; + resultEven = 2.0f; + } else if(voltageLevel >= Float.parseFloat(DicDataEnum.KV6.getCode()) && voltageLevel <= Float.parseFloat(DicDataEnum.KV20.getCode())){ + resultOdd = 3.2f; + resultEven = 1.6f; + } else if(voltageLevel >= Float.parseFloat(DicDataEnum.KV35.getCode()) && voltageLevel <= Float.parseFloat(DicDataEnum.KV66.getCode())){ + resultOdd = 2.4f; + resultEven = 1.2f; + } else if(voltageLevel >= Float.parseFloat(DicDataEnum.KV110.getCode()) && voltageLevel <= Float.parseFloat(DicDataEnum.KV1000.getCode())){ + resultOdd = 1.6f; + resultEven = 0.8f; + } + overlimit.buildUharm(resultEven,resultOdd); + } + + + /** + * 负序电压不平衡(三相电压不平衡度) + * + */ + public static void threeVoltageUnbalance(Overlimit overlimit) { + overlimit.setUbalance(2.0f); + overlimit.setShortUbalance(4.0f); + } + + + /*---------------------------------谐波电流限值start-----------------------------------*/ + + /** + * 谐波电流限值 + */ + public static void iHarm(Overlimit overlimit, Float voltageLevel,Float protocolCapacity,Float devCapacity,Float shortCapacity) { + float calCap = shortCapacity/getDlCapByVoltageLevel(voltageLevel); + //24谐波电流幅值 + Float[] iHarmTem = new Float[49]; + for (int i = 0; i <= 48; i++) { + float inHarm = iHarmCalculate(i+2,voltageLevel,protocolCapacity,devCapacity,calCap); + iHarmTem[i] = inHarm; + } + overlimit.buildIHarm(iHarmTem); + } + /** + * @Description: iHarmCalculate + * @Param: protocolCapacity 协议容量 devCapacity设备容量 calCap 短路容量 + * @return: float + * @Author: clam + * @Date: 2024/2/4 + */ + private static float iHarmCalculate(int nHarm, Float voltageLevel,float protocolCapacity, float devCapacity,float calCap) { + double tag = calCap*getHarmTag(nHarm,voltageLevel); + Double limit = getHarmonicLimit(nHarm,tag,new BigDecimal(String.valueOf(devCapacity)).doubleValue(),new BigDecimal(String.valueOf(protocolCapacity)).doubleValue()); + BigDecimal bigDecimal = BigDecimal.valueOf(limit).setScale(4,RoundingMode.HALF_UP); + return bigDecimal.floatValue(); + } + + + /** + * 电流谐波限值 + */ + private static Double getHarmTag(Integer iCount, Float voltageLevel) { + int x, y; + if (voltageLevel < DicDataEnum.KV6.getValue()) { + x = 0; + } else if (voltageLevel + * 前端控制器(行政区域) + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Slf4j +@RestController +@RequestMapping("/area") +@Api(tags = "行政区域管理") +@AllArgsConstructor +public class AreaController extends BaseController { + + private final IAreaService areaService; + + + /** + * 根据行政区域id详情 + * + * @param id 行政区域id + * @return 行政区域详情 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/selectIdArea/{id}") + @ApiOperation("根据行政区域id查询详情") + @ApiImplicitParam(name = "id", value = "查询参数", required = true) + public HttpResult selectIdArea(@PathVariable("id") String id) { + String methodDescribe = getMethodDescribe("selectIdArea"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, id); + Area result = areaService.selectIdArea(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + /** + * 根据行政区域id详情 + * + * @param list 行政区域id集合 + * @return 行政区域详情 + */ + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/areaNameByList") + @ApiOperation("根据行政区域id集合查询名称") + @ApiImplicitParam(name = "list", value = "查询参数", required = true) + public HttpResult> areaNameByList(@RequestBody List list) { + String methodDescribe = getMethodDescribe("areaNameByList"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, list); + List result = areaService.selectAreaByList(list); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + + + /** + * 新增企业区域 + * + * @param areaParam 企业区域 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD) + @PostMapping("/add") + @ApiOperation("新增企业区域") + @ApiImplicitParam(name = "areaParam", value = "企业区域数据", required = true) + public HttpResult add(@RequestBody @Validated AreaParam areaParam) { + String methodDescribe = getMethodDescribe("add"); + LogUtil.njcnDebug(log, "{},字典类型数据为:{}", methodDescribe, areaParam); + boolean result = areaService.addAreaParam(areaParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + /** + * 修改企业区域 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE) + @PostMapping("/update") + @ApiOperation("修改企业区域") + @ApiImplicitParam(name = "updateParam", value = "企业区域", required = true) + public HttpResult update(@RequestBody @Validated AreaParam.AreaUpdateParam updateParam) { + String methodDescribe = getMethodDescribe("update"); + LogUtil.njcnDebug(log, "{},字典数据为:{}", methodDescribe, updateParam); + boolean result = areaService.updateArea(updateParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + /** + * 根据选中的行政区域id查询是否含有子节点 + * + * @param ids 行政区域ids + * @return 行政区域查看所有子节点 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/selectPid") + @ApiOperation("根据行政区域id查询") + @ApiImplicitParam(name = "ids", value = "查询参数", required = true) + public HttpResult selectPid(@RequestBody List ids) { + String methodDescribe = getMethodDescribe("selectPid"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, ids); + List result = areaService.selectPid(ids); + if (!result.isEmpty()) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.DELETE_PID_EXIST, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.DELETE_PID_UNEXIST, null, methodDescribe); + } + + } + + /** + * 批量删除企业区域 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DELETE) + @PostMapping("/delete") + @ApiOperation("删除企业区域") + @ApiImplicitParam(name = "ids", value = "企业区域索引", required = true) + public HttpResult delete(@RequestBody List ids) { + String methodDescribe = getMethodDescribe("delete"); + LogUtil.njcnDebug(log, "{},企业区域数据为:{}", methodDescribe, ids); + boolean result = areaService.deleteArea(ids); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + + + /** + * 根据区域id获取省份 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/areaPro") + @ApiOperation("根据区域id获取省份") + @ApiImplicitParams({ + @ApiImplicitParam(name = "id", value = "区域id"), + @ApiImplicitParam(name = "type", value = "区域类型", required = true) + }) + public HttpResult areaPro(@RequestParam(required = false) @ApiParam("id") String id, @RequestParam("type") Integer type) { + String methodDescribe = getMethodDescribe("areaDeptTree"); + Area result = areaService.areaPro(id, type); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + /** + * 根据行政区域名称查询详细 + * + * @param name 行政区域名称 + * @return 行政区域详情 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/selectAreaByName/{name}") + @ApiOperation("根据行政区域名称查询详细") + @ApiImplicitParam(name = "name", value = "查询参数", required = true) + public HttpResult selectAreaByName(@PathVariable("name") String name) { + String methodDescribe = getMethodDescribe("selectAreaByName"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, name); + Area result = areaService.selectAreaByName(name); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + /** + * 根据部门id获取省份 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/areaDeptPro") + @ApiOperation("根据区域id获取省份") + @ApiImplicitParam(name = "id", value = "部门id") + public HttpResult areaDeptPro(@RequestParam(required = false) @ApiParam("id") String id) { + String methodDescribe = getMethodDescribe("areaDeptTree"); + Area result = areaService.areaDeptPro(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + /** + * @description 获取省市区下拉框 + * @author clam + * @date 2023/4/11 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/areaSelect") + @ApiOperation("获取省市区下拉框") + public HttpResult> areaSelect() { + String methodDescribe = getMethodDescribe("areaSelect"); + List result = areaService.areaSelect(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + /** + * 获取指定区域父级的子级区域集合 + * @author cdf + * @date 2023/10/25 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/getPidAreaList") + @ApiOperation("获取指定区域父级的子级区域集合") + @ApiImplicitParams({ + @ApiImplicitParam(name = "areaId",value = "区域id"), + @ApiImplicitParam(name = "type", value = "区域类型", required = true) + }) + public HttpResult> getPidAreaList(@RequestParam("areaId")String areaId , @RequestParam("type") Integer type) { + String methodDescribe = getMethodDescribe("getPidAreaList"); + List result = areaService.getPidAreaList(areaId,type); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + +} + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/controller/DeptController.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/controller/DeptController.java new file mode 100644 index 0000000..228848e --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/controller/DeptController.java @@ -0,0 +1,69 @@ +package com.njcn.product.system.dept.controller; + + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.tree.Tree; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.common.utils.LogUtil; + +import com.njcn.product.system.dept.pojo.dto.DeptDTO; +import com.njcn.product.system.dept.pojo.po.Dept; +import com.njcn.product.system.dept.pojo.vo.DeptTreeVO; +import com.njcn.product.system.dept.service.IDeptService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.*; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import springfox.documentation.annotations.ApiIgnore; + +import java.util.List; +import java.util.Objects; + +/** + *

+ * 前端控制器(部门信息) + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Validated +@Slf4j +@RestController +@RequestMapping("/dept") +@Api(tags = "部门管理") +@AllArgsConstructor +public class DeptController extends BaseController { + + private final IDeptService deptService; + + + + /** + * 获取部门树 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/deptTree") + @ApiOperation("部门信息树") + public HttpResult deptTree() { + String methodDescribe = getMethodDescribe("deptTree"); + List result = deptService.deptTree(); + //删除返回失败,查不到数据返回空数组,兼容治理项目没有部门直接报错的bug + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + + } + + + +} + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/AreaMapper.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/AreaMapper.java new file mode 100644 index 0000000..903d4b9 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/AreaMapper.java @@ -0,0 +1,69 @@ +package com.njcn.product.system.dept.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.system.dept.pojo.dto.AreaTreeDTO; +import com.njcn.product.system.dept.pojo.po.Area; +import com.njcn.product.system.dept.pojo.vo.AreaTreeVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface AreaMapper extends BaseMapper { + + /** + * + * @return 行政区域树(首次) + */ + List getAreaTree(@Param("id")String id,@Param("type") Integer type,@Param("state")Integer state); + + /** + * + * @return 行政区域树(首次) + */ + List getAreaIdTree(@Param("type") Integer type, @Param("state")Integer state); + + /** + * 查询父节点的所有上层节点 + * @param id + * @return 父节点的所有上层节点 + */ + String getIdString(@Param("id")String id); + + /** + * + * @param ids id + * @param state 状态 + * @return 返回的结果 + */ + List selectPid(@Param("ids")List ids,@Param("state")Integer state); + + + /** + * + * @return 行政区域树(首次) + */ + List getAreaDeptTree(@Param("id")String id, @Param("type") Integer type, @Param("state")Integer state); + + /** + * 查询所有区域 + * @return 结果 + */ + List getAreaAll(); + + /** + * 根据部门id获取区域详情 + * @param id 部门id + * @return 结果 + */ + Area areaDeptProDetail(@Param("id")String id); + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/DeptMapper.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/DeptMapper.java new file mode 100644 index 0000000..017ade7 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/DeptMapper.java @@ -0,0 +1,35 @@ +package com.njcn.product.system.dept.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.system.dept.pojo.dto.DeptDTO; +import com.njcn.product.system.dept.pojo.po.Dept; +import com.njcn.product.system.dept.pojo.vo.DeptTreeVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface DeptMapper extends BaseMapper { + + /** + * 根据条件获取后代部门索引 + * @param id 部门id + * @param type 指定部门类型 + * @return 后代部门索引 + */ + List getDeptDescendantIndexes(@Param("id")String id, @Param("type")List type); + + /** + * + * @return 部门树 + */ + List getDeptTree(@Param("id")String id, @Param("type")List type); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/mapping/AreaMapper.xml b/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/mapping/AreaMapper.xml new file mode 100644 index 0000000..ec2dc85 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/mapping/AreaMapper.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/mapping/DeptMapper.xml b/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/mapping/DeptMapper.xml new file mode 100644 index 0000000..63f4f7d --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/mapper/mapping/DeptMapper.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/dto/AreaTreeDTO.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/dto/AreaTreeDTO.java new file mode 100644 index 0000000..e1529a8 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/dto/AreaTreeDTO.java @@ -0,0 +1,20 @@ +package com.njcn.product.system.dept.pojo.dto; + +import com.njcn.web.pojo.dto.BaseDTO; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * @author denghuajun + * @date 2022/1/10 10:33 + * + */ +@Data +public class AreaTreeDTO extends BaseDTO { + @ApiModelProperty("是否被绑定") + private Integer isFalse = 0; + @ApiModelProperty("子节点") + private List children; +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/dto/DeptDTO.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/dto/DeptDTO.java new file mode 100644 index 0000000..15cb17a --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/dto/DeptDTO.java @@ -0,0 +1,46 @@ +package com.njcn.product.system.dept.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年02月11日 14:08 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DeptDTO implements Serializable { + + private String id; + + private String pid; + + private String pids; + + private String name; + + private String code; + + /** + * 专项分析类型区分 + */ + private Integer specialType; + + private String area; + + private String remark; + + private Integer sort; + + /** + * 部门类型 0-非自定义;1-web自定义;2-App自定义;3-web测试 + */ + private Integer type; + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/param/AreaParam.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/param/AreaParam.java new file mode 100644 index 0000000..108acb0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/param/AreaParam.java @@ -0,0 +1,88 @@ +package com.njcn.product.system.dept.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.web.constant.ValidMessage; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import java.math.BigDecimal; + +/** + * @author denghuajun + * @version 1.0.0 + * @date 2022年1月5日 8:59 + */ +@Data +public class AreaParam { + + @ApiModelProperty("父节点") + @NotBlank(message = ValidMessage.PID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEMS_ID, message = ValidMessage.PID_FORMAT_ERROR) + private String pid; + + + @ApiModelProperty("名称") + @NotBlank(message = ValidMessage.NAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.ALL_CHAR_1_20, message = ValidMessage.NAME_FORMAT_ERROR) + private String name; + + @ApiModelProperty("简称") + @NotBlank(message = ValidMessage.NAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.ALL_CHAR_1_20, message = ValidMessage.NAME_FORMAT_ERROR) + private String shortName; + + + @ApiModelProperty("排序(编号)") + @NotBlank(message = ValidMessage.CODE_NOT_BLANK) + @Pattern(regexp = PatternRegex.ALL_CHAR_1_20, message = ValidMessage.CODE_FORMAT_ERROR) + private String areaCode; + + + + @ApiModelProperty("区域类型 0-省级区域;1-企业区域; ") + private Integer type; + + @ApiModelProperty("中心点经度") + private BigDecimal lng; + + @ApiModelProperty("中心点纬度") + private BigDecimal lat; + + + + + + /** + * 更新操作实体 + */ + + @Data + @EqualsAndHashCode(callSuper = true) + public static class AreaUpdateParam extends AreaParam { + + + @ApiModelProperty("id") + @NotBlank(message = ValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR) + private String id; + + } + + /** + * 分页查询实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class QueryParam extends BaseParam { + /** + * 区域类型 0-省级区域;1-企业区域 + */ + private Integer type; + + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/po/Area.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/po/Area.java new file mode 100644 index 0000000..0053030 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/po/Area.java @@ -0,0 +1,73 @@ +package com.njcn.product.system.dept.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_area") +public class Area extends BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 区域Id + */ + private String id; + + /** + * 父节点(0为根节点) + */ + private String pid; + + /** + * 上层所有节点 + */ + private String pids; + + /** + * 区域名称 + */ + private String name; + + /** + * 简称 + */ + private String shortName; + + /** + * 排序(编号) + */ + private String areaCode; + + /** + * 区域类型 0-省级区域;1-企业区域; + */ + private Integer type; + + /** + * 中心点经度 + */ + private BigDecimal lng; + + /** + * 中心点纬度 + */ + private BigDecimal lat; + + /** + * 区域状态 0-删除;1-正常;默认正常 + */ + private Integer state; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/po/Dept.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/po/Dept.java new file mode 100644 index 0000000..436b4b7 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/po/Dept.java @@ -0,0 +1,75 @@ +package com.njcn.product.system.dept.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_dept") +public class Dept extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 部门表Id + */ + private String id; + + /** + * 父节点Id(0为根节点) + */ + private String pid; + + /** + * 上层所有节点Id + */ + private String pids; + + /** + * 部门名称 + */ + private String name; + + /** + * 部门编号 + */ + private String code; + + /** + * 专项分析类型区分 + */ + private Integer specialType; + + /** + * (sys_Area)行政区域Id,自定义部门无需填写部门 + */ + private String area; + + /** + * 部门类型 0-非自定义;1-web自定义;2-App自定义;3-web测试 + */ + private Integer type; + + /** + * 排序 + */ + private Integer sort; + + /** + * 部门描述 + */ + private String remark; + + /** + * 部门状态 0-删除;1-正常;默认正常 + */ + private Integer state; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/vo/AreaTreeVO.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/vo/AreaTreeVO.java new file mode 100644 index 0000000..dc9e354 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/vo/AreaTreeVO.java @@ -0,0 +1,41 @@ +package com.njcn.product.system.dept.pojo.vo; + +import com.njcn.web.pojo.vo.BaseVO; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * @author denghuajun + * @date 2022/1/6 10:03 + * + */ +@Data +public class AreaTreeVO extends BaseVO { + + @ApiModelProperty("上层所有节点") + private String pids; + + @ApiModelProperty("区域名称") + private String name; + + @ApiModelProperty("简称") + private String shortName; + + @ApiModelProperty("排序(编号)") + private String areaCode; + + + @ApiModelProperty("中心点经度") + private BigDecimal lng; + + @ApiModelProperty("中心点纬度") + private BigDecimal lat; + + + @ApiModelProperty("子节点详细信息") + private List children ; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/vo/DeptTreeVO.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/vo/DeptTreeVO.java new file mode 100644 index 0000000..d6cb7bb --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/pojo/vo/DeptTreeVO.java @@ -0,0 +1,47 @@ +package com.njcn.product.system.dept.pojo.vo; + +import com.njcn.web.pojo.vo.BaseVO; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * @author denghuajun + * @date 2022/1/4 + * + */ +@Data +public class DeptTreeVO extends BaseVO { + + @ApiModelProperty("部门编号") + private String code; + + @ApiModelProperty("子类型") + private Integer specialType; + + @ApiModelProperty("行政区域id") + private String area; + + @ApiModelProperty("行政区域name") + private String areaName; + + @ApiModelProperty("状态") + private Integer state; + + @ApiModelProperty("部门类型") + private Integer type; + + @ApiModelProperty("部门描述") + private String remark; + + @ApiModelProperty("排序") + private Integer sort; + + @ApiModelProperty("部门等级 0:全国 1:省 2:市 3:县") + private Integer level; + + @ApiModelProperty("子节点详细信息") + private List children; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/service/IAreaService.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/service/IAreaService.java new file mode 100644 index 0000000..6d5011f --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/service/IAreaService.java @@ -0,0 +1,104 @@ +package com.njcn.product.system.dept.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.system.dept.pojo.dto.AreaTreeDTO; +import com.njcn.product.system.dept.pojo.param.AreaParam; +import com.njcn.product.system.dept.pojo.po.Area; +import com.njcn.product.system.dept.pojo.vo.AreaTreeVO; + + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IAreaService extends IService { + + /** + * 根据前台传递参数,分页查询行政区域信息 + * + * @param queryParam 查询参数 + * @return 行政区域列表 + */ + Page listDictData(AreaParam.QueryParam queryParam); + + /** + * 根据行政区域id查询详情 + * + * @param id 行政区域id + * @return 行政区域详情详情 + */ + Area selectIdArea(String id); + + /** + * 根据行政区域id查询详情 + * + * @param list 行政区域id集合 + * @return 行政区域详情详情 + */ + List selectAreaByList(List list); + + /** + * + */ + List selectPid(List ids); + + + /** + * 新增企业区域 + * @param areaParam 企业区域 + * @return 新增结果 + */ + boolean addAreaParam(AreaParam areaParam); + + /** + * 修改企业区域 + * @param updateParam 企业区域数据 + * @return 操作结果 + */ + boolean updateArea(AreaParam.AreaUpdateParam updateParam); + + /** + * 批量逻辑删除企业区域数据 + * @param ids 企业区域id集合 + * @return 操作结果 + */ + boolean deleteArea(List ids); + + + + /** + * 根据区域id获取省份信息 + * + * @param type 区域类型 + * @return 树形结构 + */ + Area areaPro(String id, Integer type); + + Area areaDeptPro(String id); + + + + /** + * 根据行政区域名称查询详细 + * + * @param name 行政区域名称 + * @return 行政区域详情 + */ + Area selectAreaByName(String name); + + /** + * @Description: areaSelect + * @Author: clam + * @Date: 2023/4/11 + */ + List areaSelect(); + + List getPidAreaList(String areaId,Integer type); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/service/IDeptService.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/service/IDeptService.java new file mode 100644 index 0000000..2ebd491 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/service/IDeptService.java @@ -0,0 +1,34 @@ +package com.njcn.product.system.dept.service; + +import cn.hutool.core.lang.tree.Tree; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.system.dept.pojo.dto.DeptDTO; +import com.njcn.product.system.dept.pojo.po.Dept; +import com.njcn.product.system.dept.pojo.vo.DeptTreeVO; + + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IDeptService extends IService { + + + /** + * 根据条件获取后代部门索引 + * @param id 部门id + * @param type 指定部门类型 + * @return 后代部门索引 + */ + List getDeptDescendantIndexes(String id, List type); + + + List deptTree(); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/service/impl/AreaServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/service/impl/AreaServiceImpl.java new file mode 100644 index 0000000..8820b41 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/service/impl/AreaServiceImpl.java @@ -0,0 +1,304 @@ +package com.njcn.product.system.dept.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.constant.BizParamConstant; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; + +import com.njcn.product.system.dept.mapper.AreaMapper; +import com.njcn.product.system.dept.pojo.dto.AreaTreeDTO; +import com.njcn.product.system.dept.pojo.param.AreaParam; +import com.njcn.product.system.dept.pojo.po.Area; +import com.njcn.product.system.dept.pojo.vo.AreaTreeVO; +import com.njcn.product.system.dept.service.IAreaService; +import com.njcn.product.system.dict.enums.SystemResponseEnum; +import com.njcn.web.factory.PageFactory; +import com.njcn.web.utils.RequestUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class AreaServiceImpl extends ServiceImpl implements IAreaService { + + + @Override + public Page listDictData(AreaParam.QueryParam queryParam) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(queryParam)) { + //查询参数不为空,进行条件填充 + if (StrUtil.isNotBlank(queryParam.getSearchValue())) { + //部门根据名称模糊查询 + queryWrapper + .and(param -> param.like("sys_area.name", queryParam.getSearchValue())); + } + } + queryWrapper.ne("sys_area.state", DataStateEnum.DELETED.getCode()); + queryWrapper.ge("sys_area.type", queryParam.getType()); + //初始化分页数据 + return this.baseMapper.selectPage(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), queryWrapper); + } + + @Override + public Area selectIdArea(String id) { + return this.baseMapper.selectById(id); + } + + @Override + public List selectAreaByList(List list) { + return this.lambdaQuery().in(Area::getId, list).list(); + } + + @Override + public List selectPid(List ids) { + return this.baseMapper.selectPid(ids, DataStateEnum.ENABLE.getCode()); + } + + +// @Override +// public List areaTree(String id, Integer type) { +// List areaTreeVOList; +// if (StrUtil.isBlank(id)) { +// /* +// * 用于首次访问区域。此处需要获取当前用户所绑定的部门下的行政区域id +// * 现在默认为0 +// */ +// id = deptFeignClient.getAreaIdByDeptId(RequestUtil.getDeptIndex()).getData(); +// } +// List areaTreeVOS; +// if (type == 1) { +// areaTreeVOList = this.baseMapper.getAreaIdTree(type, DataStateEnum.ENABLE.getCode()); +// List finalAreaTreeVOList = areaTreeVOList; +// areaTreeVOS = areaTreeVOList.stream().filter(deptTreeVO -> +// BizParamConstant.PARENT_ID.equals(deptTreeVO.getPid()) +// ).peek((deptFirst) -> { +// //map映射方法改变结果,调用getChildren()方法,把一级部门deptFirst和所有数据allDept作为参数传递,查询所有下级部门 +// deptFirst.setChildren(getChildren(deptFirst, finalAreaTreeVOList)); +// }).collect(Collectors.toList()); +// } else { +// areaTreeVOS = this.baseMapper.getAreaTree(id, type, DataStateEnum.ENABLE.getCode()); +// } +// return areaTreeVOS; +// } + + /** + * 递归查找所有企业的下级 + */ + private List getChildren(AreaTreeVO areaTreeVO, List allArea) { + return allArea.stream().filter(area -> { + //在全部数据中,找到和一级部门deptFirst的valueId相等的parentId + return area.getPid().equals(areaTreeVO.getId()); + }).peek(deptId -> { + //递归查询找到下级部门 + deptId.setChildren(getChildren(deptId, allArea)); + }).collect(Collectors.toList()); + } + + @Override + public boolean addAreaParam(AreaParam areaParam) { + checkAreaCode(areaParam, false); + Area area = new Area(); + BeanUtil.copyProperties(areaParam, area); + if (BizParamConstant.PARENT_ID.equals(areaParam.getPid())) { + //上层节点 + area.setPids(BizParamConstant.PARENT_ID); + } else { + String pids = StrUtil.COMMA + areaParam.getPid(); + String pid = this.baseMapper.getIdString(area.getPid()); + //上层节点 + area.setPids(pid + pids); + } + //默认为正常状态 + area.setState(DataStateEnum.ENABLE.getCode()); + return this.save(area); + } + + @Override + public boolean updateArea(AreaParam.AreaUpdateParam updateParam) { + checkAreaCode(updateParam, true); + Area area = new Area(); + if (BizParamConstant.PARENT_ID.equals(updateParam.getPid())) { + //上层节点 + area.setPids(BizParamConstant.PARENT_ID); + } else { + String pids = StrUtil.COMMA + updateParam.getPid(); + String pid = this.baseMapper.getIdString(area.getPid()); + //上层节点 + area.setPids(pid + pids); + } + BeanUtil.copyProperties(updateParam, area); + return this.updateById(area); + } + + @Override + public boolean deleteArea(List ids) { + /* + * 查询子节点 + */ + List list = this.baseMapper.selectPid(ids, DataStateEnum.ENABLE.getCode()); + /* + * 将子节点叶添加到需要删除中 + */ + if (!list.isEmpty()) { + for (Area area : list) { + ids.add(area.getId()); + } + } + return this.lambdaUpdate().set(Area::getState, DataStateEnum.DELETED.getCode()).in(Area::getId, ids).update(); + } + + + +// @Override +// public List areaDeptTree(String id, Integer type) { +// List areaTreeVOList; +// List areaTreeVOS; +// if (StrUtil.isBlank(id)) { +// /* +// * 用于首次访问区域。此处需要获取当前用户所绑定的部门下的行政区域id +// * 现在默认为0 +// */ +// id = deptFeignClient.getAreaIdByDeptId(RequestUtil.getDeptIndex()).getData(); +// } +// areaTreeVOList = this.baseMapper.getAreaDeptTree(id,type, DataStateEnum.ENABLE.getCode()); +// List finalAreaTreeVOList = areaTreeVOList; +// String finalId = id; +// areaTreeVOS = areaTreeVOList.stream().filter(deptTreeVO -> +// deptTreeVO.getPid().equals(finalId) +// ).peek((deptFirst) -> { +// //map映射方法改变结果,调用getChildren()方法,把一级部门deptFirst和所有数据allDept作为参数传递,查询所有下级部门 +// deptFirst.setChildren(getChildren(deptFirst, finalAreaTreeVOList)); +// }).collect(Collectors.toList()); +// +// return areaTreeVOS; +// } + + @Override + public Area areaPro(String id, Integer type) { + QueryWrapper areaQueryWrapper = new QueryWrapper<>(); + areaQueryWrapper.eq("sys_area.id", id); + areaQueryWrapper.eq("sys_area.type", type); + areaQueryWrapper.eq("sys_area.state", DataStateEnum.ENABLE.getCode()); + Area area = this.baseMapper.selectOne(areaQueryWrapper); + if (BizParamConstant.PARENT_ID.equals(area.getId()) || BizParamConstant.PARENT_ID.equals(area.getPid())) { + return area; + }else{ + id = area.getPid(); + area = areaPro(id, type); + } + return area; + } + + @Override + public Area areaDeptPro(String id) { + Area areaDetail = this.baseMapper.areaDeptProDetail(id); + return areaPro(areaDetail.getId(),areaDetail.getType()); + } + + +// @Override +// public List getDeptIdAreaTree() { +// +// //获取当前系统登录的部门信息 +// String areaId = deptFeignClient.getAreaIdByDeptId(RequestUtil.getDeptIndex()).getData(); +// List areaTreeVOS = this.baseMapper.getAreaAll(); +// return areaTreeVOS.stream().filter(areaTreeVO -> +// areaTreeVO.getId().equals(areaId) +// ).peek((areaFirst) -> { +// //map映射方法改变结果,调用getChildren()方法,把一级部门deptFirst和所有数据allDept作为参数传递,查询所有下级部门 +// areaFirst.setChildren(getChildren(areaFirst, areaTreeVOS)); +// }).collect(Collectors.toList()); +// } + + @Override + public Area selectAreaByName(String name) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(Area::getName, name); + return this.baseMapper.selectOne(lambdaQueryWrapper); + } + + /** + * 递归查找所有企业的下级 + * + */ + private List getChildren(AreaTreeDTO areaTreeVO, List allArea) { + return allArea.stream().filter(area -> { + //在全部数据中,找到和一级部门deptFirst的valueId相等的parentId + return area.getPid().equals(areaTreeVO.getId()); + }).peek(deptId -> { + //递归查询找到下级部门 + deptId.setChildren(getChildren(deptId, allArea)); + }).collect(Collectors.toList()); + } + + /** + * 校验参数,检查是否存在相同编码的企业区域 + */ + private void checkAreaCode(AreaParam areaParam, boolean isExcludeSelf) { + LambdaQueryWrapper dictTypeLambdaQueryWrapper = new LambdaQueryWrapper<>(); + dictTypeLambdaQueryWrapper + .eq(Area::getAreaCode, areaParam.getAreaCode()) + .eq(Area::getState, DataStateEnum.ENABLE.getCode()); + //更新的时候,需排除当前记录 + if (isExcludeSelf) { + if (areaParam instanceof AreaParam.AreaUpdateParam) { + dictTypeLambdaQueryWrapper.ne(Area::getId, ((AreaParam.AreaUpdateParam) areaParam).getId()); + } + } + int countByAccount = this.count(dictTypeLambdaQueryWrapper); + //大于等于1个则表示重复 + if (countByAccount >= 1) { + throw new BusinessException(SystemResponseEnum.AREA_CODE_REPEAT); + } + } + + @Override + public List areaSelect() { + List areaTreeVOS = this.baseMapper.getAreaAll(); + return areaTreeVOS.stream ( ).filter (temp ->BizParamConstant.PARENT_ID.equals(temp.getPid())) + .peek((areaFirst) -> { + //map映射方法改变结果,调用getChildren()方法,把一级部门deptFirst和所有数据allDept作为参数传递,查询所有下级部门 + areaFirst.setChildren (getChildren (areaFirst, areaTreeVOS)); + }).collect (Collectors.toList ( )); + } + + @Override + public List getPidAreaList(String areaId, Integer type) { + List result = new ArrayList<>(); + Area area = this.getById(areaId); + if(Objects.isNull(area)){ + return result; + } + if(BizParamConstant.PARENT_ID.equals(area.getId())){ + result.add(area); + }else { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(Area::getPid,area.getPid()) + .eq(Area::getState,DataStateEnum.ENABLE.getCode()).eq(Area::getType,type).orderByAsc(Area::getAreaCode); + result = this.list(lambdaQueryWrapper); + } + return result; + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dept/service/impl/DeptServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/system/dept/service/impl/DeptServiceImpl.java new file mode 100644 index 0000000..12d3fab --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dept/service/impl/DeptServiceImpl.java @@ -0,0 +1,70 @@ +package com.njcn.product.system.dept.service.impl; + + +import cn.hutool.core.text.StrPool; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.system.dept.mapper.DeptMapper; +import com.njcn.product.system.dept.pojo.dto.DeptDTO; +import com.njcn.product.system.dept.pojo.po.Dept; +import com.njcn.product.system.dept.pojo.vo.DeptTreeVO; +import com.njcn.product.system.dept.service.IDeptService; +import com.njcn.web.utils.RequestUtil; +import com.njcn.web.utils.WebUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DeptServiceImpl extends ServiceImpl implements IDeptService { + + @Override + public List getDeptDescendantIndexes(String id, List type) { + return this.baseMapper.getDeptDescendantIndexes(id, type); + } + + @Override + public List deptTree() { + List deptType = WebUtil.filterDeptType(); + String deptIndex = RequestUtil.getDeptIndex(); + List deptList = this.baseMapper.getDeptTree(deptIndex, deptType); + return deptList.stream() + .filter(deptVO -> deptVO.getId().equals(deptIndex)) + .peek(deptFirst -> { + if (!Objects.isNull(deptFirst.getPid())) { + deptFirst.setLevel(deptFirst.getPids().split(StrPool.COMMA).length - 1); + } + deptFirst.setChildren(getChildren(deptFirst, deptList)); + }) + .collect(Collectors.toList()); + } + private List getChildren(DeptTreeVO deptFirst, List allDept) { + return allDept.stream().filter(dept -> dept.getPid().equals(deptFirst.getId())) + .peek(deptVo -> { + if (!Objects.isNull(deptVo.getPids())) { + deptVo.setLevel(deptVo.getPids().split(",").length - 1); + } + deptVo.setChildren(getChildren(deptVo, allDept)); + if (deptVo.getType() == 0) { + deptVo.setName(deptVo.getAreaName()); + } + }).sorted(Comparator.comparing(DeptTreeVO::getSort)).collect(Collectors.toList()); + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/controller/DictDataController.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/controller/DictDataController.java new file mode 100644 index 0000000..3285aa8 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/controller/DictDataController.java @@ -0,0 +1,245 @@ +package com.njcn.product.system.dict.controller; + + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.common.utils.LogUtil; + +import com.njcn.product.system.dict.pojo.param.DictDataParam; +import com.njcn.product.system.dict.pojo.po.DictData; +import com.njcn.product.system.dict.pojo.vo.DictDataVO; +import com.njcn.product.system.dict.service.IDictDataService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import springfox.documentation.annotations.ApiIgnore; + +import java.util.List; + +/** + *

+ * 前端控制器 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Validated +@Slf4j +@Api(tags = "字典数据操作") +@RestController +@RequestMapping("/dictData") +@RequiredArgsConstructor +public class DictDataController extends BaseController { + + private final IDictDataService dictDataService; + + /** + * 分页查询字典类型数据 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/list") + @ApiOperation("查询字典数据") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult> list(@RequestBody @Validated DictDataParam.DictDataQueryParam queryParam) { + String methodDescribe = getMethodDescribe("list"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam); + Page result = dictDataService.listDictData(queryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + /** + * 新增字典数据 + * + * @param dictDataParam 字典数据 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD) + @PostMapping("/add") + @ApiOperation("新增字典数据") + @ApiImplicitParam(name = "dictDataParam", value = "字典数据", required = true) + public HttpResult add(@RequestBody @Validated DictDataParam dictDataParam) { + String methodDescribe = getMethodDescribe("add"); + LogUtil.njcnDebug(log, "{},字典数据为:{}", methodDescribe, dictDataParam); + boolean result = dictDataService.addDictData(dictDataParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + /** + * 修改字典数据 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE) + @PostMapping("/update") + @ApiOperation("修改字典数据") + @ApiImplicitParam(name = "updateParam", value = "字典数据", required = true) + public HttpResult update(@RequestBody @Validated DictDataParam.DictDataUpdateParam updateParam) { + String methodDescribe = getMethodDescribe("update"); + LogUtil.njcnDebug(log, "{},字典数据为:{}", methodDescribe, updateParam); + boolean result = dictDataService.updateDictData(updateParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + + /** + * 批量删除字典数据 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE) + @PostMapping("/delete") + @ApiOperation("删除字典数据") + @ApiImplicitParam(name = "ids", value = "字典索引", required = true, dataTypeClass = List.class) + public HttpResult delete(@RequestBody List ids) { + String methodDescribe = getMethodDescribe("delete"); + LogUtil.njcnDebug(log, "{},字典ID数据为:{}", methodDescribe, String.join(StrUtil.COMMA, ids)); + boolean result = dictDataService.deleteDictData(ids); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + /** + * 根据字典类型id分页查询字典数据 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/getTypeIdData") + @ApiOperation("根据字典类型id查询字典数据") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult> getTypeIdData(@RequestBody @Validated DictDataParam.DicTypeIdQueryParam queryParam) { + String methodDescribe = getMethodDescribe("list"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam); + Page result = dictDataService.getTypeIdData(queryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getDicDataById") + @ApiOperation("根据字典id查询字典数据") + @ApiImplicitParam(name = "dicIndex", value = "查询参数", required = true) + public HttpResult getDicDataById(@RequestParam("dicIndex") String dicIndex) { + String methodDescribe = getMethodDescribe("getDicDataById"); + DictData result = dictDataService.getDicDataById(dicIndex); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getDicDataByTypeName") + @ApiOperation("根据字典类型名称查询字典数据") + @ApiImplicitParam(name = "dictTypeName", value = "查询参数", required = true) + public HttpResult> getDicDataByTypeName(@RequestParam("dictTypeName") String dictTypeName) { + String methodDescribe = getMethodDescribe("getDicDataByTypeName"); + List result = dictDataService.getDicDataByTypeName(dictTypeName); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getDicDataByName") + @ApiOperation("根据字典名称查询字典数据") + @ApiImplicitParam(name = "dicName", value = "查询参数", required = true) + public HttpResult getDicDataByName(@RequestParam("dicName") String dicName) { + String methodDescribe = getMethodDescribe("getDicDataByName"); + DictData result = dictDataService.getDicDataByName(dicName); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getDicDataByNameAndType") + @ApiOperation("根据字典名称查询字典数据") + @ApiImplicitParams({ + @ApiImplicitParam(name = "dicName", value = "查询参数", required = true), + @ApiImplicitParam(name = "typeName", value = "查询参数", required = true) + }) + public HttpResult getDicDataByNameAndType(@RequestParam("dicName") String dicName,@RequestParam("typeName") String typeName) { + String methodDescribe = getMethodDescribe("getDicDataByNameAndType"); + DictData result = dictDataService.getDicDataByNameAndType(dicName,typeName); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getDicDataByCodeAndType") + @ApiOperation("根据字典Code和字典类型查询字典数据") + @ApiImplicitParams({ + @ApiImplicitParam(name = "dicDataCode", value = "查询参数", required = true), + @ApiImplicitParam(name = "dicTypeCode", value = "查询参数", required = true) + }) + public HttpResult getDicDataByCodeAndType(@RequestParam("dicDataCode") String dicCode,@RequestParam("dicTypeCode") String typeCode) { + String methodDescribe = getMethodDescribe("getDicDataByCodeAndType"); + DictData result = dictDataService.getDicDataByCodeAndType(dicCode,typeCode); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getDicDataByCode") + @ApiOperation("根据字典code查询字典数据") + @ApiImplicitParam(name = "code", value = "查询参数", required = true) + public HttpResult getDicDataByCode(@RequestParam("code") String code) { + String methodDescribe = getMethodDescribe("getDicDataByCode"); + DictData result = dictDataService.getDicDataByCode(code); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + /** + * 后台新增字典数据 + * + * @param dicTypeName 类型名称 + * @param dicDataName 数据名称 + * @return 新增后的字典数据 + */ + @ApiIgnore + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/addDicData") + @ApiOperation("后台新增字典数据") + public HttpResult addDicData(String dicTypeName, String dicDataName) { + String methodDescribe = getMethodDescribe("addDicData"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, dictDataService.addDictData(dicTypeName,dicDataName), methodDescribe); + } + + /** + * 根据字典类型名称&数据名称获取字典数据 + * + * @param dicTypeName 字典类型名称 + * @param dicDataName 字典数据名称 + * @return 字典数据 + */ + @ApiIgnore + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getDicDataByNameAndTypeName") + @ApiOperation("根据字典类型名称&数据名称获取字典数据") + public HttpResult getDicDataByNameAndTypeName(String dicTypeName, String dicDataName) { + String methodDescribe = getMethodDescribe("getDicDataByNameAndTypeName"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, dictDataService.getDicDataByNameAndTypeName(dicTypeName,dicDataName), methodDescribe); + } + + @ApiIgnore + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getDicDataByTypeCode") + @ApiOperation("根据字典类型code查询字典数据") + @ApiImplicitParam(name = "dictTypeCode", value = "查询参数", required = true) + public HttpResult> getDicDataByTypeCode(@RequestParam("dictTypeCode") String dictTypeCode) { + String methodDescribe = getMethodDescribe("getDicDataByTypeCode"); + List result = dictDataService.getDicDataByTypeCode(dictTypeCode); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + +} + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/controller/DictTypeController.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/controller/DictTypeController.java new file mode 100644 index 0000000..e548901 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/controller/DictTypeController.java @@ -0,0 +1,153 @@ +package com.njcn.product.system.dict.controller; + + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.dto.SimpleTreeDTO; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.common.utils.LogUtil; + +import com.njcn.product.system.dict.pojo.param.DictTypeParam; +import com.njcn.product.system.dict.pojo.po.DictType; +import com.njcn.product.system.dict.service.IDictTypeService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + *

+ * 前端控制器 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Slf4j +@Api(tags = "字典类型表操作") +@RestController +@RequestMapping("/dictType") +@RequiredArgsConstructor +public class DictTypeController extends BaseController { + + private final IDictTypeService dictTypeService; + + + /** + * 分页查询字典类型数据 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/list") + @ApiOperation("查询字典类型") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult> list(@RequestBody @Validated DictTypeParam.DictTypeQueryParam queryParam) { + String methodDescribe = getMethodDescribe("list"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam); + Page result = dictTypeService.listDictTypes(queryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + /** + * 查询所有字典类型数据 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/listAll") + @ApiOperation("查询所有字典类型数据") + public HttpResult> listAll() { + String methodDescribe = getMethodDescribe("listAll"); + LogUtil.njcnDebug(log, "{}", methodDescribe); + List dictTypeList = dictTypeService.list(new LambdaQueryWrapper().eq(DictType::getState, DataStateEnum.ENABLE.getCode())); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, dictTypeList, methodDescribe); + } + + + /** + * 新增字典类型 + * + * @param dictTypeParam 字典类型数据 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD) + @PostMapping("/add") + @ApiOperation("新增字典类型") + @ApiImplicitParam(name = "dictTypeParam", value = "字典类型数据", required = true) + public HttpResult add(@RequestBody @Validated DictTypeParam dictTypeParam) { + String methodDescribe = getMethodDescribe("add"); + LogUtil.njcnDebug(log, "{},字典类型数据为:{}", methodDescribe, dictTypeParam); + boolean result = dictTypeService.addDictType(dictTypeParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + /** + * 修改字典类型 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE) + @PostMapping("/update") + @ApiOperation("修改字典类型") + @ApiImplicitParam(name = "updateParam", value = "字典类型数据", required = true) + public HttpResult update(@RequestBody @Validated DictTypeParam.DictTypeUpdateParam updateParam) { + String methodDescribe = getMethodDescribe("update"); + LogUtil.njcnDebug(log, "{},字典类型数据为:{}", methodDescribe, updateParam); + boolean result = dictTypeService.updateDictType(updateParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + + /** + * 批量删除字典类型 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DELETE) + @PostMapping("/delete") + @ApiOperation("删除字典类型") + @ApiImplicitParam(name = "ids", value = "字典索引", required = true) + public HttpResult delete(@RequestBody List ids) { + String methodDescribe = getMethodDescribe("delete"); + LogUtil.njcnDebug(log, "{},字典ID数据为:{}", methodDescribe, String.join(StrUtil.COMMA, ids)); + boolean result = dictTypeService.deleteDictType(ids); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + /** + * 获取所有字典数据基础信息 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/dictDataCache") + @ApiOperation("获取所有字典数据基础信息") + public HttpResult> dictDataCache() { + String methodDescribe = getMethodDescribe("dictDataCache"); + LogUtil.njcnDebug(log, "{},获取所有字典数据基础信息", methodDescribe); + List dictData = dictTypeService.dictDataCache(); + if (CollectionUtil.isNotEmpty(dictData)) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, dictData, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.NO_DATA, null, methodDescribe); + } + } + +} + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/enums/DicDataEnum.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/enums/DicDataEnum.java new file mode 100644 index 0000000..21841a2 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/enums/DicDataEnum.java @@ -0,0 +1,677 @@ +package com.njcn.product.system.dict.enums; + +import lombok.Getter; + +/** + * 类的介绍:字典数据名称 + * + * @author xuyang + * @version 1.0.0 + * @createTime 2021/8/5 21:56 + */ +@Getter +public enum DicDataEnum { + + /** + * 数据中心稳态统计指标 + */ + PLPC_ENUM("频率偏差", "PLPC"), + DYPC_ENUM("电压偏差", "DYPC"), + SXDYBPHD_ENUM("负序电压不平衡度", "SXDYBPHD"), + XBDY_ENUM("谐波电压", "XBDY"), + CSSB_ENUM("长时闪变", "CSSB"), + XBDL_ENUM("谐波电流", "XBDL"), + FXDL_ENUM("负序电流", "FXDL"), + JXBDY_ENUM("间谐波电压", "JXBDY"), + + + /** + * 稳态统计指标 + */ + VOLTAGE_DEV("电压偏差", "Voltage_Dev"), + FLICKER("长时闪变", "Flicker"), + HARMONIC_VOLTAGE("谐波电压", "Harmonic_Voltage"), + HARMONIC_CURRENT("谐波电流", "Harmonic_Current"), + INTERHARMONIC_VOLTAGE("间谐波电压", "Interharmonic_Voltage"), + PHASE_VOLTAGE("负序电压不平衡度", "phase_Voltage"), + FREQUENCY_DEV("频率偏差", "Frequency_Dev"), + NEG_CURRENT("负序电流", "Neg_Current"), + THD_V("电压总谐波畸变率", "Thd_V"), + phase_Voltage("三相电压不平衡度","phase_Voltage"), + TOTAL_INDICATOR("总稳态指标", "Total_Indicator"), + + /** + * 污区图统计类型 + */ + I_ALL("谐波电流", "I_All"), + V_HARMONIC("谐波电压", "V_Harmonic"), + + + /** + * 监测点类别 + */ + ONE_LINE("Ⅰ类监测点", "One_Line"), + TWO_LINE("Ⅱ类监测点", "Two_Line"), + THREE_LINE("Ⅲ类监测点", "Three_Line"), + + + /** + * 监测点类型 + */ + Power_Supply_Point("供电点","Power_Supply_Point"), + Pub_Connect_Point("公共连接点PCC","Pub_Connect_Point"), + Parallel_Point("并网点","Parallel_Point"), + Other("其他","Other"), + + + /** + * 电压互感器类型 + */ + Cap_V("电容式","Cap_V"), + Pele_V("光电式","Pele_V"), + Elec_V("电子式","Elec_V"), + Other_S("其他","Other"), + Ele_V("电磁式","Ele_V"), + + /** + * 中性点接地方式 + */ + Ground_Res("经非线性电阻接地-消谐器","Ground_Res"), + Ground_Trans("经互感器接地-4PT","Ground_Trans"), + Ground_Dir("直接接地-3PT","Ground_Dir"), + A_Center("A类测试中性点接地方式","A_Center"), + Ground_Other("其他","Other"), + + + + + /** + * 终端类型 + */ + DEV_QUALITY("电能质量监测终端", "Dev_Quality"), + DEV_SMART("智能电表", "Dev_Smart"), + DEV_MIX("智能融合终端", "Dev_Mix"), + + + /** + * 装置类别 + */ + Test_Equipment("测试设备","Test_Equipment"), + Monitor_Terminals("监测终端","Monitor_Terminals"), + Detect_Equipment("检测设备","Detect_Equipment"), + Govern_Devices("治理设备","Govern_Devices"), + + /*** + * 告警类型 + */ + COMM_ERR("通讯异常", "Comm_Err"), + + /** + * 暂态统计指标 + */ + TOTAL_INDICATORS("总暂态指标", "Total_Indicators"), + VOLTAGE_DIP("电压暂降", "Voltage_Dip"), + VOLTAGE_RISE("电压暂升", "Voltage_Rise"), + SHORT_INTERRUPTIONS("短时中断", "Short_Interruptions"), + DISTURBANCE("扰动", "Disturbance"), + OTHER("其他", "Other"), + RECORDING_WAVE("录波", "Recording_Wave"), + + /** + * 数据类型 + */ + MAINNET_POINT("主网测点", "Mainnet_Point"), + DISTRIBUTION_POINT("配网测点", "Distribution_Point"), + + /** + * 分布式光伏台区渗透率水平 + */ + RATE_0_25("0-25", "Rate_0_25"), + RATE_25_50("25-50", "Rate_25_50"), + RATE_50_75("50-75", "Rate_50_75"), + RATE_75_100("75-100", "Rate_75_100"), + RATE_100("100", "Rate_100"), + + /** + * 入网报告状态 + */ + NEWLY("新建", "Newly"), + AUDIT("待审核", "Audit"), + FAILED("未通过", "Failed"), + FINISH("已生效", "Finish"), + + /** + * 审核状态 + */ + INIT("新建", "Init"), + FAIL("未通过", "Fail"), + AUDITT("待审核", "Auditt"), + SUCCESS("已通过", "Success"), + + /** + * 填报进度 + */ + NOT_REPORTED("未填报", "Not_Reported"), + INSIGHTS("成效分析", "Insights"), + PLAN_MEASURES("计划整改措施", "Plan_Measures"), + ACTUAL_MEASURES("实际采取措施", "Actual_Measures"), + CAUSE_ANALYSIS("原因分析", "Cause_Analysis"), + ARCHIVED("已归档", "Archived"), + + /** + * 问题来源 + */ + ONLINE("在线监测告警", "Online"), + DEV_EXCEPTION("设备异常", "Dev_Exception"), + GENERAL("普测超标", "General"), + USER_COMPLAINTS("用户投诉", "User_Complaints"), + + /** + * 台区电能质量事件类型 + */ + EVENT_TYPE_P("低功率因数0.7-0.8", "Event_Type_p"), + EVENT_TYPE_U("潮流倒送", "Event_Type_u"), + EVENT_TYPE_T("电压越上限15%以上", "Event_Type_t"), + EVENT_TYPE_W("电压越限", "Event_Type_w"), + EVENT_TYPE_O("低功率因数0.7以下", "Event_Type_o"), + EVENT_TYPE_E("电压越上限", "Event_Type_e"), + EVENT_TYPE_Y("电压越下限", "Event_Type_y"), + EVENT_TYPE_L("低功率因数0.8-0.9", "Event_Type_l"), + EVENT_TYPE_Q("电压总谐波畸变率超标", "Event_Type_q"), + EVENT_TYPE_R("电压越上限7%-15%", "Event_Type_r"), + EVENT_TYPE_I("低功率因数", "Event_Type_i"), + PENET_LIMIT("渗透率超上限", "Penet_Limit"), + EVENT_TYPE_A("潮流倒送导致设备重载", "Event_Type_a"), + EVENT_TYPE_S("潮流倒送导致设备过载", "Event_Type_s"), + EVENT_TYPE_D("电压越上限严重度", "Event_Type_d"), + EVENT_TYPE_F("电压越下限严重度", "Event_Type_f"), + EVENT_TYPE_G("渗透率", "Event_Type_g"), + EVENT_TYPE_Z("超标3%-10%", "Event_Type_z"), + EVENT_TYPE_X("超标10%以下", "Event_Type_x"), + EVENT_TYPE_C("重过载", "Event_Type_c"), + /** + * 监测点状态 + */ + RUN("运行", "Run"), + OVERHAUL("检修", "Overhaul"), + DEBUGGING("调试", "Debugging"), + DECOMMISSIONING("停运", "Decommissioning"), + RETIREMENT("退役", "Retirement"), + + /** + * 终端状态 + */ + FREE_MOORY("剩余内存", "Free_Mmory"), + FREE_STORE("剩余存储空间", "Free_Store"), + NOT_OPERATION("未投运", "Not_Operation"), + RUNNING("在运", "Running"), + RETIRE("退役", "Retire"), + ON_SITE("现场留用", "On_Site"), + STOCK_STANDBY("库存备用", "Stock_Standby"), + TO_BE_SCRAPPED("待报废", "To_Be_Scrapped"), + SCRAP("报废", "Scrap"), + + /** + * 监测点标签(废弃,统一使用监测点对象类型) + */ + ONSHORE_WIND("陆上风电", "Onshore_Wind"), + POWER_STATION("光伏电站", "Power_Station"), + ELECTRIFIED_RAILWAYS("电气化铁路", "Electrified_Railways"), + SMELT_LOAD("冶炼负荷", "Smelt_Load"), + DISTRIBUTED_PHOTOVOLTAICS("分布式光伏", "Distributed_Photovoltaics"), + WIND_FARM("风电场", "Wind_Farm"), + SENSITIVE_USERS("重要敏感用户", "Sensitive_Users"), + IMPORTANT_USERS("重要用户", "Important_Users"), + //废弃字段 + TRACTION_STATION("牵引站", "Traction_Station"), + LINEAR_LOADS("其他非线性负荷", "Linear_Loads"), + + + /** + * 电压等级 + */ + AC_380V("交流380V(含400V)", "AC_380V(400V)"), + DY_380V("交流0.38kV", "0.38kV"), + DY_10KV("交流10kV", "10kV"), + DY_35KV("交流35kV", "35kV"), + DY_110KV("交流110kV", "110kV"), + DY_220KV("交流220kV", "220kV"), + DY_500KV("交流500kV", "500kV"), + DY_DC_500kV("直流500kV", "DC_500kV"), + + + /** + * 电压等级 + * 此电压用于计算,真实code请使用上面枚举 + */ + + V100("100V", "0.1",0.1f), + V220("220V", "0.22",0.22f), + KV038("0.38kV", "0.38",0.38f), + V380("380V", "0.38",0.38f), + KV04("0.4kV", "0.4",0.4f), + KV06("0.6kV", "0.6",0.6f), + V400("400V", "0.4",0.4f), + KV1("1kV", "1",1.0f), + KV6("6kV", "6",6.0f), + KV10("10kV", "10",10.0f), + KV20("20kV", "20",20.0f), + KV30("30kV", "30",30.0f), + KV35("35kV", "35",35.0f), + KV50("50kV", "50",50.0f), + KV66("66kV", "66",66.0f), + KV72_5("72.5kV", "725",725.0f), + KV110("110kV", "110",110.0f), + KV120("120kV", "120",120.0f), + KV125("125kV", "125",125.0f), + KV200("200kV", "200",200.0f), + KV220("220kV", "220",220.0f), + KV320("320kV", "320",320.0f), + KV330("330kV", "330",330.0f), + KV400("400kV", "400",400.0f), + KV500("500kV", "500",500.0f), + KV600("600kV", "600",600.0f), + KV660("660kV", "660",660.0f), + KV750("750kV", "750",750.0f), + KV1000("1000kV", "1000",1000.0f), + KV1100("1100kV", "1100",1100.0f), + + /** + * 计划采取实施 + */ + GOVERNANCE_FACTS("事实治理工程", "Governance_Facts"), + GRID_OPERATES("电网运行方式调整", "Grid_Operates"), + PARAMETER_OPT("治理装置运行参数优化", "Parameter_Opt"), + RECTIFY_ORDERS("提出整改工单", "Rectify_Orders"), + + /** + * 牵引站变压器接线方式 + */ + SINGLE_TRANS("单相牵引变压器", "Single_Trans"), + THREE_TRANS("三相YN d11联结牵引变压器", "Three_Trans"), + THREE_PHASE_TRANS("三相YN d11 d1组成的牵引变压器", "Three_Phase_Trans"), + SCOTT_TRANS("SCOTT牵引变压器", "SCOTT_Trans"), + YN_V_TRANS("YN v联结平衡牵引变压器", "YN_V_Trans"), + YN_A_TRANS("YN A联结平衡牵引变压器", "YN_A_Trans"), + /** + * APP暂态事件类型 + */ + EVT_DIPSTR("电压暂降事件启动","Evt_DipStr"), + EVT_INTRSTR("电压中断事件启动","Evt_IntrStr"), + EVT_SWLSTR("电压暂升事件启动","Evt_SwlStr"), + + + + /** + * 监测对象 + */ + PHOTOVOLT("光伏台区", "Photovolt"), + FEEDER_TENKV("10kV馈线", "Feeder_TenkV"), + MAIN_CHANGE("主变", "Main_Change"), + + /** + * 工单状态 + */ + PEND_DISPATCH("待派单", "Pend_Dispatch"), + DISPATCHED("已派单", "Dispatched"), + CLOSED("已关闭", "Closed"), + + /** + * 问题类型 + */ + VOLTAGE_LIMIT("谐波电压越限", "Voltage_Limit"), + CURRENT_LIMIT("谐波电流越限", "Current_Limit"), + + /** + * 审核状态 + */ + REVIEW("待审核", "Review"), + AUDITED("已审核", "Audited"), + APPROVED("审核通过", "Approved"), + NOT_APPROVED("审核通过", "Not_Approved"), + + /** + * 审核处理 + */ + GENERATE("生成工单", "Generate"), + NO_REQUIRED("无需处理", "No_Required"), + + /** + * 工单流程 + */ + GENERATED("生成工单", "Generated"), + DISPATCH("派单", "Dispatch"), + FEEDBACK("反馈", "Feedback"), + AUDITING("审核", "Auditing"), + RECTIFICATION("整改", "Rectification"), + ASSESS("评估", "Assess"), + PIGEONHOLE("归档", "Pigeonhole"), + + /** + * 评估结果 + */ + PASS("评估合格", "Pass"), + NOT_PASS("评估不合格", "Not_Pass"), + + /** + * 工单类型 + */ + RECT_ORDER("整改单", "Rect_Order"), + + /** + * 一级业务类型 + */ + TRANS_BUSINESS("运检业务", "Trans_Business"), + + /** + * 日志字典类型 + */ + LINE_PARAMETER("监测点日志", "Line_Parameter"), + DEV_PARAMETER("设备日志", "Dev_Parameter"), + WEB_ADD("web新增用户", "Web_Add"), + DATA_PLAN("流量套餐修改", "Data_Plan"), + PROCESS_PARMETER("终端进程操作", "Process_Parmeter"), + + + /** + * 接线方式 + */ + STAR("星型接线", "Trans_Business"), + STAR_TRIANGLE("星三角", "Star_Triangle"), + OPEN_DELTA("开口三角", "Open_Delta"), + + /** + * 装置类型 + */ + GATEWAY_DEV("网关", "Gateway"), + CONNECT_DEV("直连设备", "Direct_Connected_Device"), + DEV("装置", "Device"), + PORTABLE("便携式设备", "Portable"), + + + /** + * 数据模型 + */ + APF("APF","Apf"), + DVR("DVR","Dvr"), + EPD("电能数据","Epd"), + PQD("电能质量数据","Pqd"), + BMD("基础测量数据","Bmd"), + EVT("事件","Evt"), + ALM("告警","Alm"), + STS("状态","Sts"), + DI("开入","Di"), + DO("电能数据","Do"), + PARM("参数","Parm"), + SET("定值","Set"), + INSET("内部定值","InSet"), + CTRL("控制","Ctrl"), + TERMINAL_SORT("台账类型","terminal_sort"), + /** + * 暂降原因 + */ + SHORT_TROUBLE("短路故障", "Short_Trouble"), + TRANSFORMER_EXCITATION("变压器激磁", "Transformer_Excitation"), + RESON_REST("其他", "Reson_Rest"), + LARGE_INDUCTION("大型感应电动机启动", "Large_Induction"), + VOLTAGE_DISTURBANCE("电压扰动", "Voltage_Disturbance"), + + + /** + * 暂降类型 + */ + PHASE_A("A相接地", "Phase_A"), + PHASE_B("B相接地", "Phase_B"), + PHASE_C("C相接地", "Phase_C"), + INTERPHASE_AB("AB相间", "Interphase_AB"), + INTERPHASE_BC("BC相间", "Interphase_BC"), + INTERPHASE_AC("AC相间", "Interphase_AC"), + GROUND_AB("AB两相接地", "Ground_AB"), + GROUND_BC("BC两相接地", "Ground_BC"), + GROUND_AC("AC两相接地", "Ground_AC"), + GROUND_ABC("三相接地", "Ground_ABC"), + TYPE_REST("其他", "Type_Rest"), + + /** + * 监测点位置 + */ + LOAD_SIDE("负载侧", "Load_Side"), + GRID_SIDE("电网侧", "Grid_Side"), + OUTPUT_SIDE("输出侧", "Output_Side"), + + /** + * 警告级别 + */ + + ALARM("告警", "Alarm"), + FAULT("故障", "Fault"), + + /** + * 装置级别 + */ + MOST_IMPORMENT("极重要","Vital"), + + /** + * 测量信号输入形式 + */ + NUMBER_SIGNAL("数字信号","Digital_Signal"), + SIMULATION_SIGNAL("模拟信号","Analog_Signal"), + + /** + * 设备地区特征 + */ + DOWNTOWN("市中心区","downtown"), + CITY("市区","city"), + TOWN("城镇","town"), + COUNTY_SEAT("县城区","County_Seat"), + COUNTRYSIDE("农村","countryside"), + TOWNSHIP("乡镇","township"), + AGRO_AREA("农牧区","Agro_Area"), + + /** + * 设备使用性质代码 + */ + DEDICATED("专用","dedicated"), + PUBLIC("公用","public"), + + + /** + * 监督类型 + */ + POWER_QUALITY("电能质量敏感用户监督","Power_Quality"), + UHV_Converter("特高压换流站监督","UHV_Converter"), + New_Energy("新能源场站监督","New_Energy"), + Technical_Super("供电电压质量技术监督","Technical_Super"), + capacitor_bank("电容器组监督","capacitor_bank"), + report_supervision("评估报告监督","report_supervision"), + /** + * app基础信息类型 + */ + DATA_BASE("资料库","Data_base"), + INTRODUCTION("系统介绍","introduction"), + USER_MANUAL("使用手册","User_Manual"), + USER_AGREEMENT("用户协议","User_Agreement"), + COMPANY_PROFILE("公司简介","Company_Profile"), + PERSONAL_INFOR_PROTECT("个人信息保护政策","Personal_Infor_Protect"), + + /** + * app设备事件类型权限转移,数据恢复 + */ + AUTHORITY_TRANSFER("权限转移","Authority_transfer"), + DATA_RECOVERY("数据恢复","Data_recovery"), + + /** + * 谐波数据报表,数据单位类别 + */ + EFFECTIVE("有效值","effective"), + POWER("功率","power"), + DISTORTION("畸变率","distortion"), + VOLTAGE("电压偏差","voltage"), + UNIT_FREQUENCY("频率","unitFrequency"), + UNBALANCE("三项不平横","unbalance"), + FUND("基波","fund"), + + + + + + /**pms******************************start*/ + + + /** + * 实施状态 + */ + Nocarried("未开展","Nocarried"), + Progressing("开展中","Progressing"), + Reviewing("待审核","Reviewing"), + Completed("已完成","Completed"), + + /*3.45 典型源荷用户类型*/ + TRACTIONSTATION("牵引站","01"), + WINDFARM_USER("风电场用户","02"), + PHOTOVOLTAICSIT_EUSERS("光伏场站用户","03"), + OTHER_INTERFERENCESOURCE_USERS("其他干扰源用户","04"), + + /*3.39 监测对象类型-大类*/ + SEMICONDUCTOR_MANUFACTURING("半导体制造","2401"), + PRECISION_MACHINING("精密加工","2402"), + PARTY_GOVERNMENT("党政机关","2403"), + NOSOCOMIUM("医院","2404"), + TRANSPORTATION_HUB("交通枢纽(公交场站、客运站、火车站等)","2405"), + AERODROME("机场","2406"), + FINANCE("金融","2407"), + DATA_CENTER("数据中心","2408"), + HAZARDOUS_CHEMICALS("危险化学品","2409"), + EXPLOSIVE_PRODUCTS("易燃易爆品制造","2410"), + LARGEVENUE("大型场馆(体育场、剧院等)","2411"), + WINDPOWER_STATION("风电场","1401"), + PHOTOVOLTAIC_POWER_STATION("光伏电站","1402"), + ELECTRIFIED_RAILWAY("电气化铁路","1300"), + + + /** + * 所属站别类型 + */ + Trans_Sub("变电站","Trans_Sub"), + Converter("换流站","Converter"), + Ele_Railways("电气化铁路","Ele_Railways"), + Wind_Farms("风电场","Wind_Farms"), + Power_Station("光伏电站","Power_Station"), + Smelting_Load("冶炼负荷","Smelting_Load"), + Imp_Users("重要敏感用户","Imp_Users"), + Station_Other("其他","Other"), + + /*承载能力评估用户类型*/ + Power_Station_Users("光伏电站用户","Power_Station_Users"), + Charging_Station_Users("充电站用户","Charging_Station_Users"), + Electric_Heating_Load_Users("电加热负荷用户","Electric_Heating_Load_Users"), + Electrified_Rail_Users("电气化铁路用户","Electrified_Rail_Users"), + + //变压器连接方式 + YNd11("YNd11","YNd11"), + YNy0("YNy0","YNy0"), + Yy0("Yy0","Yy0"), + Yyn0("Yyn0","Yyn0"), + Yd11("Yd11","Yd11"), + Y_yn("Y/yn","Y_yn"), + Y_d("Y/d","Y_d"), + D_y("D/y","D_y"), + YNyn("YNyn","YNyn"), + + //用户模式 + SPECIAL_USER("专变用户","special_user"),//专变用户 + PUBLIC_USER("公变用户","public_user"),// ,公变用户 + + //统计类型 + STATISTICAL_TYPE_Y("年数据","01"), + STATISTICAL_TYPE_M("月数据","02"), + STATISTICAL_TYPE_D("日数据","03"), + + /**pms******************************end*/ + + //pq干扰源类型 + PQ_ELE_RAILWAYS("电气化铁路","Ele_Railways"), + PQ_POWER_STATION("光伏电站","Power_Station"), + PQ_WIND_FARMS("风电场","Electrolytic_Load"), + + //所属地市local_municipality + //张家口市、廊坊市、唐山市、承德市、秦皇岛市、风光储、超高压 + ZHANGJIAKOU("张家口市","zhangjiakou"), + LANGFANG("廊坊市","langfang"), + TANGSHAN("唐山市","tangshan"), + CHENGDE("承德市","chengde"), + QINGHUANGDAO("秦皇岛市","qinghuangdao"), + FENGFENGRESERVE("风光储","fengfengreserve"), + EXTRA_HIGH_PRESSURE("超高压","extra_high_pressure"), + + //行业类型-冀北 + TRAFFIC("交通","Traffic"), + METALLURGY("冶金","Metallurgy"), + MACHINERY("机械","Machinery"), + CHEMICAL_INDUSTRY("化工","Chemical_Industry"), + MANUFACTURING("制造","Manufacturing"), + SHIPBUILDING("造船","Shipbuilding"), + UTILITIES("公用事业","Utilities"), + POWER_PLANT("电厂","Power_Plant"), + COMMERCE("商业","Commerce"), + MUNICIPAL("市政","Municipal"), + CIVILIAN("民用","Civilian"), + ELECTRONICS("电子","Electronics"), + COMMUNICATION("通讯","Communication"), + ELECTRIC_POWER("电力","Electric_Power"), + OTHER_INDUSTRY("其他","Other_Industry"), + + + + + + //河北工单相关 + //3.67工单状态 + WORK_ORDER_STATUS_NO("未处理","01"), + WORK_ORDER_STATUS_ING("处理中","02"), + WORK_ORDER_STATUS_HAS("已上报","03"), + WORK_ORDER_STATUS_CLOSE("已闭环","04"), + + + YES("是","1"), + NO("否","0"), + + No_Upload("未上送","0"), + Has_Upload("已上送","1"), + Reduce_Upload("取消上送","2"), + Return_Upload("待重新上送","3"), + + + //字典树类型 + Obj_Type("监测对象类型","0"), + Custom_Report_Type("自定义报表类型","1") + + + ; + + private final String name; + private final String code; + private final Float value; + + DicDataEnum(String name, String code,Float value) { + this.name = name; + this.code = code; + this.value = value; + } + + DicDataEnum(String name, String code) { + this.name = name; + this.code = code; + this.value = + null; + } + + public static DicDataEnum getDicDataEnumValue(String code) { + for (DicDataEnum item : values()) { + if (item.getCode().equals(code)) { + return item; + } + } + return null; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/enums/DicDataTypeEnum.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/enums/DicDataTypeEnum.java new file mode 100644 index 0000000..b1a1afa --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/enums/DicDataTypeEnum.java @@ -0,0 +1,163 @@ +package com.njcn.product.system.dict.enums; + +import lombok.Getter; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2021/8/5 21:56 + */ +@Getter +public enum DicDataTypeEnum { + /** + * 字典类型名称 + */ + FRONT_TYPE("前置类型","Front_Type"), + POWER_CATEGORY("电源类别","Power_Category"), + POWER_STATION_TYPE("电站类型","Power_Station_Type"), + POWER_GENERATION("发电方式","Power_Generation"), + CONNECTION_MODE("能源消纳方式","Connection_Mode"), + ECC_STAT("用电客户状态","Ecc_Stat"), + DEV_TYPE("终端型号","Dev_Type"), + DEV_VARIETY("终端类型","Dev_Variety"), + DEV_FUN("终端功能","Dev_Fun"), + DEV_STATUS("终端状态","Dev_Status"), + DEV_LEVEL("终端等级","Dev_Level"), + DEV_CONNECT("接线方式","Dev_Connect"), + DEV_MANUFACTURER("制造厂商","Dev_Manufacturers"), + //电压等级用于pms区分交直流 + DEV_VOLTAGE("电压等级","Dev_Voltage"), + //标准电压等级用于pq不区分交直流 + DEV_VOLTAGE_STAND("标准电压等级","Dev_Voltage_Stand"), + PANORAMIC_VOLTAGE("全景电压等级","Panoramic_voltage"), + EVENT_REASON("暂降原因","Event_Reason"), + EVENT_TYPE("暂降类型","Event_Type"), + BUSINESS_TYPE("行业类型","Business_Type"), + INTERFERENCE_SOURCE_TYPE("干扰源类型","Interference_Source"), + ALARM_TYPE("告警类型","alarm_Type"), + DEV_OPS("运维日志","Dev_Ops"), + INDICATOR_TYPE("指标类型","Indicator_Type"), + COMMUNICATE_TYPE("通讯类型","Communicate_Type"), + RATE_TYPE("费率类型","Rate_Type"), + ELE_LOAD_TYPE("用能负荷类型","Ele_Load_Type"), + ELE_STATISTICAL_TYPE("用能统计类型","Ele_Statistical_Type"), + REPORT_TYPE("自定义报表类型","Report_Type"), + LINE_MARK("监测点评分等级","Line_Grade"), + LINE_TYPE("监测点类型","Line_Type"), + STEADY_STATIS("稳态统计指标","Steady_Statis"), + EVENT_STATIS("暂态指标","Event_Statis"), + MONITORING_LABELS("监测点标签","Monitoring_Labels"), + POLLUTION_STATIS("污区图统计类型","Pollution_Statis"), + BENCHMARK_INDICATORS("基准水平评价指标","Benchmark_Indicator"), + LINE_SORT("监测点类别","Line_Sort"), + DATA_TYPE("数据类型","Data_Type"), + PERMEABILITY_TYPE("分布式光伏台区渗透率水平","Permeability_Type"), + ON_NETWORK_STATUS("报告状态","On-network_Status"), + AUDIT_STATUS("审核状态","Audit_Status"), + FILL_PROGRESS("填报进度","Fill_Progress"), + PROBLEM_SOURCES("问题来源","Problem_Sources"), + AREA_PQ_EVENT_TYPE("台区电能质量事件类型","area_pq_event_type"), + LINE_STATE("监测点状态","Line_State"), + DEVICE_STATUS("设备状态","Device_Status"), + //INTERFERENCE_SOURCE("监测对象类别","Interference_Source"), + PLAN_TAKE("计划采取实施","Plan_Take"), + MONITOR_OBJ("监测对象","Monitor_Obj"), + CONNET_GROUP_WAY("牵引站变压器接线方式","Connet_Group_Way"), + WORK_ORDER_STATUS("工单状态","Work_Order_Status"), + PROBLEM_TYPE("问题类型","Problem_Type"), + CHECK_STATUS("审核状态","Check_Status"), + CHECK_RESULT("审核处理","Check_Result"), + WORK_ORDER_PROCESS("工单流程","Work_Order_Process"), + ASSESS_RESULT("评估结果","Assess_Result"), + WORK_ORDER_TYPE("工单类型","Work_Order_Type"), + + PRIMARY_TYPE("一级业务类型","Primary_Type"), + DEV_CLASS("终端类型(治理)","Dev_Class"), + CS_STATISTICAL_TYPE("治理统计类型","Cs_Statistical_Type"), + LINE_POSITION("监测点位置","Line_Position"), + ALARM_LEVEL("警告级别","Alarm_Level"), + + + CS_DATA_TYPE("数据模型类别", "Cs_Data_Type"), + PROBLEM_INDICATORS("问题指标","Problem_Indicators"), + + + //pms + DEV_CATEGORY("装置类别","Device_Category"), + DEV_GRADE("终端等级","Dev_Level"), + INPUT_SIGNAL("测量信号输入形式","Signal_form"), + VOLTAGE_TRANSFORMER("电压互感器类型","Voltage_Transformer"), + Neutral_Point("中性点接地方式","Neutral_Point"), + DEVICE_REGIONLYPE("设备地区特征","Device_RegionLype"), + DEVICE_USERNATURE("设备使用性质代码","Device_UseNature"), + SUPV_TYPE("监督类型","supv_type"), + SUPV_OBJ_TYPE("监督对象类型","supv_obj_type"), + + evaluation_report("评估用户或报告分类编码","evaluation_report"), + + user_class("用户分类","user_class"), + + SUPV_STAGE("监督阶段","supv_stage"), + EFFECT_STATUS("实施状态","effect_status"), + MONITOR_TYPE("监督监测点类型","monitor_type"), + SUPV_PROBLEM_TYPE("监督问题类型","problem_type"), + RECTIFICATION_MEASURE("整改方案","RectificationMeasure"), + + SUPV_PLAN_STATUS("监督计划状态","plan_status"), + BILL_TYPE("单据类型","bill_type"), + SPECIALITY_TYPE("所属专业","speciality_type"), + RECTIFICATION_STATUS_TYPE("整改情况","rectification_status_type"), + file_type("附件分类"," file_type"), + problem_level_type("问题等级"," problem_level_type"), + + Station_Type("所属站别类型","Station_Type"), + + + APP_BASE_INFORMATION_TYPE("app基础信息类型","appInformationType"), + + APP_DEVICE_EVENT_TYPE("app设备事件类型","appDeviceEventType"), + + DEVICE_UNIT("数据单位类型","Device_Unit"), + //国网上送 + pms_disturb_type("pms国网上送干扰源类型","pms_disturb_type"), + pms_disturb_sort("pms国网上送干扰源类别","pms_disturb_sort"), + type_of_station("站房类型","type_of_station"), + File_status("档案状态","File_status"), + USER_CLASS("用户分类","User_Class"), + IMPORTANCE_LEVEL("重要性等级","Importance_Level"), + ELE_CLASS("用电类别","Ele_Class"), + INDUSTRY_TYPE("行业分类","industry_type"), + PLAN_STATUS("计划状态","plan_status"), + APP_EVENT("APP暂态事件类型","app_event"), + DEVICE_TYPE("治理装置类型编码","Device_Type"), + + CARRY_CAPCITY_USER_TYPE("承载能力评估用户类型","carry_capcity_user_type"), + CARRY_CAPCITY_CONNECTION_MODE("变压器连接方式","carry_capcity_connection_mode"), + + CARRY_CAPCITYUSER_MODE("用户模式","carry_capcity_user_mode"), + LOCAL_MUNICIPALITY("所属地市","local_municipality"), + INDUSTRY_TYPE_JB("行业类型-冀北","industry_type_jb"), + LOAD_LEVEL("负荷级别","load_level"), + SUPPLY_CONDITION("供电电源情况","supply_condition"), + + JIBEI_AREA("所属地市","jibei_area"), + Major_Nonlinear_Device("主要非线性设备类型","Major_Nonlinear_Device"), + EVALUATION_DEPT("主要非线性设备类型","evaluation_dept"), + EVALUATION_TYPE("评估类型","Evaluation_Type"), + ; + + + + + + private final String name; + private final String code; + + DicDataTypeEnum(String name,String code){ + this.name=name; + this.code=code; + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/enums/SystemResponseEnum.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/enums/SystemResponseEnum.java new file mode 100644 index 0000000..eeb09a4 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/enums/SystemResponseEnum.java @@ -0,0 +1,77 @@ +package com.njcn.product.system.dict.enums; + +import lombok.Getter; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月20日 09:56 + */ +@Getter +public enum SystemResponseEnum { + + /** + * 系统模块异常响应码的范围: + * A00350 ~ A00449 + */ + SYSTEM_COMMON_ERROR("A00350","系统模块异常"), + DICT_TYPE_NAME_REPEAT("A00351", "字典类型名称重复"), + DICT_DATA_NAME_REPEAT("A00352", "字典数据名称重复"), + AREA_CODE_REPEAT("A00353","行政区域编码重复"), + LOAD_TYPE_EMPTY("A00354","用能负荷数据为空"), + LINE_MARK_EMPTY("A00355","字典监测点评分等级数据为空"), + VOLTAGE_EMPTY("A00356","查询字典电压等级数据为空"), + + INTERFERENCE_EMPTY("A00356","查询字典干扰源类型数据为空"), + BUSINESS_EMPTY("A00356","查询字典行业类型数据为空"), + SYSTEM_TYPE_EMPTY("A00356","查询字典系统类型数据为空"), + DEV_TYPE_EMPTY("A00357","查询字典设备类型数据为空"), + MANUFACTURER("A00358","查询字典终端厂家数据为空"), + DEV_VARIETY("A00359","查询字典终端类型数据为空"), + + /*pms*/ + LINE_TYPE_VARIETY_EMPTY("A00360","查询字典监测点类型数据为空"), + LINE_STATE_EMPTY("A00361","查询字典监测点状态为空"), + LINE_TYPE_EMPTY("A00362","查询字典监测点类型状态为空"), + POTENTIAL_TYPE_EMPTY("A00363","查询字典电压互感器类型为空"), + Neutral_Mode_EMPTY("A00364","查询字典中性点接地方式为空"), + MONITOR_TAG_EMPTY("A00365","查询字典监测点标签类型为空"), + MONITORY_TYPE_EMPTY("A00366","查询字典监测对象类型为空"), + TERMINAL_WIRING_EMPTY("A00367","查询字典监测终端接线方式为空"), + MONITOR_TYPE_EMPTY("A00368","查询字典监测点类别为空"), + ACTIVATED_STATE("A00369","必须存在一个已激活的系统类型"), + ADVANCE_REASON("A00370","查询字典暂降原因为空"), + EFFECT_STATUS_EMPTY("A00370","查询字典实施状态为空"), + + EVENT_REPORT_REPEAT("A00361","暂态报告模板重复"), + NOT_EXISTED("A00361", "您查询的该条记录不存在"), + TIMER_NO_CLASS("A00361", "请检查定时任务是否添加"), + + /** + * 定时任务执行类不存在 + */ + TIMER_NOT_EXISTED("A00361", "定时任务执行类不存在"), + EXE_EMPTY_PARAM("A00361", "请检查定时器的id,定时器cron表达式,定时任务是否为空!"), + + /** + * 审计日志模块异常响应 + */ + NOT_FIND_FILE("A0300", "文件未备份或者备份文件为空,请先备份文件"), + LOG_EXCEPTION("A0301", "导入旧日志文件异常"), + LOG_EXCEPTION_TIME("A0302", "导入旧日志文件异常:缺少时间范围"), + DELETE_DATA("A0303", "导入旧日志文件异常:删除数据失败"), + MULTIPLE_CLICKS_LOG_FILE_WRITER("A0304", "当前文件备份数据未结束,请勿多次点击"), + MULTIPLE_CLICKS_RECOVER_LOG_FILE("A0303", "当前文件恢复数据未结束,请勿多次点击"), + + PAGE_SAME_NAME("A00357","页面名称重复"), + ; + + private final String code; + + private final String message; + + SystemResponseEnum(String code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/DictDataMapper.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/DictDataMapper.java new file mode 100644 index 0000000..40c9ddb --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/DictDataMapper.java @@ -0,0 +1,61 @@ +package com.njcn.product.system.dict.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import com.njcn.product.system.dict.pojo.po.DictData; +import com.njcn.product.system.dict.pojo.vo.DictDataVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface DictDataMapper extends BaseMapper { + + /** + * 分页查询字典数据 + * + * @param page 分页数据 + * @param queryWrapper 查询条件 + * @return 字典数据 + */ + Page page(@Param("page") Page page, @Param("ew") QueryWrapper queryWrapper); + + /** + * @param dictypeName 字典类型名称 + * @return 根据字典类型名称查询字典数据 + */ + List getDicDataByTypeName(@Param("dictypeName") String dictypeName); + + DictData getDicDataByName(@Param("dicName") String dicName); + + DictData getDicDataByNameAndType(@Param("dicName") String dicName, @Param("typeName") String typeName); + + DictData getDicDataByCodeAndType(@Param("dicCode") String dicCode, @Param("typeCode") String typeCode); + + DictData getDicDataByCode(@Param("code") String code); + + /** + * 根据字典类型名称&数据名称获取字典数据 + * + * @param dicTypeName 字典类型名称 + * @param dicDataName 字典数据名称 + * @return 字典数据 + */ + DictData getDicDataByNameAndTypeName(@Param("dicTypeName") String dicTypeName, @Param("dicDataName") String dicDataName); + + /** + * @param dictTypeCode 字典类型名称 + * @return 根据字典类型名称查询字典数据 + */ + List getDicDataByTypeCode(@Param("dictTypeCode") String dictTypeCode); + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/DictTypeMapper.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/DictTypeMapper.java new file mode 100644 index 0000000..981ccef --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/DictTypeMapper.java @@ -0,0 +1,25 @@ +package com.njcn.product.system.dict.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.system.dict.pojo.po.DictType; +import com.njcn.product.system.dict.pojo.vo.DictDataCache; + + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface DictTypeMapper extends BaseMapper { + + /** + * 查询所有的字典简单信息 + * @return 字典信息 + */ + List dictDataCache(); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/mapping/DictDataMapper.xml b/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/mapping/DictDataMapper.xml new file mode 100644 index 0000000..5240452 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/mapping/DictDataMapper.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/mapping/DictTypeMapper.xml b/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/mapping/DictTypeMapper.xml new file mode 100644 index 0000000..de9f26c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/mapper/mapping/DictTypeMapper.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictDataParam.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictDataParam.java new file mode 100644 index 0000000..68f6fd8 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictDataParam.java @@ -0,0 +1,95 @@ +package com.njcn.product.system.dict.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.web.constant.ValidMessage; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.*; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月17日 15:49 + */ +@Data +public class DictDataParam { + + + @ApiModelProperty("字典类型id") + @NotBlank(message = ValidMessage.DICT_TYPE_ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.DICT_TYPE_ID_FORMAT_ERROR) + private String typeId; + + + @ApiModelProperty("名称") + @NotBlank(message = ValidMessage.NAME_NOT_BLANK) + private String name; + + + @ApiModelProperty("编码") + @NotBlank(message = ValidMessage.CODE_NOT_BLANK) + private String code; + + + @ApiModelProperty("排序") + @NotNull(message = ValidMessage.SORT_NOT_NULL) + @Min(value = 0, message = ValidMessage.SORT_FORMAT_ERROR) + @Max(value = 999, message = ValidMessage.SORT_FORMAT_ERROR) + private Integer sort; + + + @ApiModelProperty("事件等级:0-普通;1-中等;2-严重(默认为0)") + private Integer level; + + @ApiModelProperty("与高级算法内部Id描述对应") + private Integer algoDescribe; + + + @ApiModelProperty("字典值,用于记录字典的计算值如10kV记录为 10") + private String value; + + + /** + * 更新操作实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class DictDataUpdateParam extends DictDataParam { + + /** + * 表Id + */ + @ApiModelProperty("id") + @NotBlank(message = ValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR) + private String id; + } + + /** + * 分页查询实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class DictDataQueryParam extends BaseParam { + + + + } + + /** + * 根据字典类型id分页查询字典数据 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class DicTypeIdQueryParam extends BaseParam { + @ApiModelProperty("字典类型id") + @NotBlank(message = ValidMessage.DICT_TYPE_ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.DICT_TYPE_ID_FORMAT_ERROR) + private String typeId; + + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictTreeParam.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictTreeParam.java new file mode 100644 index 0000000..c73d22a --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictTreeParam.java @@ -0,0 +1,84 @@ +package com.njcn.product.system.dict.pojo.param; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.web.constant.ValidMessage; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月17日 15:49 + */ +@Data +public class DictTreeParam { + + + + /** + * 父id + */ + @ApiModelProperty(value = "父id") + private String pid; + + + /** + * 名称 + */ + @ApiModelProperty(value = "名称") + @NotBlank(message = ValidMessage.NAME_NOT_BLANK) + private String name; + + /** + * 编码 + */ + @TableField(value = "编码") + @NotBlank(message = ValidMessage.CODE_NOT_BLANK) + private String code; + + /** + * 用于区分多种类型的字典树 0.台账对象类型 1.自定义报表指标类型 + */ + private Integer type; + + /** + * 根据type自定义内容,type:0用于区分对象类型是101电网侧 102用户侧 + */ + private String extend; + + /** + * 排序 + */ + private Integer sort; + + /** + * 描述 + */ + private String remark; + + + + + /** + * 更新操作实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class DictTreeUpdateParam extends DictTreeParam { + + + @ApiModelProperty("id") + @NotBlank(message = ValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR) + private String id; + } + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictTypeParam.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictTypeParam.java new file mode 100644 index 0000000..83569e1 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/param/DictTypeParam.java @@ -0,0 +1,82 @@ +package com.njcn.product.system.dict.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.web.constant.ValidMessage; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.*; + + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月17日 09:40 + */ +@Data +public class DictTypeParam { + + @ApiModelProperty("名称") + @NotBlank(message = ValidMessage.NAME_NOT_BLANK) + private String name; + + @ApiModelProperty("编码") + @NotBlank(message = ValidMessage.CODE_NOT_BLANK) + @Pattern(regexp = PatternRegex.ALL_CHAR_1_20, message = ValidMessage.CODE_FORMAT_ERROR) + private String code; + + + @ApiModelProperty("排序") + @NotNull(message = ValidMessage.SORT_NOT_NULL) + @Min(value = 0, message = ValidMessage.SORT_FORMAT_ERROR) + @Max(value = 999, message = ValidMessage.SORT_FORMAT_ERROR) + private Integer sort; + + + @ApiModelProperty("开启等级:0-不开启;1-开启,默认不开启") + @NotNull(message = ValidMessage.OPEN_LEVEL_NOT_NULL) + @Min(value = 0, message = ValidMessage.OPEN_LEVEL_FORMAT_ERROR) + @Max(value = 1, message = ValidMessage.OPEN_LEVEL_FORMAT_ERROR) + private Integer openLevel; + + + @ApiModelProperty("开启算法描述:0-不开启;1-开启,默认不开启") + @NotNull(message = ValidMessage.OPEN_DESCRIBE_NOT_NULL) + @Min(value = 0, message = ValidMessage.OPEN_DESCRIBE_FORMAT_ERROR) + @Max(value = 1, message = ValidMessage.OPEN_DESCRIBE_FORMAT_ERROR) + private Integer openDescribe; + + + @ApiModelProperty("描述") + private String remark; + + /** + * 更新操作实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class DictTypeUpdateParam extends DictTypeParam { + + + @ApiModelProperty("id") + @NotBlank(message = ValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR) + private String id; + + } + + /** + * 分页查询实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class DictTypeQueryParam extends BaseParam { + + + } + +} + + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/po/DictData.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/po/DictData.java new file mode 100644 index 0000000..13a466d --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/po/DictData.java @@ -0,0 +1,65 @@ +package com.njcn.product.system.dict.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_dict_data") +public class DictData extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 字典数据表Id + */ + private String id; + + /** + * 字典类型表Id + */ + private String typeId; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 排序 + */ + private Integer sort; + + /** + * 事件等级:0-普通;1-中等;2-严重(默认为0) + */ + private Integer level; + + /** + * 与高级算法内部Id描述对应; + */ + private Integer algoDescribe; + + /** + * 目前只用于表示电压等级数值 + */ + private String value; + + /** + * 状态:0-删除 1-正常 + */ + private Integer state; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/po/DictType.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/po/DictType.java new file mode 100644 index 0000000..b2b7682 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/po/DictType.java @@ -0,0 +1,62 @@ +package com.njcn.product.system.dict.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_dict_type") +public class DictType extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 字典类型表Id + */ + private String id; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 排序 + */ + private Integer sort; + + /** + * 开启等级:0-不开启;1-开启,默认不开启 + */ + private Integer openLevel; + + + /** + * 开启描述:0-不开启;1-开启,默认不开启 + */ + private Integer openDescribe; + + + /** + * 描述 + */ + private String remark; + + /** + * 状态:0-删除 1-正常 + */ + private Integer state; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/vo/DictDataCache.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/vo/DictDataCache.java new file mode 100644 index 0000000..c9c2ea5 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/vo/DictDataCache.java @@ -0,0 +1,33 @@ +package com.njcn.product.system.dict.pojo.vo; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年03月24日 16:06 + */ +@Data +public class DictDataCache implements Serializable { + + private String id; + + private String name; + + private String code; + + private String value; + + private int sort; + + private String typeId; + + private String typeName; + + private String typeCode; + + private Integer algoDescribe; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/vo/DictDataVO.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/vo/DictDataVO.java new file mode 100644 index 0000000..628bf95 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/pojo/vo/DictDataVO.java @@ -0,0 +1,63 @@ +package com.njcn.product.system.dict.pojo.vo; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月20日 15:52 + */ +@Data +public class DictDataVO implements Serializable { + + + private static final long serialVersionUID = 1L; + + /** + * 字典数据表Id + */ + private String id; + + /** + * 字典类型表名称 + */ + private String typeName; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 排序 + */ + private Integer sort; + + /** + * 事件等级:0-普通;1-中等;2-严重(默认为0) + */ + private Integer level; + + /** + * 与高级算法内部Id描述对应; + */ + private Integer algoDescribe; + + /** + * 字典值 + */ + private String value; + + /** + * 状态:0-删除 1-正常 + */ + private Integer state; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/service/IDictDataService.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/service/IDictDataService.java new file mode 100644 index 0000000..16ea5fe --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/service/IDictDataService.java @@ -0,0 +1,123 @@ +package com.njcn.product.system.dict.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.system.dict.pojo.param.DictDataParam; +import com.njcn.product.system.dict.pojo.po.DictData; +import com.njcn.product.system.dict.pojo.vo.DictDataVO; + + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IDictDataService extends IService { + + /** + * 根据前台传递参数,分页查询字典数据 + * @param queryParam 查询参数 + * @return 字典列表 + */ + Page listDictData(DictDataParam.DictDataQueryParam queryParam); + + /** + * 新增数据字典 + * @param dictDataParam 字典数据 + * @return 操作结果 + */ + boolean addDictData(DictDataParam dictDataParam); + + /** + * 更新字典数据 + * @param updateParam 字典数据 + * @return 操作结果 + */ + boolean updateDictData(DictDataParam.DictDataUpdateParam updateParam); + + /** + * 批量逻辑删除字典数据 + * @param ids 字典id集合 + * @return 操作结果 + */ + boolean deleteDictData(List ids); + + /** + * 根据字典类型id查询字典信息 + */ + Page getTypeIdData(DictDataParam.DicTypeIdQueryParam queryParam); + + /** + * + * @param dicIndex 字典id + * @return 根据字典id查询字典数据 + */ + DictData getDicDataById(String dicIndex); + + /** + * + * @param dictypeName 字典类型名称 + * @return 根据字典类型名称查询字典数据 + */ + List getDicDataByTypeName(String dictypeName); + + /** + * + * @param dictTypeCode 字典类型code + * @return 根据字典类型名称查询字典数据 + */ + List getDicDataByTypeCode(String dictTypeCode); + + /** + * + * @param dicName 字典名称 + * @return 根据字典名称查询字典数据 + */ + DictData getDicDataByName(String dicName); + + /** + * + * @param dicName 字典名称,类型名称 + * @return 根据字典名称查询字典数据 + */ + DictData getDicDataByNameAndType(String dicName,String typeName); + + /** + * + * @param dicCode 字典Code,类型名称 + * @return 根据字典Code查询字典数据 + */ + DictData getDicDataByCodeAndType(String dicCode,String typeCode); + /** + * + * @param code 字典code + * @return 根据字典code查询字典数据 + */ + DictData getDicDataByCode(String code); + + + /** + * 根据字典类型名称&数据名称获取字典数据 + * + * @param dicTypeName 字典类型名称 + * @param dicDataName 字典数据名称 + * @return 字典数据 + */ + DictData getDicDataByNameAndTypeName(String dicTypeName, String dicDataName); + + /** + * 后台新增字典数据 + * @param dicTypeName 类型名称 + * @param dicDataName 数据名称 + * @return 新增后的字典数据 + */ + DictData addDictData(String dicTypeName, String dicDataName); + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/service/IDictTypeService.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/service/IDictTypeService.java new file mode 100644 index 0000000..5c75dab --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/service/IDictTypeService.java @@ -0,0 +1,67 @@ +package com.njcn.product.system.dict.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.common.pojo.dto.SimpleTreeDTO; +import com.njcn.product.system.dict.pojo.param.DictTypeParam; +import com.njcn.product.system.dict.pojo.po.DictType; + + +import java.util.List; + +/** + * @author hongawen + * @since 2021-12-13 + */ +public interface IDictTypeService extends IService { + + /** + * 根据前台传递参数,分页查询字典类型数据 + * @param queryParam 查询参数 + * @return 字典列表 + */ + Page listDictTypes(DictTypeParam.DictTypeQueryParam queryParam); + + /** + * 新增字典类型数据 + * + * @param dictTypeParam 字典类型数据 + * @return 操作结果 + */ + boolean addDictType(DictTypeParam dictTypeParam); + + /** + * 修改字典类型 + * + * @param updateParam 字典类型数据 + * @return 操作结果 + */ + boolean updateDictType(DictTypeParam.DictTypeUpdateParam updateParam); + + /** + * 批量逻辑删除字典类型数据 + * @param ids id集合 + * @return 操作结果 + */ + boolean deleteDictType(List ids); + + /** + * 获取所有字典数据基础信息 + * @return 返回所有字典数据 + */ + List dictDataCache(); + + /** + * 根据名称获取字典类型数据 + * @param dicTypeName 类型名称 + * @return 类型数据 + */ + DictType getByName(String dicTypeName); + + /** + * 根据名称新增字典类型数据 + * @param dicTypeName 类型名称 + * @return 类型数据 + */ + DictType addByName(String dicTypeName); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/service/impl/DictDataServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/service/impl/DictDataServiceImpl.java new file mode 100644 index 0000000..9237474 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/service/impl/DictDataServiceImpl.java @@ -0,0 +1,203 @@ +package com.njcn.product.system.dict.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.db.constant.DbConstant; + +import com.njcn.product.system.dict.enums.DicDataTypeEnum; +import com.njcn.product.system.dict.mapper.DictDataMapper; +import com.njcn.product.system.dict.pojo.param.DictDataParam; +import com.njcn.product.system.dict.pojo.po.DictData; +import com.njcn.product.system.dict.pojo.po.DictType; +import com.njcn.product.system.dict.pojo.vo.DictDataVO; +import com.njcn.product.system.dict.service.IDictDataService; +import com.njcn.product.system.dict.service.IDictTypeService; +import com.njcn.product.system.dict.enums.SystemResponseEnum; +import com.njcn.web.factory.PageFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; + +/** + * @author hongawen + * @since 2021-12-13 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DictDataServiceImpl extends ServiceImpl implements IDictDataService { + + private final IDictTypeService dictTypeService; + + + @Override + public Page listDictData(DictDataParam.DictDataQueryParam queryParam) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(queryParam)) { + //查询参数不为空,进行条件填充 + if (StrUtil.isNotBlank(queryParam.getSearchValue())) { + //字典类型表,仅提供名称、编码模糊查询 + queryWrapper + .and(param -> param.like("sys_dict_data.name", queryParam.getSearchValue()) + .or().like("sys_dict_data.code", queryParam.getSearchValue())); + } + //排序 + if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) { + queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy())); + } else { + //没有排序参数,默认根据sort字段排序,没有排序字段的,根据updateTime更新时间排序 + queryWrapper.orderBy(true, true, "sys_dict_data.sort"); + } + } + queryWrapper.ne("sys_dict_data.state", DataStateEnum.DELETED.getCode()); + //初始化分页数据 + return this.baseMapper.page(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), queryWrapper); + } + + @Override + public boolean addDictData(DictDataParam dictDataParam) { + checkDicDataName(dictDataParam, false); + DictData dictData = new DictData(); + BeanUtil.copyProperties(dictDataParam, dictData); + //默认为正常状态 + dictData.setState(DataStateEnum.ENABLE.getCode()); + return this.save(dictData); + } + + @Override + public boolean updateDictData(DictDataParam.DictDataUpdateParam updateParam) { + checkDicDataName(updateParam, true); + DictData dictData = new DictData(); + BeanUtil.copyProperties(updateParam, dictData); + return this.updateById(dictData); + } + + @Override + public boolean deleteDictData(List ids) { + return this.lambdaUpdate() + .set(DictData::getState, DataStateEnum.DELETED.getCode()) + .in(DictData::getId, ids) + .update(); + } + + @Override + public Page getTypeIdData(DictDataParam.DicTypeIdQueryParam queryParam) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(queryParam)) { + //查询参数不为空,进行条件填充 + if (StrUtil.isNotBlank(queryParam.getSearchValue())) { + //字典类型表,仅提供名称、编码模糊查询 + queryWrapper + .and(param -> param.like("sys_dict_data.name", queryParam.getSearchValue()) + .or().like("sys_dict_data.code", queryParam.getSearchValue())); + } + //排序 + if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) { + queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy())); + } else { + //没有排序参数,默认根据sort字段排序,没有排序字段的,根据updateTime更新时间排序 + queryWrapper.orderBy(true, true, "sys_dict_data.sort"); + } + } + queryWrapper.ne("sys_dict_data.state", DataStateEnum.DELETED.getCode()); + queryWrapper.eq("sys_dict_data.type_id", queryParam.getTypeId()); + //初始化分页数据 + return this.baseMapper.page(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), queryWrapper); + } + + @Override + public DictData getDicDataById(String dicIndex) { + + return this.baseMapper.selectById(dicIndex); + } + + @Override + public List getDicDataByTypeName(String dictTypeName) { + + return this.baseMapper.getDicDataByTypeName(dictTypeName); + } + + @Override + public List getDicDataByTypeCode(String dictTypeCode) { + + return this.baseMapper.getDicDataByTypeCode(dictTypeCode); + } + + + @Override + public DictData getDicDataByName(String dicName) { + return this.baseMapper.getDicDataByName(dicName); + } + + @Override + public DictData getDicDataByNameAndType(String dicName, String typeName) { + return this.baseMapper.getDicDataByNameAndType(dicName, typeName); + } + + @Override + public DictData getDicDataByCodeAndType(String dicCode, String typeCode) { + return this.baseMapper.getDicDataByCodeAndType(dicCode, typeCode); + } + + @Override + public DictData getDicDataByCode(String code) { + return this.baseMapper.getDicDataByCode(code); + } + + + + @Override + public DictData getDicDataByNameAndTypeName(String dicTypeName, String dicDataName) { + return this.baseMapper.getDicDataByNameAndTypeName(dicTypeName, dicDataName); + } + + @Override + public DictData addDictData(String dicTypeName, String dicDataName) { + //根据type名称获取index,如果不存在该字典类型,则新增该字典类型 + DictType dictType = dictTypeService.getByName(dicTypeName); + if (Objects.isNull(dictType)) { + dictType = dictTypeService.addByName(dicTypeName); + } + DictData dictData = new DictData(); + dictData.setTypeId(dictType.getId()); + dictData.setName(dicDataName); + dictData.setCode(dicDataName); + dictData.setSort(0); + dictData.setLevel(0); + dictData.setState(DataStateEnum.ENABLE.getCode()); + this.save(dictData); + return dictData; + } + + /** + * 校验参数,检查是否存在相同名称的字典类型 + */ + private void checkDicDataName(DictDataParam dictDataParam, boolean isExcludeSelf) { + LambdaQueryWrapper dictDataLambdaQueryWrapper = new LambdaQueryWrapper<>(); + dictDataLambdaQueryWrapper + .eq(DictData::getName, dictDataParam.getName()) + .eq(DictData::getTypeId, dictDataParam.getTypeId()) + .eq(DictData::getState, DataStateEnum.ENABLE.getCode()); + //更新的时候,需排除当前记录 + if (isExcludeSelf) { + if (dictDataParam instanceof DictDataParam.DictDataUpdateParam) { + dictDataLambdaQueryWrapper.ne(DictData::getId, ((DictDataParam.DictDataUpdateParam) dictDataParam).getId()); + } + } + int countByAccount = this.count(dictDataLambdaQueryWrapper); + //大于等于1个则表示重复 + if (countByAccount >= 1) { + throw new BusinessException(SystemResponseEnum.DICT_DATA_NAME_REPEAT); + } + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/dict/service/impl/DictTypeServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/system/dict/service/impl/DictTypeServiceImpl.java new file mode 100644 index 0000000..909a4aa --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/dict/service/impl/DictTypeServiceImpl.java @@ -0,0 +1,159 @@ +package com.njcn.product.system.dict.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.dto.SimpleDTO; +import com.njcn.common.pojo.dto.SimpleTreeDTO; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.db.constant.DbConstant; + +import com.njcn.product.system.dict.mapper.DictTypeMapper; +import com.njcn.product.system.dict.pojo.param.DictTypeParam; +import com.njcn.product.system.dict.pojo.po.DictType; +import com.njcn.product.system.dict.pojo.vo.DictDataCache; +import com.njcn.product.system.dict.service.IDictTypeService; +import com.njcn.product.system.dict.enums.SystemResponseEnum; +import com.njcn.web.factory.PageFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author hongawen + * @since 2021-12-13 + */ +@Service +@RequiredArgsConstructor +public class DictTypeServiceImpl extends ServiceImpl implements IDictTypeService { + + @Override + public Page listDictTypes(DictTypeParam.DictTypeQueryParam queryParam) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(queryParam)) { + //查询参数不为空,进行条件填充 + if (StrUtil.isNotBlank(queryParam.getSearchValue())) { + //字典类型表,仅提供名称、编码模糊查询 + queryWrapper + .and(param -> param.like("sys_dict_type.name", queryParam.getSearchValue()) + .or().like("sys_dict_type.code", queryParam.getSearchValue()) + ); + } + //排序 + if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) { + queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy())); + } else { + //没有排序参数,默认根据sort字段排序,没有排序字段的,根据updateTime更新时间排序 + queryWrapper.orderBy(true, true, "sys_dict_type.sort"); + } + } + queryWrapper.ne("sys_dict_type.state", DataStateEnum.DELETED.getCode()); + return this.baseMapper.selectPage(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), queryWrapper); + } + + @Override + public boolean addDictType(DictTypeParam dictTypeParam) { + checkDicTypeName(dictTypeParam, false); + DictType dictType = new DictType(); + BeanUtil.copyProperties(dictTypeParam, dictType); + //默认为正常状态 + dictType.setState(DataStateEnum.ENABLE.getCode()); + return this.save(dictType); + } + + @Override + public boolean updateDictType(DictTypeParam.DictTypeUpdateParam updateParam) { + checkDicTypeName(updateParam, true); + DictType dictType = new DictType(); + BeanUtil.copyProperties(updateParam, dictType); + return this.updateById(dictType); + } + + @Override + public boolean deleteDictType(List ids) { + return this.lambdaUpdate() + .set(DictType::getState, DataStateEnum.DELETED.getCode()) + .in(DictType::getId, ids) + .update(); + } + + + @Override + public List dictDataCache() { + List allDictData = this.baseMapper.dictDataCache(); + Map> dictDataCacheMap = allDictData.stream() + .collect(Collectors.groupingBy(DictDataCache::getTypeId)); + return dictDataCacheMap.keySet().stream().map(typeId -> { + SimpleTreeDTO simpleTreeDTO = new SimpleTreeDTO(); + List dictDataCaches = dictDataCacheMap.get(typeId); + List simpleDTOList = dictDataCaches.stream().map(dictDataCache -> { + simpleTreeDTO.setCode(dictDataCache.getTypeCode()); + simpleTreeDTO.setId(dictDataCache.getTypeId()); + simpleTreeDTO.setName(dictDataCache.getTypeName()); + SimpleDTO simpleDTO = new SimpleDTO(); + simpleDTO.setCode(dictDataCache.getCode()); + simpleDTO.setId(dictDataCache.getId()); + simpleDTO.setName(dictDataCache.getName()); + simpleDTO.setSort(dictDataCache.getSort()); + simpleDTO.setValue(dictDataCache.getValue()); + simpleDTO.setAlgoDescribe(dictDataCache.getAlgoDescribe()); + return simpleDTO; + }).sorted(Comparator.comparing(SimpleDTO::getSort)).collect(Collectors.toList()); + simpleTreeDTO.setChildren(simpleDTOList); + return simpleTreeDTO; + }).collect(Collectors.toList()); + } + + @Override + public DictType getByName(String dicTypeName) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(DictType::getName, dicTypeName) + .eq(DictType::getState, DataStateEnum.ENABLE.getCode()); + return this.baseMapper.selectOne(lambdaQueryWrapper); + } + + @Override + public DictType addByName(String dicTypeName) { + DictType dictType = new DictType(); + dictType.setName(dicTypeName); + dictType.setCode(dicTypeName); + dictType.setSort(0); + dictType.setOpenDescribe(0); + dictType.setOpenLevel(0); + dictType.setState(DataStateEnum.ENABLE.getCode()); + this.save(dictType); + return dictType; + } + + + + /** + * 校验参数,检查是否存在相同名称的字典类型 + */ + private void checkDicTypeName(DictTypeParam dictTypeParam, boolean isExcludeSelf) { + LambdaQueryWrapper dictTypeLambdaQueryWrapper = new LambdaQueryWrapper<>(); + dictTypeLambdaQueryWrapper + .eq(DictType::getName, dictTypeParam.getName()) + .eq(DictType::getState, DataStateEnum.ENABLE.getCode()); + //更新的时候,需排除当前记录 + if (isExcludeSelf) { + if (dictTypeParam instanceof DictTypeParam.DictTypeUpdateParam) { + dictTypeLambdaQueryWrapper.ne(DictType::getId, ((DictTypeParam.DictTypeUpdateParam) dictTypeParam).getId()); + } + } + int countByAccount = this.count(dictTypeLambdaQueryWrapper); + //大于等于1个则表示重复 + if (countByAccount >= 1) { + throw new BusinessException(SystemResponseEnum.DICT_TYPE_NAME_REPEAT); + } + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/controller/ConfigController.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/controller/ConfigController.java new file mode 100644 index 0000000..e7fb163 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/controller/ConfigController.java @@ -0,0 +1,216 @@ +package com.njcn.product.system.theme.controller; + + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.common.utils.LogUtil; + +import com.njcn.product.system.dict.enums.SystemResponseEnum; +import com.njcn.product.system.theme.pojo.param.ConfigParam; +import com.njcn.product.system.theme.pojo.po.Config; +import com.njcn.product.system.theme.service.IConfigService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Objects; + +/** + *

+ * 前端控制器 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Slf4j +@Api(tags = "系统配置操作") +@RestController +@RequestMapping("/config") +@RequiredArgsConstructor +public class ConfigController extends BaseController { + + private final IConfigService iConfigService; + + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getSysConfig") + @ApiOperation("获取系统配置") + public HttpResult getSysConfig() { + String methodDescribe = getMethodDescribe("getSysConfig"); + LogUtil.njcnDebug(log, "{}", methodDescribe, methodDescribe); + Config config = iConfigService.lambdaQuery() + .eq(Config::getState, DataStateEnum.ENABLE.getCode()) + .one(); + if (Objects.isNull(config)) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.NO_DATA, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, config, methodDescribe); + } + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getSysConfigData") + @ApiOperation("获取系统配置列表") + public HttpResult> getSysConfigData() { + String methodDescribe = getMethodDescribe("getSysConfigData"); + LogUtil.njcnDebug(log, "{}", methodDescribe, methodDescribe); + List res = iConfigService.getList(); + if (CollectionUtils.isEmpty(res)) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.NO_DATA, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, res, methodDescribe); + } + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getSysConfigById") + @ApiOperation("根据配置Id获取系统配置") + @ApiImplicitParam(name = "id", value = "参数id", required = true) + public HttpResult getSysConfigById(@RequestParam("id") String id) { + String methodDescribe = getMethodDescribe("getSysConfigById"); + LogUtil.njcnDebug(log, "{}", methodDescribe, id); + Config config = iConfigService.getById(id); + if (Objects.isNull(config)) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.NO_DATA, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, config, methodDescribe); + } + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/updateSysConfigById") + @ApiOperation("根据配置Id更改(激活)系统状态") + @ApiImplicitParam(name = "id", value = "参数id", required = true) + public HttpResult updateSysConfigById(@RequestParam("id") String id) { + String methodDescribe = getMethodDescribe("updateSysConfigById"); + LogUtil.njcnDebug(log, "{}", methodDescribe, id); + Config config = iConfigService.getById(id); + if (!Objects.isNull(config)) { + iConfigService.update( new UpdateWrapper().eq("sys_config.State", DataStateEnum.ENABLE.getCode()) + .set("sys_config.State", DataStateEnum.DELETED.getCode())); + iConfigService.update( new UpdateWrapper().eq("sys_config.Id", id) + .set("sys_config.State", DataStateEnum.ENABLE.getCode())); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + }else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.ID_NOT_EXIST, null, methodDescribe); + } + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/removeSysConfigById") + @ApiOperation("根据配置Id删除系统配置") + @ApiImplicitParam(name = "id", value = "参数id", required = true) + public HttpResult removeSysConfigById(@RequestParam("id") String id) { + String methodDescribe = getMethodDescribe("removeSysConfigById"); + LogUtil.njcnDebug(log, "{}", methodDescribe, id); + int count = iConfigService.count(new LambdaQueryWrapper() + .eq(Config::getState, DataStateEnum.ENABLE.getCode()) + .ne(Config::getId, id)); + if(count==0){ + //不可更改当前激活状态,必须保留一个激活系统 + throw new BusinessException(SystemResponseEnum.ACTIVATED_STATE); + } + boolean res = iConfigService.removeById(id); + if (res) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.ID_NOT_EXIST, null, methodDescribe); + } + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD) + @PostMapping("/addSysConfig") + @ApiOperation("新增系统配置") + @ApiImplicitParam(name = "configParam", value = "新增配置实体", required = true) + public HttpResult addSysConfig(@RequestBody @Validated ConfigParam configParam) { + String methodDescribe = getMethodDescribe("addSysConfig"); + LogUtil.njcnDebug(log, "{}", methodDescribe, configParam); + boolean res = iConfigService.addSysConfig(configParam); + if (res) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + throw new BusinessException(CommonResponseEnum.FAIL); + } + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE) + @PostMapping("/updateSysConfig") + @ApiOperation("修改系统配置") + @ApiImplicitParam(name = "configUpdateParam", value = "更新配置实体", required = true) + public HttpResult updateSysConfig(@RequestBody @Validated ConfigParam.ConfigUpdateParam configUpdateParam) { + String methodDescribe = getMethodDescribe("updateSysConfig"); + LogUtil.njcnDebug(log, "{}", methodDescribe, configUpdateParam); + boolean res = iConfigService.updateSysConfig(configUpdateParam); + if (res) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } else { + throw new BusinessException(CommonResponseEnum.FAIL); + } + } + + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/系统扩容操作") + @ApiOperation("系统扩容操作") + public HttpResult addMemory(@RequestParam("size")Integer sizeInMB) { + String methodDescribe = getMethodDescribe("addMemory"); + + try { + // 将MB转换为字节 + long sizeInBytes = sizeInMB * 1024 * 1024; + + // 分配一个足够大的byte数组 + byte[] memory = new byte[(int) sizeInBytes]; + + // 为了确保JVM不会优化掉这个内存分配(因为它可能认为这个变量未使用), + // 我们可以对数组进行简单的操作,比如填充数据 + for (int i = 0; i < memory.length; i++) { + // 简单的数据填充 + memory[i] = (byte) (i % 256); + } + + // 实际上,你可能不需要对数组进行填充,因为仅仅是分配就足以占用内存。 + // 但填充可以确保内存被实际使用,而不是被JVM优化掉。 + + // 注意:这里有一个int类型的限制,因为数组长度是int类型。 + // 如果你需要分配超过Integer.MAX_VALUE字节的内存,你将需要找到其他方法(比如使用多个数组) + + System.out.println("Allocated approximately " + sizeInMB + " MB of memory."); + + // 为了让效果更明显,你可以尝试保持这个引用,或者让这个方法运行足够长的时间 + // 以便你可以观察JVM的行为(比如,使用jconsole或jvisualvm等工具) + // 注意:在实际应用中,你应该在不再需要时释放这个引用,以避免内存泄漏。 + + } catch (OutOfMemoryError e) { + System.err.println("Failed to allocate memory: " + e.getMessage()); + } + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + +} + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/controller/FunctionController.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/controller/FunctionController.java new file mode 100644 index 0000000..9cd8a4a --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/controller/FunctionController.java @@ -0,0 +1,149 @@ +package com.njcn.product.system.theme.controller; + + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.common.utils.LogUtil; + +import com.njcn.product.system.theme.pojo.vo.FunctionVO; +import com.njcn.product.system.theme.service.IFunctionService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + *

+ * 前端控制器 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Validated +@Slf4j +@RestController +@RequestMapping("/function") +@Api(tags = "菜单信息管理") +@AllArgsConstructor +public class FunctionController extends BaseController { + + private final IFunctionService functionService; + +// /** +// * 新增资源 +// * @param functionParam 资源数据 +// */ +// @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD) +// @PostMapping("/add") +// @ApiOperation("新增菜单") +// @ApiImplicitParam(name = "functionParam", value = "菜单数据", required = true) +// public HttpResult add(@RequestBody @Validated FunctionParam functionParam) { +// String methodDescribe = getMethodDescribe("add"); +// LogUtil.njcnDebug(log, "{},菜单数据为:{}", methodDescribe, functionParam); +// boolean result = functionService.addFunction(functionParam); +// if (result) { +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); +// } else { +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); +// } +// } +// +// @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE) +// @PutMapping("/update") +// @ApiOperation("修改菜单") +// @ApiImplicitParam(name = "functionParam", value = "菜单数据", required = true) +// public HttpResult update(@RequestBody @Validated FunctionParam.FunctionUpdateParam functionParam) { +// String methodDescribe = getMethodDescribe("update"); +// LogUtil.njcnDebug(log, "{},更新的菜单信息为:{}", methodDescribe,functionParam); +// boolean result = functionService.updateFunction(functionParam); +// if (result){ +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); +// } else { +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); +// } +// } +// +// @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DELETE) +// @DeleteMapping("/delete") +// @ApiOperation("删除菜单") +// @ApiImplicitParam(name = "id", value = "菜单id", required = true) +// public HttpResult delete(@RequestParam @Validated String id) { +// String methodDescribe = getMethodDescribe("delete"); +// LogUtil.njcnDebug(log, "{},删除的菜单id为:{}", methodDescribe,id); +// functionService.deleteFunction(id); +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); +// } +// +// @OperateInfo(info = LogEnum.SYSTEM_COMMON) +// @GetMapping("/functionTree") +// @ApiOperation("菜单树") +// public HttpResult> getFunctionTree() { +// String methodDescribe = getMethodDescribe("getFunctionTree"); +// List list = functionService.getFunctionTree(); +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,list,methodDescribe); +// } +// +// @OperateInfo(info = LogEnum.SYSTEM_COMMON) +// @GetMapping("/getFunctionById") +// @ApiOperation("菜单详情") +// @ApiImplicitParam(name = "id", value = "菜单id", required = true) +// public HttpResult getFunctionById(String id){ +// String methodDescribe = getMethodDescribe("getFunctionById"); +// LogUtil.njcnDebug(log, "{},菜单id为:{}", methodDescribe,id); +// Function function = functionService.getFunctionById(id); +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,function,methodDescribe); +// } +// +// @OperateInfo(info = LogEnum.SYSTEM_COMMON) +// @GetMapping("/getButtonById") +// @ApiOperation("获取按钮") +// @ApiImplicitParam(name = "id", value = "菜单id", required = true) +// public HttpResult> getButtonById(String id){ +// String methodDescribe = getMethodDescribe("getButtonById"); +// LogUtil.njcnDebug(log, "{},菜单id为:{}", methodDescribe,id); +// List list = functionService.getButtonsById(id); +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,list,methodDescribe); +// } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getRouteMenu") + @ApiOperation("路由菜单") + public HttpResult> getRouteMenu(){ + String methodDescribe = getMethodDescribe("getRouteMenu"); + List list = functionService.getRouteMenu(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,list,methodDescribe); + } +// +// @OperateInfo(operateType = OperateType.UPDATE,info = LogEnum.SYSTEM_MEDIUM) +// @PostMapping("/assignFunctionByRoleIndexes") +// @ApiOperation("角色分配菜单") +// @ApiImplicitParam(name = "roleFunctionComponent", value = "角色信息", required = true) +// public HttpResult assignFunctionByRoleIndexes(@RequestBody @Validated RoleParam.RoleFunctionComponent roleFunctionComponent) { +// String methodDescribe = getMethodDescribe("assignFunctionByRoleIndexes"); +// LogUtil.njcnDebug(log, "{},传入的角色id和资源id集合为:{}", methodDescribe,roleFunctionComponent); +// boolean result = functionService.updateRoleComponent(roleFunctionComponent); +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); +// } +// +// @OperateInfo(info = LogEnum.SYSTEM_COMMON) +// @GetMapping("/userFunctionTree") +// @ApiOperation("用户菜单树") +// public HttpResult> getUserFunctionTree() { +// String methodDescribe = getMethodDescribe("getUserFunctionTree"); +// List list = functionService.getUserFunctionTree(); +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,list,methodDescribe); +// } +} + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/controller/ThemeController.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/controller/ThemeController.java new file mode 100644 index 0000000..1c1dcb4 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/controller/ThemeController.java @@ -0,0 +1,55 @@ +package com.njcn.product.system.theme.controller; + + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; + +import com.njcn.product.system.theme.pojo.po.Theme; +import com.njcn.product.system.theme.service.IThemeService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + *

+ * 前端控制器 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Validated +@Slf4j +@RestController +@RequestMapping("/theme") +@Api(tags = "主题管理") +@AllArgsConstructor +public class ThemeController extends BaseController { + + private final IThemeService themeService; + + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getTheme") + @ApiOperation("当前主题") + public HttpResult getTheme(){ + String methodDescribe = getMethodDescribe("getTheme"); + Theme theme = themeService.getTheme(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,theme,methodDescribe); + } + + +} + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/ConfigMapper.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/ConfigMapper.java new file mode 100644 index 0000000..3342873 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/ConfigMapper.java @@ -0,0 +1,21 @@ +package com.njcn.product.system.theme.mapper; + + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.system.theme.pojo.po.Config; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface ConfigMapper extends BaseMapper { + + List getList(); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/FunctionMapper.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/FunctionMapper.java new file mode 100644 index 0000000..1af2d83 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/FunctionMapper.java @@ -0,0 +1,29 @@ +package com.njcn.product.system.theme.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.system.theme.pojo.po.Function; +import com.njcn.product.system.theme.pojo.vo.FunctionVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface FunctionMapper extends BaseMapper { + + List getAllFunctions(); + + List getFunctionsByList(@Param("list")List functionList); + + List getUserFunctionsByList(@Param("list")List functionList); + + List getByList(@Param("list")List functionList); + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/HomePageMapper.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/HomePageMapper.java new file mode 100644 index 0000000..ef6cd5a --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/HomePageMapper.java @@ -0,0 +1,16 @@ +package com.njcn.product.system.theme.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.system.theme.pojo.po.HomePage; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface HomePageMapper extends BaseMapper { + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/RoleFunctionMapper.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/RoleFunctionMapper.java new file mode 100644 index 0000000..64a13bf --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/RoleFunctionMapper.java @@ -0,0 +1,25 @@ +package com.njcn.product.system.theme.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.system.theme.pojo.po.RoleFunction; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface RoleFunctionMapper extends BaseMapper { + /** + * 根据角色id集合查询是否绑定 + * @param ids + * @return + */ + List selectRoleFunction(@Param("ids")List ids); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/ThemeMapper.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/ThemeMapper.java new file mode 100644 index 0000000..41fa66b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/ThemeMapper.java @@ -0,0 +1,16 @@ +package com.njcn.product.system.theme.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.system.theme.pojo.po.Theme; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface ThemeMapper extends BaseMapper { + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/ConfigMapper.xml b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/ConfigMapper.xml new file mode 100644 index 0000000..e0087fa --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/ConfigMapper.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/FunctionMapper.xml b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/FunctionMapper.xml new file mode 100644 index 0000000..d24d6db --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/FunctionMapper.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/RoleFunctionMapper.xml b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/RoleFunctionMapper.xml new file mode 100644 index 0000000..2399ded --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/mapper/mapping/RoleFunctionMapper.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/ConfigParam.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/ConfigParam.java new file mode 100644 index 0000000..9d56cb0 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/ConfigParam.java @@ -0,0 +1,78 @@ +package com.njcn.product.system.theme.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.web.constant.ValidMessage; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.*; +import java.math.BigDecimal; + +/** + * @version 1.0.0 + * @author: chenchao + * @date: 2022/08/09 15:23 + */ +@Data +public class ConfigParam { + + /** + * 系统类型 + */ + @ApiModelProperty("系统类型:0-省级系统;1-企业系统;2-数据中心") + @NotNull(message = "系统类型不可为空") + @Max(value = 2) + @Min(value = 0) + private Integer type; + + /** + * 数据上报 + */ + @ApiModelProperty("数据上报(以逗号分割,比如:冀北,网公司)默认为空") + private String dataReport; + + /** + * 审计日志大小 + */ + @ApiModelProperty("审计日志大小(MB)") + @NotNull(message = "审计日志大小不可为空") + @Min(value = 1024,message = "审计日志大小不能小于1024M") + @Max(value = 204800,message = "审计日志大小不能大于20G") + private BigDecimal logSize; + + /** + * 审计日志保留时间 + */ + @ApiModelProperty("审计日志存储时间(1-6个月,默认3个月)") + @Min(value = 1,message = "审计日志保留时间不能小于1") + @Max(value = 6,message = "审计日志保留时间不能大于6") + private Integer logTime; + + /** + * 系统类型 + */ + @ApiModelProperty("激活状态:0-未激活;1-激活") + @NotNull(message = "激活状态不可为空") + @Max(value = 1) + @Min(value = 0) + private Integer state; + + /** + * 更新操作实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class ConfigUpdateParam extends ConfigParam { + + /** + * id + */ + @ApiModelProperty("配置Id") + @NotBlank(message = ValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR) + private String id; + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/FunctionParam.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/FunctionParam.java new file mode 100644 index 0000000..d0b6217 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/FunctionParam.java @@ -0,0 +1,77 @@ +package com.njcn.product.system.theme.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.product.auth.pojo.constant.UserValidMessage; +import com.njcn.web.constant.ValidMessage; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2022/1/17 10:25 + */ +@Data +public class FunctionParam { + + @ApiModelProperty("节点") + @NotBlank(message = UserValidMessage.PID_NOT_BLANK) + private String pid; + + @ApiModelProperty("名称") + @NotBlank(message = UserValidMessage.USERNAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.FUNCTION_NAME, message = ValidMessage.NAME_FORMAT_ERROR) + private String name; + + @ApiModelProperty("资源标识") + @NotBlank(message = UserValidMessage.CODE_NOT_BLANK) + private String code; + + @ApiModelProperty("路径") + @NotBlank(message = UserValidMessage.PATH_NOT_BLANK) + @Pattern(regexp = PatternRegex.FUNCTION_URL, message = UserValidMessage.PATH_FORMAT_ERROR) + private String path; + + @ApiModelProperty("图标") + private String icon; + + @ApiModelProperty("排序") + @NotNull(message = UserValidMessage.SORT_NOT_BLANK) + @Range(min = 0, max = 999, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer sort; + + @ApiModelProperty("资源类型") + @NotNull(message = UserValidMessage.TYPE_NOT_BLANK) + @Range(min = 0, max = 4, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer type; + + @ApiModelProperty("描述") + private String remark; + + @ApiModelProperty("路由名称") + private String routeName; + + /** + * 资源更新操作实体 + * 需要填写的参数:资源的id + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class FunctionUpdateParam extends FunctionParam { + + @ApiModelProperty("资源Id") + @NotBlank(message = UserValidMessage.FUNCTION_ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = UserValidMessage.FUNCTION_ID_FORMAT_ERROR) + private String id; + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/HomePageParam.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/HomePageParam.java new file mode 100644 index 0000000..6e5e386 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/HomePageParam.java @@ -0,0 +1,62 @@ +package com.njcn.product.system.theme.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.product.auth.pojo.constant.UserValidMessage; +import com.njcn.web.constant.ValidMessage; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2022/1/18 16:14 + */ +@Data +public class HomePageParam { + + @ApiModelProperty("自定义页面名称") + private String name; + + @ApiModelProperty("布局模板") + @NotBlank(message = UserValidMessage.LAYOUT_NOT_BLANK) + private String layout; + + @ApiModelProperty("路径") + @NotBlank(message = UserValidMessage.PATH_NOT_BLANK) + private String path; + + @ApiModelProperty("排序") + @NotNull(message = UserValidMessage.SORT_NOT_BLANK) + @Range(min = 0, max = 999, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer sort; + + @ApiModelProperty("图标") + private String icon; + + /** + * 首页操作实体 + * 需要填写的参数:首页的id + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class HomePageUpdateParam extends HomePageParam { + + @ApiModelProperty("首页Id") + @NotBlank(message = UserValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR) + private String id; + } + + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/RoleParam.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/RoleParam.java new file mode 100644 index 0000000..562f116 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/param/RoleParam.java @@ -0,0 +1,85 @@ +package com.njcn.product.system.theme.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.web.constant.ValidMessage; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.util.List; + +/** + * @author denghuajun + * @date 2022/01/17 14:39 + * 角色 + */ +@Data +public class RoleParam { + + + + @ApiModelProperty("名称") + @NotBlank(message = ValidMessage.NAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.DEPT_NAME_REGEX, message = ValidMessage.NAME_FORMAT_ERROR) + private String name; + + @ApiModelProperty("角色代码") + @NotNull(message = ValidMessage.CODE_NOT_BLANK) + private String code; + + /** + * 角色类型 0:超级管理员;1:管理员;2:普通用户 + */ + @ApiModelProperty("角色类型") + private Integer type; + + @ApiModelProperty("角色描述") + private String remark; + + + + + + + /** + * 更新操作实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class RoleUpdateParam extends RoleParam { + + /** + * 表Id + */ + @ApiModelProperty("id") + @NotBlank(message = ValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR) + private String id; + } + + /** + * 分页查询实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class QueryParam extends BaseParam { + + /** + * 权限类型 + */ + private Integer type; + } + /** + * 角色的相关关联 + */ + @Data + public static class RoleFunctionComponent { + private String id; + private List idList; + + } +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Config.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Config.java new file mode 100644 index 0000000..517eccb --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Config.java @@ -0,0 +1,53 @@ +package com.njcn.product.system.theme.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_config") +public class Config extends BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 配置Id + */ + private String id; + + /** + * 系统类型:0-省级系统;1-企业系统;2-数据中心 + */ + private Integer type; + + /** + * 数据上报(以逗号分割,比如:冀北,网公司)默认为空 + */ + private String dataReport; + + /** + * 审计日志大小(MB) + */ + private BigDecimal logSize; + + /** + * 审计日志存储时间(1-6个月,默认3个月) + */ + private Integer logTime; + + /** + * 状态:0-删除 1-正常 + */ + private Integer state; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Function.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Function.java new file mode 100644 index 0000000..b039a44 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Function.java @@ -0,0 +1,80 @@ +package com.njcn.product.system.theme.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_function") +public class Function extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 资源表Id + */ + private String id; + + /** + * 父节点(0为根节点) + */ + private String pid; + + /** + * 所有上层节点 + */ + private String pids; + + /** + * 名称 + */ + private String name; + + /** + * 资源标识 + */ + private String code; + + /** + * 路径 + */ + private String path; + + /** + * 图标(没有图标则默认为null) + */ + private String icon; + + /** + * 排序 + */ + private Integer sort; + + /** + * 资源类型:0-菜单、1-按钮、2-公共资源、3-服务间调用资源 4-tab页 + */ + private Integer type; + + /** + * 资源描述 + */ + private String remark; + + /** + * 资源状态:0-删除 1-正常(默认为正常) + */ + private Integer state; + + /** + * 路由名称 + */ + private String routeName; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/HomePage.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/HomePage.java new file mode 100644 index 0000000..5444922 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/HomePage.java @@ -0,0 +1,60 @@ +package com.njcn.product.system.theme.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_home_page") +public class HomePage extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主页面Id + */ + private String id; + + /** + * 用户Id + */ + private String userId; + + /** + * 自定义页面名称 + */ + private String name; + + /** + * 布局魔板 + */ + private String layout; + + /** + * 路径 + */ + private String path; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态:0-删除 1-正常 + */ + private Integer state; + + /** + * 图标 + */ + private String icon; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/RoleFunction.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/RoleFunction.java new file mode 100644 index 0000000..5acd5b3 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/RoleFunction.java @@ -0,0 +1,28 @@ +package com.njcn.product.system.theme.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@TableName("sys_role_function") +public class RoleFunction { + + private static final long serialVersionUID = 1L; + + /** + * 角色Id + */ + private String roleId; + + /** + * 资源Id + */ + private String functionId; + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Theme.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Theme.java new file mode 100644 index 0000000..2565937 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/po/Theme.java @@ -0,0 +1,119 @@ +package com.njcn.product.system.theme.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_theme") +public class Theme extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 主题Id + */ + private String id; + + /** + * 主题名称 + */ + private String name; + + /** + * logo名称 + */ + private String logoUrl; + + /** + * favicon名称 + */ + private String faviconUrl; + + /** + * 主题颜色 + */ + private String color; + + /** + * 0-未激活 1-激活,所有数据只有一条数据处于激活状态 + */ + private Integer active; + + /** + * 主题描述 + */ + private String remark; + + /** + * 状态:0-删除 1-正常 + */ + private Integer state; + + //V3主题使用的字段 + /** + * 切换栏位置 + */ + private String mainAnimation; + /** + * 主键主题色 + */ + private String elementUiPrimary; + /** + * 表格标题背景颜色 + */ + private String tableHeaderBackground; + /** + * 表格标题文字颜色 + */ + private String tableHeaderColor; + /** + * 表格激活颜色 + */ + private String tableCurrent; + /** + * 侧边菜单背景色 + */ + private String menuBackground; + /** + * 侧边菜单文字颜色 + */ + private String menuColor; + /** + * 侧边菜单激活项背景色 + */ + private String menuActiveBackground; + /** + * 侧边菜单激活项文字色 + */ + private String menuActiveColor; + /** + * 侧边菜单顶栏背景色 + */ + private String menuTopBarBackground; + /** + * 顶栏文字色 + */ + private String headerBarTabColor; + /** + * 顶栏背景色 + */ + private String headerBarBackground; + + /** + * logo文件服务器路径 + */ + private String logoPath; + /** + * favicon文件服务器路径 + */ + private String faviconPath; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/vo/FunctionVO.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/vo/FunctionVO.java new file mode 100644 index 0000000..b9321f2 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/pojo/vo/FunctionVO.java @@ -0,0 +1,55 @@ +package com.njcn.product.system.theme.pojo.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2022/1/17 17:02 + */ +@Data +public class FunctionVO implements Serializable { + + @ApiModelProperty("资源Id") + private String id; + + @ApiModelProperty("节点") + private String pid; + + @ApiModelProperty("名称") + private String title; + + @ApiModelProperty("资源标识") + private String code; + + @ApiModelProperty("路由名称") + private String routeName; + + @ApiModelProperty("路径") + private String routePath; + + @ApiModelProperty("图标") + private String icon; + + @ApiModelProperty("排序") + private Integer sort; + + @ApiModelProperty("资源类型") + private Integer type; + + @ApiModelProperty("描述") + private String remark; + + @ApiModelProperty("子级") + List children; + + @ApiModelProperty("tab数据") + List userTab; + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IConfigService.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IConfigService.java new file mode 100644 index 0000000..ab52243 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IConfigService.java @@ -0,0 +1,32 @@ +package com.njcn.product.system.theme.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.system.theme.pojo.param.ConfigParam; +import com.njcn.product.system.theme.pojo.po.Config; + + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IConfigService extends IService { + + /** + * 新增系统配置 + * @param configParam 配置参数 + */ + boolean addSysConfig(ConfigParam configParam); + /** + * 修改系统配置 + * @param configUpdateParam 配置参数 + */ + boolean updateSysConfig(ConfigParam.ConfigUpdateParam configUpdateParam); + + List getList(); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IFunctionService.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IFunctionService.java new file mode 100644 index 0000000..92a4320 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IFunctionService.java @@ -0,0 +1,123 @@ +package com.njcn.product.system.theme.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.system.theme.pojo.param.FunctionParam; +import com.njcn.product.system.theme.pojo.param.RoleParam; +import com.njcn.product.system.theme.pojo.po.Function; +import com.njcn.product.system.theme.pojo.vo.FunctionVO; + + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IFunctionService extends IService { + + /** + * 刷新用户权限信息到缓存中 + */ + void refreshRolesFunctionsCache(); + + /** + * 功能描述:新增资源 + * + * @param functionParam 资源参数 + * @return boolean + * @author xy + * @date 2022/1/17 11:19 + */ + boolean addFunction(FunctionParam functionParam); + + /** + * 功能描述: 修改菜单 + * + * @param functionParam + * @return boolean + * @author xy + * @date 2022/1/17 14:23 + */ + boolean updateFunction(FunctionParam.FunctionUpdateParam functionParam); + + /** + * 功能描述:删除菜单 + * + * @param id + * @return boolean + * @author xy + * @date 2022/1/17 16:53 + */ + void deleteFunction(String id); + + /** + * 功能描述: 获取菜单树 + * + * @param + * @return java.util.List + * @author xy + * @date 2022/1/17 17:04 + */ + List getFunctionTree(); + + /** + * 功能描述: 根据id获取菜单详情 + * + * @param id + * @return com.njcn.user.pojo.po.Function + * @author xy + * @date 2022/1/17 17:39 + */ + Function getFunctionById(String id); + + /** + * 功能描述: 根据菜单id获取按钮 + * + * @param id + * @return java.util.List + * @author xy + * @date 2022/1/17 17:40 + */ + List getButtonsById(String id); + + /** + * 功能描述: 获取路由菜单 + * + * @param + * @return java.util.List + * @author xy + * @date 2022/1/18 13:47 + */ + List getRouteMenu(); + + /** + * 功能描述: 角色分配菜单 + * + * @param roleFunctionComponent + * @return java.util.List + * @author xy + * @date 2022/2/17 17:00 + */ + Boolean updateRoleComponent(RoleParam.RoleFunctionComponent roleFunctionComponent); + + /** + * 功能描述: 获取用户菜单树 + * + * @param + * @return java.util.List + * @author xy + * @date 2022/2/21 11:29 + */ + List getUserFunctionTree(); + + /** + * 根据菜单集合获取数据 + * @author xy + */ + List getFunctionByList(List list); + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IHomePageService.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IHomePageService.java new file mode 100644 index 0000000..a2a2b1b --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IHomePageService.java @@ -0,0 +1,81 @@ +package com.njcn.product.system.theme.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.system.theme.pojo.param.HomePageParam; +import com.njcn.product.system.theme.pojo.po.HomePage; + + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IHomePageService extends IService { + + /** + * 功能描述: 新增驾驶舱 + * + * @param homePageParam + * @return boolean + * @author xy + * @date 2022/1/18 16:54 + */ + boolean add(HomePageParam homePageParam); + + /** + * 功能描述: 删除驾驶舱 + * + * @param id + * @return boolean + * @author xy + * @date 2022/1/18 16:54 + */ + boolean delete(String id); + + /** + * 功能描述: 修改驾驶舱 + * + * @param homePageUpdate + * @return boolean + * @author xy + * @date 2022/1/18 16:54 + */ + boolean update(HomePageParam.HomePageUpdateParam homePageUpdate); + + + /** + * 功能描述: 获取用户的首页模式 + * + * @param id + * @return java.util.List + * @author xy + * @date 2022/1/18 15:33 + */ + List getHomePagesByUserId(String id); + + /** + * 功能描述:根据驾驶舱id获取详情 + * + * @param id + * @return com.njcn.user.pojo.po.HomePage + * @author xy + * @date 2022/1/18 17:29 + */ + HomePage getHomePageById(String id); + + /** + * 功能描述:查看已使用的驾驶舱路径 + * + * @param path + * @return java.util.List + * @author xy + * @date 2022/1/18 17:34 + */ + List getUsedHomePage(String path); + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IRoleFunctionService.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IRoleFunctionService.java new file mode 100644 index 0000000..48003b9 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IRoleFunctionService.java @@ -0,0 +1,30 @@ +package com.njcn.product.system.theme.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.system.theme.pojo.po.RoleFunction; +import com.njcn.product.system.theme.pojo.vo.FunctionVO; + + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IRoleFunctionService extends IService { + List getFunctionsByRoleIndex(String id); + + /** + * 功能描述: 根据角色集合获取菜单方法 + * + * @param roleList 角色集合 + * @return java.util.List + * @author xy + * @date 2022/1/18 14:22 + */ + List getFunctionsByList(List roleList); +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IThemeService.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IThemeService.java new file mode 100644 index 0000000..952a460 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/IThemeService.java @@ -0,0 +1,33 @@ +package com.njcn.product.system.theme.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.system.theme.pojo.po.Theme; + + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IThemeService extends IService { + + + + /** + * 功能描述: 获取当前主题 + * + * @return com.njcn.system.pojo.po.Theme + * @author xy + * @date 2022/1/12 15:39 + */ + Theme getTheme(); + + + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/ConfigServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/ConfigServiceImpl.java new file mode 100644 index 0000000..df15879 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/ConfigServiceImpl.java @@ -0,0 +1,86 @@ +package com.njcn.product.system.theme.service.impl; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; + +import com.njcn.product.system.dict.enums.SystemResponseEnum; +import com.njcn.product.system.theme.mapper.ConfigMapper; +import com.njcn.product.system.theme.pojo.param.ConfigParam; +import com.njcn.product.system.theme.pojo.po.Config; +import com.njcn.product.system.theme.service.IConfigService; +import com.njcn.web.utils.RequestUtil; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Service +public class ConfigServiceImpl extends ServiceImpl implements IConfigService { + + + @Override + public boolean addSysConfig(ConfigParam configParam) { + Config config = new Config(); + BeanUtils.copyProperties(configParam, config); + config.setCreateBy(RequestUtil.getUserIndex()); + config.setCreateTime(LocalDateTime.now()); + config.setUpdateBy(RequestUtil.getUserIndex()); + config.setUpdateTime(LocalDateTime.now()); + config.setState(DataStateEnum.ENABLE.getCode()); + this.baseMapper.insert(config); + return true; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean updateSysConfig(ConfigParam.ConfigUpdateParam configUpdateParam) { + Config config = this.baseMapper.selectById(configUpdateParam.getId()); + if (!Objects.isNull(config)) { + if (config.getState().equals(DataStateEnum.ENABLE.getCode())) { + if (Objects.equals(configUpdateParam.getState(), config.getState())) { + BeanUtils.copyProperties(configUpdateParam, config); + config.setUpdateBy(RequestUtil.getUserIndex()); + config.setUpdateTime(LocalDateTime.now()); + this.baseMapper.updateById(config); + return true; + } else { + // 不可更改当前激活状态,必须保留一个激活系统 + throw new BusinessException(SystemResponseEnum.ACTIVATED_STATE); + } + } else { + if (configUpdateParam.getState().equals(DataStateEnum.ENABLE.getCode())) { + // 先将所有的都置为非激活状态 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.set(Config::getState, DataStateEnum.DELETED.getCode()); + this.baseMapper.update(null, updateWrapper); + } + BeanUtils.copyProperties(configUpdateParam, config); + config.setUpdateBy(RequestUtil.getUserIndex()); + config.setUpdateTime(LocalDateTime.now()); + this.baseMapper.updateById(config); + return true; + } + } + return false; + } + + @Override + public List getList() { + return this.baseMapper.getList(); + } + + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/FunctionServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/FunctionServiceImpl.java new file mode 100644 index 0000000..952e033 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/FunctionServiceImpl.java @@ -0,0 +1,354 @@ +package com.njcn.product.system.theme.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.auth.pojo.constant.FunctionState; +import com.njcn.product.auth.pojo.constant.UserType; +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.product.auth.pojo.po.Role; +import com.njcn.product.auth.service.IRoleService; +import com.njcn.product.auth.service.IUserRoleService; +import com.njcn.product.system.theme.mapper.FunctionMapper; +import com.njcn.product.system.theme.mapper.RoleFunctionMapper; +import com.njcn.product.system.theme.pojo.param.FunctionParam; +import com.njcn.product.system.theme.pojo.param.RoleParam; +import com.njcn.product.system.theme.pojo.po.Function; +import com.njcn.product.system.theme.pojo.po.HomePage; +import com.njcn.product.system.theme.pojo.po.RoleFunction; +import com.njcn.product.system.theme.pojo.vo.FunctionVO; +import com.njcn.product.system.theme.service.IFunctionService; +import com.njcn.product.system.theme.service.IHomePageService; +import com.njcn.product.system.theme.service.IRoleFunctionService; +import com.njcn.redis.pojo.enums.RedisKeyEnum; +import com.njcn.redis.utils.RedisUtil; +import com.njcn.product.auth.pojo.po.UserRole; +import com.njcn.web.utils.RequestUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class FunctionServiceImpl extends ServiceImpl implements IFunctionService { + + private final RedisUtil redisUtil; + + private final IRoleService roleService; + + private final IRoleFunctionService roleFunctionService; + + private final FunctionMapper functionMapper; + + private final IUserRoleService userRoleService; + + private final IHomePageService homePageService; + + private final RoleFunctionMapper roleFunctionMapper; + + /** + * 将系统中角色--资源对应数据缓存到redis + * 先清除,再缓存 + */ + @Override + public void refreshRolesFunctionsCache() { + redisUtil.delete(RedisKeyEnum.ROLE_FUNCTION_KEY.getKey()); + redisUtil.delete(RedisKeyEnum.PUBLIC_FUNCTIONS_KEY.getKey()); + //缓存公共资源 + List publicFunctions = lambdaQuery() + .eq(Function::getType, 2) + .eq(Function::getState, DataStateEnum.ENABLE.getCode()) + .list(); + redisUtil.saveByKey( + RedisKeyEnum.PUBLIC_FUNCTIONS_KEY.getKey() + , publicFunctions.stream().map(Function::getPath).collect(Collectors.toList()) + ); + + //缓存每个角色对应的资源 + Map> roleFunctionInfo = new HashMap<>(8); + List roles = roleService.lambdaQuery() + .eq(Role::getState, DataStateEnum.ENABLE.getCode()) + .list(); + + roles.forEach(role -> { + //根据当前角色列表获取其所对应的所有资源 + List roleFunctions = roleFunctionService.lambdaQuery() + .eq(RoleFunction::getRoleId, role.getId()) + .list(); + //根据角色权限关系表获取权限的uri + if (CollectionUtil.isEmpty(roleFunctions)) { + roleFunctionInfo.put(SecurityConstants.AUTHORITY_PREFIX + role.getCode(), null); + } else { + List functions = lambdaQuery() + .in(Function::getId, roleFunctions.stream().map(RoleFunction::getFunctionId).collect(Collectors.toList())) + .eq(Function::getState, DataStateEnum.ENABLE.getCode()) + .list(); + roleFunctionInfo.put(SecurityConstants.AUTHORITY_PREFIX + role.getCode(), functions.stream().map(Function::getPath).collect(Collectors.toList())); + } + }); + redisUtil.saveByKey(RedisKeyEnum.ROLE_FUNCTION_KEY.getKey(), roleFunctionInfo); + } + + @Override + public boolean addFunction(FunctionParam functionParam) { + checkFunctionParam(functionParam, false); + Function function = new Function(); + BeanUtil.copyProperties(functionParam, function); + function.setState(FunctionState.ENABLE); + if (Objects.equals(functionParam.getPid(), FunctionState.FATHER_PID)) { + function.setPids(FunctionState.FATHER_PID); + } else { + Function fatherFaction = this.lambdaQuery().eq(Function::getId, functionParam.getPid()).one(); + if (Objects.equals(fatherFaction.getPid(), FunctionState.FATHER_PID)) { + function.setPids(functionParam.getPid()); + } else { + String pidS = fatherFaction.getPids(); + function.setPids(pidS + "," + functionParam.getPid()); + } + } + boolean result = this.save(function); + if (result) { + //刷新redis里面的资源权限 + refreshRolesFunctionsCache(); + } + return result; + } + + @Override + public boolean updateFunction(FunctionParam.FunctionUpdateParam functionParam) { + checkFunctionParam(functionParam, true); + Function function = new Function(); + BeanUtil.copyProperties(functionParam, function); + boolean result = this.updateById(function); + if (result) { + refreshRolesFunctionsCache(); + } + return result; + } + + @Override + public void deleteFunction(String id) { + boolean result; + List list = this.lambdaQuery().eq(Function::getState, FunctionState.ENABLE).eq(Function::getPid, id).list(); + if (CollectionUtils.isEmpty(list)) { + result = this.lambdaUpdate() + .set(Function::getState, FunctionState.DELETE) + .in(Function::getId, id) + .update(); + if (result) { + refreshRolesFunctionsCache(); + } + } else { + throw new BusinessException(UserResponseEnum.BINDING_BUTTON); + } + } + + @Override + public List getFunctionTree() { + List list = functionMapper.getAllFunctions(); + return list.stream() + .filter(fun -> Objects.equals(FunctionState.FATHER_PID, fun.getPid())) + .peek(funS -> funS.setChildren(getChildCategoryList(funS, list))) + .sorted(Comparator.comparingInt(FunctionVO::getSort)) + .collect(Collectors.toList()); + } + + @Override + public Function getFunctionById(String id) { + return this.lambdaQuery().eq(Function::getId, id).one(); + } + + @Override + public List getButtonsById(String id) { + List typeList = Arrays.asList(FunctionState.BUTTON, FunctionState.PUBLIC, FunctionState.TAB); + return this.lambdaQuery().eq(Function::getPid, id).in(Function::getType, typeList).eq(Function::getState, FunctionState.ENABLE).orderByAsc(Function::getSort).list(); + } + + @Override + public List getRouteMenu() { + List result = new ArrayList<>(); + List functionList; + if (Objects.equals(RequestUtil.getUsername(), UserType.SUPER_ADMIN)) { + //查询所有菜单 + functionList = this.lambdaQuery().eq(Function::getState, FunctionState.ENABLE).list().stream().map(Function::getId).distinct().collect(Collectors.toList()); + } else { + List roleList = userRoleService.getUserRoleByUserId(RequestUtil.getUserIndex()).stream().map(UserRole::getRoleId).distinct().collect(Collectors.toList()); + functionList = roleFunctionService.getFunctionsByList(roleList); + } + if (CollectionUtils.isEmpty(functionList)) { + return result; + } + List functionVOList = functionMapper.getFunctionsByList(functionList); + result = functionVOList.stream() + .filter(fun -> Objects.equals(FunctionState.FATHER_PID, fun.getPid().trim())) + .peek(funS -> funS.setChildren(getChildCategoryList(funS, functionVOList))) + .sorted(Comparator.comparingInt(FunctionVO::getSort)) + .collect(Collectors.toList()); + //组装驾驶舱 + setDriverChildren(result); + //处理tab页 + setTab(result); + return result; + } + + @Override + public Boolean updateRoleComponent(RoleParam.RoleFunctionComponent roleFunctionComponent) { + deleteComponentsByRoleIndex(roleFunctionComponent.getId()); + if (!roleFunctionComponent.getIdList().isEmpty()) { + List list = new ArrayList<>(); + RoleFunction roleFunction; + for (String pojo : roleFunctionComponent.getIdList()) { + roleFunction = new RoleFunction(); + roleFunction.setRoleId(roleFunctionComponent.getId()); + roleFunction.setFunctionId(pojo); + list.add(roleFunction); + } + roleFunctionService.saveBatch(list); + } + refreshRolesFunctionsCache(); + return true; + } + + @Override + public List getUserFunctionTree() { + List result; + List functionList; + if (Objects.equals(RequestUtil.getUsername(), UserType.SUPER_ADMIN)) { + //查询所有菜单 + functionList = this.lambdaQuery().eq(Function::getState, FunctionState.ENABLE).list().stream().map(Function::getId).distinct().collect(Collectors.toList()); + } else { + List roleList = userRoleService.getUserRoleByUserId(RequestUtil.getUserIndex()).stream().map(UserRole::getRoleId).distinct().collect(Collectors.toList()); + functionList = roleFunctionService.getFunctionsByList(roleList); + } + List functionVOList = functionMapper.getUserFunctionsByList(functionList); + result = functionVOList.stream() + .filter(fun -> Objects.equals(FunctionState.FATHER_PID, fun.getPid())) + .peek(funS -> funS.setChildren(getChildCategoryList(funS, functionVOList))) + .sorted(Comparator.comparingInt(FunctionVO::getSort)) + .collect(Collectors.toList()); + //组装驾驶舱 + setDriverChildren(result); + return result; + } + + @Override + public List getFunctionByList(List list) { + return this.lambdaQuery().in(Function::getId, list).list(); + } + + /** + * 根据角色删除资源 + * + * @param roleIndex 角色索引 + */ + public void deleteComponentsByRoleIndex(String roleIndex) { + QueryWrapper roleFunctionQueryWrapper = new QueryWrapper<>(); + roleFunctionQueryWrapper.eq("sys_role_function.role_id", roleIndex); + roleFunctionMapper.delete(roleFunctionQueryWrapper); + } + + /** + * 根据当前分类找出子类,递归找出子类的子类 + */ + private List getChildCategoryList(FunctionVO currMenu, List categories) { + return categories.stream().filter(o -> Objects.equals(o.getPid(), currMenu.getId())) + .peek(o -> o.setChildren(getChildCategoryList(o, categories))) + .sorted(Comparator.comparingInt(FunctionVO::getSort)) + .collect(Collectors.toList()); + } + + /** + * 组装驾驶舱子级 + * + * @param list 菜单集合 + */ + private void setDriverChildren(List list) { + List homePages = homePageService.getHomePagesByUserId(RequestUtil.getUserIndex()); + list.forEach(item -> { + if (Objects.equals(item.getRoutePath(), FunctionState.DRIVER_NAME)) { + homePages.forEach(po -> { + FunctionVO functionVO = new FunctionVO(); + functionVO.setId(po.getId()); + functionVO.setPid(item.getId()); + functionVO.setTitle(po.getName()); + functionVO.setCode(item.getCode()); + functionVO.setRouteName(po.getPath().substring(po.getPath().lastIndexOf("/") + 1)); + functionVO.setRoutePath(po.getPath()); + functionVO.setIcon(po.getIcon()); + functionVO.setSort(po.getSort()); + functionVO.setType(item.getType()); + functionVO.setRemark(po.getName()); + functionVO.setChildren(new ArrayList<>()); + item.getChildren().add(functionVO); + }); + } + }); + } + + /** + * 处理tab页 + */ + private void setTab(List list) { + if (!CollectionUtils.isEmpty(list)) { + list.forEach(item -> { + List children = item.getChildren(); + if (!CollectionUtils.isEmpty(children)) { + for (FunctionVO child : children) { + List children2 = child.getChildren(); + if (!CollectionUtils.isEmpty(children2)) { + setTab(children2); + } else if (Objects.equals(child.getType(), 4)) { + item.setUserTab(item.getChildren()); + item.setChildren(new ArrayList<>()); + break; + } + } + } + }); + } + } + + /** + * 校验参数, + * 1.检查是否存在相同名称的菜单 + * 名称 && 路径做唯一判断 + */ + private void checkFunctionParam(FunctionParam functionParam, boolean isExcludeSelf) { + LambdaQueryWrapper functionLambdaQueryWrapper = new LambdaQueryWrapper<>(); + functionLambdaQueryWrapper + .eq(Function::getName, functionParam.getName()) + .eq(Function::getPath, functionParam.getPath()) + .eq(Function::getPid, functionParam.getPid()) + .eq(Function::getState, FunctionState.ENABLE); + //更新的时候,需排除当前记录 + if (isExcludeSelf) { + if (functionParam instanceof FunctionParam.FunctionUpdateParam) { + functionLambdaQueryWrapper.ne(Function::getId, ((FunctionParam.FunctionUpdateParam) functionParam).getId()); + } + } + int countByAccount = this.count(functionLambdaQueryWrapper); + //大于等于1个则表示重复 + if (countByAccount >= 1) { + throw new BusinessException(UserResponseEnum.FUNCTION_PATH_EXIST); + } + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/HomePageServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/HomePageServiceImpl.java new file mode 100644 index 0000000..1851376 --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/HomePageServiceImpl.java @@ -0,0 +1,99 @@ +package com.njcn.product.system.theme.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.exception.BusinessException; + +import com.njcn.product.auth.pojo.constant.HomePageState; +import com.njcn.product.auth.pojo.enums.UserResponseEnum; +import com.njcn.product.system.theme.mapper.HomePageMapper; +import com.njcn.product.system.theme.pojo.param.HomePageParam; +import com.njcn.product.system.theme.pojo.po.HomePage; +import com.njcn.product.system.theme.service.IHomePageService; +import com.njcn.web.utils.RequestUtil; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Service +public class HomePageServiceImpl extends ServiceImpl implements IHomePageService { + + @Override + public boolean add(HomePageParam homePageParam) { + checkHomePageName(homePageParam,false); + String component = homePageParam.getLayout().replace(""","\""); + HomePage homePage = new HomePage(); + BeanUtil.copyProperties(homePageParam, homePage); + homePage.setUserId(RequestUtil.getUserIndex()); + homePage.setState(HomePageState.ENABLE); + homePage.setLayout(component); + return this.save(homePage); + } + + @Override + public boolean delete(String id) { + return this.lambdaUpdate() + .set(HomePage::getState, HomePageState.DELETE) + .in(HomePage::getId, id) + .update(); + } + + @Override + public boolean update(HomePageParam.HomePageUpdateParam homePageUpdate) { + checkHomePageName(homePageUpdate,true); + HomePage homePage = new HomePage(); + BeanUtil.copyProperties(homePageUpdate, homePage); + return this.updateById(homePage); + } + + @Override + public List getHomePagesByUserId(String id) { + List userList = new ArrayList<>(); + userList.add(id); + userList.add(HomePageState.DEFAULT_USER_ID); + return this.lambdaQuery().in(HomePage::getUserId,userList).eq(HomePage::getState, HomePageState.ENABLE).orderByAsc(HomePage::getSort).list(); + } + + @Override + public HomePage getHomePageById(String id) { + return this.lambdaQuery().in(HomePage::getId,id).one(); + } + + @Override + public List getUsedHomePage(String path) { + return this.lambdaQuery().in(HomePage::getUserId,RequestUtil.getUserIndex()).eq(HomePage::getState,HomePageState.ENABLE).likeRight(HomePage::getPath,path).list().stream().map(HomePage::getPath).distinct().collect(Collectors.toList()); + } + + /** + * 校验参数,检查是否存在相同名称的首页 + */ + private void checkHomePageName(HomePageParam homePageParam, boolean isExcludeSelf) { + LambdaQueryWrapper homePageLambdaQueryWrapper = new LambdaQueryWrapper<>(); + homePageLambdaQueryWrapper + .eq(HomePage::getName,homePageParam.getName()) + .eq(HomePage::getState, HomePageState.ENABLE); + //更新的时候,需排除当前记录 + if(isExcludeSelf){ + if(homePageParam instanceof HomePageParam.HomePageUpdateParam){ + homePageLambdaQueryWrapper.ne(HomePage::getId,((HomePageParam.HomePageUpdateParam) homePageParam).getId()); + } + } + int countByAccount = this.count(homePageLambdaQueryWrapper); + //大于等于1个则表示重复 + if (countByAccount >= 1) { + throw new BusinessException(UserResponseEnum.REGISTER_HOMEPAGE_NAME_EXIST); + } + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/RoleFunctionServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/RoleFunctionServiceImpl.java new file mode 100644 index 0000000..92cc47c --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/RoleFunctionServiceImpl.java @@ -0,0 +1,51 @@ +package com.njcn.product.system.theme.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.system.theme.mapper.FunctionMapper; +import com.njcn.product.system.theme.mapper.RoleFunctionMapper; +import com.njcn.product.system.theme.pojo.po.RoleFunction; +import com.njcn.product.system.theme.pojo.vo.FunctionVO; +import com.njcn.product.system.theme.service.IRoleFunctionService; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Service +@AllArgsConstructor +public class RoleFunctionServiceImpl extends ServiceImpl implements IRoleFunctionService { + + private final FunctionMapper functionMapper; + + @Override + public List getFunctionsByRoleIndex(String id) { + List result = new ArrayList<>(); + List functionList = new ArrayList<>(); + QueryWrapper componentQueryWrapper = new QueryWrapper<>(); + componentQueryWrapper.eq("sys_role_function.role_id",id); + functionList = this.baseMapper.selectList(componentQueryWrapper).stream().map(RoleFunction::getFunctionId).collect(Collectors.toList()); + if (CollectionUtil.isNotEmpty(functionList)){ + result = functionMapper.getByList(functionList); + } + return result; + } + + @Override + public List getFunctionsByList(List roleList) { + return this.lambdaQuery().in(RoleFunction::getRoleId,roleList).list().stream().map(RoleFunction::getFunctionId).distinct().collect(Collectors.toList()); + } + +} diff --git a/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/ThemeServiceImpl.java b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/ThemeServiceImpl.java new file mode 100644 index 0000000..0d342da --- /dev/null +++ b/carry_capacity/src/main/java/com/njcn/product/system/theme/service/impl/ThemeServiceImpl.java @@ -0,0 +1,39 @@ +package com.njcn.product.system.theme.service.impl; + + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.system.theme.mapper.ThemeMapper; +import com.njcn.product.system.theme.pojo.po.Theme; +import com.njcn.product.system.theme.service.IThemeService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import sun.misc.BASE64Encoder; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Service +@RequiredArgsConstructor +public class ThemeServiceImpl extends ServiceImpl implements IThemeService { + + + @Override + public Theme getTheme() { + return this.lambdaQuery() + .eq(Theme::getActive,1).one(); + } + + + +} diff --git a/carry_capacity/src/main/resources/application.yml b/carry_capacity/src/main/resources/application.yml new file mode 100644 index 0000000..a3f3485 --- /dev/null +++ b/carry_capacity/src/main/resources/application.yml @@ -0,0 +1,96 @@ +#当前服务的基本信息 +microservice: + ename: carryCapacity + name: carryCapacity +#当前服务的基本信息 +server: + port: 9001 +spring: + application: + name: carry_capacity + datasource: + druid: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://192.168.1.24:13306/pqsinfo_cznlpg?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai + username: root + password: njcnpqs + # url: jdbc:mysql://localhost:3306/pqs91001?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=CTT + # username: root + # password: root + #初始化建立物理连接的个数、最小、最大连接数 + initial-size: 5 + min-idle: 5 + max-active: 50 + #获取连接最大等待时间,单位毫秒 + max-wait: 60000 + #链接保持空间而不被驱逐的最长时间,单位毫秒 + min-evictable-idle-time-millis: 300000 + validation-query: select 1 + test-while-idle: true + test-on-borrow: false + test-on-return: false + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + #influxDB内容配置 + influx: + url: http://192.168.1.24:8086 + user: admin + password: 123456 + database: pqbase_pg + mapper-location: com.njcn.**.imapper +#mybatis配置信息 +mybatis-plus: + mapper-locations: classpath*:com/njcn/**/mapping/*.xml + #别名扫描 + type-aliases-package: com.njcn.product.**.pojo + configuration: + #驼峰命名 + map-underscore-to-camel-case: true + #配置sql日志输出 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl +# #关闭日志输出 +# log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl + global-config: + db-config: + #指定主键生成策略 + id-type: assign_uuid +db: + type: mysql +#文件位置配置 +business: + #处理波形数据位置 + # wavePath: D://comtrade + wavePath: /usr/local/comtrade + #处理临时数据 + #tempPath: D://file + tempPath: /usr/local/file + #文件存储的方式 + file: + storage: 3 +#oss服务器配置 +min: + io: + endpoint: http://192.168.1.13:9009 + accessKey: minio + secretKey: minio@123 + bucket: excelreport + #华为obs服务器配置 +huawei: + access-key: J9GS9EA79PZ60OK23LWP + security-key: BirGrAFDSLxU8ow5fffyXgZRAmMRb1R1AdqCI60d + obs: + bucket: test-8601 + endpoint: https://obs.cn-east-3.myhuaweicloud.com + # 单位为秒 + expire: 3600 + +#线程池配置信息 +threadPool: + corePoolSize: 10 + maxPoolSize: 20 + queueCapacity: 500 + keepAliveSeconds: 60 +file: + upload-dir: D:/carry + + diff --git a/carry_capacity/src/main/resources/njcn.jks b/carry_capacity/src/main/resources/njcn.jks new file mode 100644 index 0000000000000000000000000000000000000000..0b25e1dab9b67252efec20faf5ba9fa866dcca6b GIT binary patch literal 2242 zcmchYXHe6L7RCSn5QqUnFH$1atP)BH0i*~7q=^tg5tJqZOOF&4NT?x#lqXe;6bmB7 zfUr~%71Us;f`9>Rv_-%tEmR3*nVoq%JLC5^cg~0N`*i2bopX2Rcjo~Bfb0wWTTo(v z7jfUjyflNo1^{jdh63Hi2=MTtc|a(r0ucs5ZU9Pwem~p6{8;1#i`PBfu%axAM!pi+ z`w)V9e63p!SeVNWdQJ zxIx(IbUd=neEKmAS^2)gTbM64q}FiLoKaG*u@Q$ZR?wZX9DY-UYaCq}^P}kco5jVh zcas&>p<9tXH5vG4)~FlCW1vcxh|s z7g~4v-9?G9Ww;y0cE+R?mBXx$e=3uHh^1KZF8KCOF|!uqC>_fb#Y5x^vSk(E^v$nr z_uMCI{G@_PSfs|K+$<@W;8;j-UfuN{&&-gK)|aE(KVOOISrzY;+N-8e!d$fuXvrN? z;t%00`5fg zI5=IFeB-tE#T0kls27FB6PXf5ywlh5C++7Qhh%bV#`IUHf(3r&H~?uJ(vHM?9-@ie3aT||D9x_@%fs)YPd{;U1(j! z<-r&SeGa|U@0zOo1VjFai`%yHpJayKhsLxqo~>i3xaT8Pdgncub>#Xu|hJRPkf z)ru}aB5T610`X6Wa5bi$364BsV+mqCvKle(g~zNHC-V5>?yP^>v0cqrwlGkZ7@=aW z({Z9i!Pv-&@z2bae!cQ9apjBfZCfYENvpng1Z>j}w<=n&+aIgEmkE&Bn{v3)WO}z5jG{|O%lOBr2CEZUH z)*;W1E_3X=xQrE5-{A^(Bb|BHw~E3n+T4RqVo7Le>5L2cr6@nV;Rg{Z!j-31qE2+N`CEC@l2J z_EAm2_}e}8+v3(L(3fkqRm00SV)#bf$;P`ASxiH-Y`_3^A+<=-p;egoLVw?OnX0c< zt?p50A0hd11tR}K%bN67)f)azsZ@8NOz%K!#8tDYlyZ+8yj}S7Y{e?>OKsVb546uf zABdznM>G#ocNW(eC2gO+ZjtlmG9Eq{p_&B=EL@EVoURmWbQu&^(U|+Y7rC9)wJ~}C zz(vMTxFRqVh{t^{2nc~dO^y$+FbE#FN|II51_A{6xB*O%rqF(6aydeIL}8A^K;q>H z;{V8jUkL0M!ut#1`-O1*LokOV5m-%4tfsc67IuFw>{~2G>mU1n-i87y{yUESZ^1=+*< z&aqEqE>?pdFT_1`LTio?>vfD%9ZA}GIVrlz{cN`64caF|I8>5C;P%hZJ4#|x6|TZH%n+fMHu+eqV0}^#5Bpob(xz=F zx-e(h*jTlFBgH*^o50H6)LRV8m#6>MSX9vpmrTPR6?Ij5gK(`S(8aZV@X>j3(50+( zi1rwS3k1N23K%&Ia{oe*0#FI4m_~xAUvxn{VwbI-g{|+GjSc<||96P{H?@pvl>#nMC|l9^SgHe4OUo<4Crs}(OL9$7X{H1_L{odih8?hden?o80kQI&T@jn*>2RN zL3_lVr5Fxea@0KYI*y=oZYWvu@RkAW#lifUY2DkoB%@lJSk0Cvndd6;OFGZP&q{5# RJX?|GGgF&k_4F@m{{_HG+F1Yq literal 0 HcmV?d00001 diff --git a/carry_capacity/target/carry_capacity-1.0.0.jar b/carry_capacity/target/carry_capacity-1.0.0.jar new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/carry_capacity-1.0.0.jar.original b/carry_capacity/target/carry_capacity-1.0.0.jar.original new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/application.yml b/carry_capacity/target/classes/application.yml new file mode 100644 index 0000000..a3f3485 --- /dev/null +++ b/carry_capacity/target/classes/application.yml @@ -0,0 +1,96 @@ +#当前服务的基本信息 +microservice: + ename: carryCapacity + name: carryCapacity +#当前服务的基本信息 +server: + port: 9001 +spring: + application: + name: carry_capacity + datasource: + druid: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://192.168.1.24:13306/pqsinfo_cznlpg?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai + username: root + password: njcnpqs + # url: jdbc:mysql://localhost:3306/pqs91001?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=CTT + # username: root + # password: root + #初始化建立物理连接的个数、最小、最大连接数 + initial-size: 5 + min-idle: 5 + max-active: 50 + #获取连接最大等待时间,单位毫秒 + max-wait: 60000 + #链接保持空间而不被驱逐的最长时间,单位毫秒 + min-evictable-idle-time-millis: 300000 + validation-query: select 1 + test-while-idle: true + test-on-borrow: false + test-on-return: false + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + #influxDB内容配置 + influx: + url: http://192.168.1.24:8086 + user: admin + password: 123456 + database: pqbase_pg + mapper-location: com.njcn.**.imapper +#mybatis配置信息 +mybatis-plus: + mapper-locations: classpath*:com/njcn/**/mapping/*.xml + #别名扫描 + type-aliases-package: com.njcn.product.**.pojo + configuration: + #驼峰命名 + map-underscore-to-camel-case: true + #配置sql日志输出 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl +# #关闭日志输出 +# log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl + global-config: + db-config: + #指定主键生成策略 + id-type: assign_uuid +db: + type: mysql +#文件位置配置 +business: + #处理波形数据位置 + # wavePath: D://comtrade + wavePath: /usr/local/comtrade + #处理临时数据 + #tempPath: D://file + tempPath: /usr/local/file + #文件存储的方式 + file: + storage: 3 +#oss服务器配置 +min: + io: + endpoint: http://192.168.1.13:9009 + accessKey: minio + secretKey: minio@123 + bucket: excelreport + #华为obs服务器配置 +huawei: + access-key: J9GS9EA79PZ60OK23LWP + security-key: BirGrAFDSLxU8ow5fffyXgZRAmMRb1R1AdqCI60d + obs: + bucket: test-8601 + endpoint: https://obs.cn-east-3.myhuaweicloud.com + # 单位为秒 + expire: 3600 + +#线程池配置信息 +threadPool: + corePoolSize: 10 + maxPoolSize: 20 + queueCapacity: 500 + keepAliveSeconds: 60 +file: + upload-dir: D:/carry + + diff --git a/carry_capacity/target/classes/com/njcn/product/auth/mapper/mapping/UserRoleMapper.xml b/carry_capacity/target/classes/com/njcn/product/auth/mapper/mapping/UserRoleMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDataPOMapper.xml b/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDataPOMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDevicePOMapper.xml b/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityDevicePOMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityResultPOMapper.xml b/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityResultPOMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyDhlPOMapper.xml b/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyDhlPOMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyPOMapper.xml b/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityStrategyPOMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityUserPOMapper.xml b/carry_capacity/target/classes/com/njcn/product/carrycapacity/mapper/mapping/CarryCapacityUserPOMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/DeptLineMapper.xml b/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/DeptLineMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/DeviceMapper.xml b/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/DeviceMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/LineDetailMapper.xml b/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/LineDetailMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/LineMapper.xml b/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/LineMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/OverlimitMapper.xml b/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/OverlimitMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/TreeMapper.xml b/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/TreeMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/VoltageMapper.xml b/carry_capacity/target/classes/com/njcn/product/device/ledger/mapper/mapping/VoltageMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/system/dept/mapper/mapping/AreaMapper.xml b/carry_capacity/target/classes/com/njcn/product/system/dept/mapper/mapping/AreaMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/system/dept/mapper/mapping/DeptMapper.xml b/carry_capacity/target/classes/com/njcn/product/system/dept/mapper/mapping/DeptMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/system/dict/mapper/mapping/DictDataMapper.xml b/carry_capacity/target/classes/com/njcn/product/system/dict/mapper/mapping/DictDataMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/system/dict/mapper/mapping/DictTypeMapper.xml b/carry_capacity/target/classes/com/njcn/product/system/dict/mapper/mapping/DictTypeMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/system/theme/mapper/mapping/ConfigMapper.xml b/carry_capacity/target/classes/com/njcn/product/system/theme/mapper/mapping/ConfigMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/system/theme/mapper/mapping/FunctionMapper.xml b/carry_capacity/target/classes/com/njcn/product/system/theme/mapper/mapping/FunctionMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/com/njcn/product/system/theme/mapper/mapping/RoleFunctionMapper.xml b/carry_capacity/target/classes/com/njcn/product/system/theme/mapper/mapping/RoleFunctionMapper.xml new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/classes/njcn.jks b/carry_capacity/target/classes/njcn.jks new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/maven-archiver/pom.properties b/carry_capacity/target/maven-archiver/pom.properties new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/carry_capacity/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/carry_capacity/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/carry_capacity/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/cn-advance/.gitignore b/cn-advance/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/cn-advance/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/cn-advance/pom.xml b/cn-advance/pom.xml new file mode 100644 index 0000000..3e9295c --- /dev/null +++ b/cn-advance/pom.xml @@ -0,0 +1,148 @@ + + + 4.0.0 + + com.njcn.product + CN_Product + 1.0.0 + + + cn-advance + 1.0.0 + cn-advance + cn-advance + + + + com.njcn + njcn-common + 0.0.1 + + + + com.njcn + mybatis-plus + 0.0.1 + + + + com.njcn + spingboot2.3.12 + 2.3.12 + + + + com.njcn.product + cn-terminal + 1.0.0 + + + + com.njcn.product + cn-system + 1.0.0 + + + + com.njcn + pqs-influx + ${project.version} + + + + com.alibaba + fastjson + 2.0.22 + + + + net.java.dev.jna + jna + 5.5.0 + + + + + + org.apache.commons + commons-math3 + 3.6.1 + + + + + org.ejml + ejml-simple + 0.41 + + + + cglib + cglib + 3.3.0 + + + + + cn.afterturn + easypoi-spring-boot-starter + 4.4.0 + + + + cn.afterturn + easypoi-base + 4.4.0 + + + cn.afterturn + easypoi-web + 4.4.0 + + + + xerces + xercesImpl + 2.12.2 + + + + + + + + src/main/resources + true + + *.yml + + + + src/main/resources + false + + *.dll + *.xlsx + + + + src/main/resources + false + + *.so + + + + src/main/java + false + + **/*.xml + + + + + + + + diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/controller/EventRelevantAnalysisController.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/controller/EventRelevantAnalysisController.java new file mode 100644 index 0000000..025015a --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/controller/EventRelevantAnalysisController.java @@ -0,0 +1,80 @@ +package com.njcn.product.advance.eventSource.controller; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.util.StrUtil; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.advance.eventSource.service.EventRelevantAnalysisService; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.LargeScreenCountParam; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * pqs + * 事件关联分析 + * + * @author cdf + * @date 2023/6/30 + */ +@Slf4j +@RestController +@RequestMapping("process") +@Api(tags = "暂降事件关联分析") +@RequiredArgsConstructor +public class EventRelevantAnalysisController extends BaseController { + + private final EventRelevantAnalysisService eventRelevantAnalysisService; + + @PostMapping("processEvents") + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("启动关联分析") + public HttpResult processEvents(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("processEvents"); + List timeVal = checkLocalDate(param.getSearchBeginTime(),param.getSearchEndTime()); + eventRelevantAnalysisService.processEvents(timeVal.get(0),timeVal.get(1),param.getDeptId()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + + + /** + * 校验字符串起始时间和结束时间并返回时间格式时间 + * @author cdf + * @date 2023/8/10 + */ + public List checkLocalDate(String startTime,String endTime) { + List resultList = new ArrayList<>(); + if(StrUtil.isBlank(startTime) || StrUtil.isBlank(endTime)){ + throw new BusinessException(CommonResponseEnum.TIME_ERROR); + } + try { + startTime = startTime+StrUtil.SPACE+"00:00:00"; + endTime = endTime+StrUtil.SPACE+"23:59:59"; + LocalDateTime start = LocalDateTime.parse(startTime, DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)); + LocalDateTime end = LocalDateTime.parse(endTime,DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)); + resultList.add(start); + resultList.add(end); + } catch (Exception e) { + throw new BusinessException(CommonResponseEnum.TIME_ERROR); + } + return resultList; + } + + + + + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RelevantLogMapper.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RelevantLogMapper.java new file mode 100644 index 0000000..ea97e37 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RelevantLogMapper.java @@ -0,0 +1,24 @@ +package com.njcn.product.advance.eventSource.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.advance.eventSource.pojo.po.PqsRelevanceLog; + + +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2023/6/19 + */ +public interface RelevantLogMapper extends BaseMapper { + + + + + + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RmpEventAdvanceMapper.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RmpEventAdvanceMapper.java new file mode 100644 index 0000000..57da042 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RmpEventAdvanceMapper.java @@ -0,0 +1,26 @@ +package com.njcn.product.advance.eventSource.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.advance.eventSource.pojo.dto.eventAggregate.EntityLogic; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.RmpEventDetailPO; + +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2023/6/19 + */ +public interface RmpEventAdvanceMapper extends BaseMapper { + + + /** + * 获取母线物理隔绝信息 + * @author cdf + * @date 2023/7/21 + */ + List getLogic(); + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RmpEventDetailAssMapper.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RmpEventDetailAssMapper.java new file mode 100644 index 0000000..a99b88f --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/RmpEventDetailAssMapper.java @@ -0,0 +1,21 @@ +package com.njcn.product.advance.eventSource.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.advance.eventSource.pojo.dto.eventAggregate.EventAssObj; +import com.njcn.product.advance.eventSource.pojo.po.RmpEventDetailAssPO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2023/8/9 + */ +public interface RmpEventDetailAssMapper extends BaseMapper { + + + int insertEventAssData(@Param("list") List list); + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/mapping/RelevanceMapper.xml b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/mapping/RelevanceMapper.xml new file mode 100644 index 0000000..d9b8bab --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/mapping/RelevanceMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/mapping/RmpEventDetailAssMapper.xml b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/mapping/RmpEventDetailAssMapper.xml new file mode 100644 index 0000000..c03ce45 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/mapper/mapping/RmpEventDetailAssMapper.xml @@ -0,0 +1,24 @@ + + + + + + + insert into r_mp_event_detail_ass values + + ( + #{eventAssData.indexEventAss},#{eventAssData.time},#{eventAssData.describe}, + #{eventAssData.bRange},#{eventAssData.indexUser},#{eventAssData.indexUser},#{eventAssData.updateTime},#{eventAssData.updateTime} + ) + + + + + + + + + + + diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/constant/HarmonicValidMessage.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/constant/HarmonicValidMessage.java new file mode 100644 index 0000000..42864f5 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/constant/HarmonicValidMessage.java @@ -0,0 +1,10 @@ +package com.njcn.product.advance.eventSource.pojo.constant; + +/** + * @author xy + * @date 2021/12/29 15:10 + */ +public interface HarmonicValidMessage { + + String DATA_NOT_BLANK = "参数不能为空"; +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityGroupData.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityGroupData.java new file mode 100644 index 0000000..44c23a4 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityGroupData.java @@ -0,0 +1,43 @@ +package com.njcn.product.advance.eventSource.pojo.dto.eventAggregate; + +import lombok.Data; + +/** + * pqs + * + * @author cdf + * @date 2023/7/20 + */ +@Data +public class EntityGroupData { + private int idx[]; + private int all_evt_num; + private int evt_in_num; + private int evt_out_num; + private int evt_res_num; + + private int Matrixcata[][]; + + private EntityGroupEvtData in_buf[]; + private EntityGroupEvtData out_buf[]; + private EntityGroupEvtData res_buf[]; + private EntityGroupEvtData grp_buf[][]; + + private int grp_num[]; + private int grp_all_num; + private EntityGroupEvtData grp_cata_buf[][][]; + private int grp_cata_num[][]; + + public EntityGroupData() { + idx = new int[FinalData.MAX_EVT_NUM]; + Matrixcata = new int[FinalData.MAX_CATA_NUM][FinalData.MAX_EVT_NUM]; + in_buf = new EntityGroupEvtData[FinalData.MAX_EVT_NUM]; + out_buf = new EntityGroupEvtData[FinalData.MAX_EVT_NUM]; + res_buf = new EntityGroupEvtData[FinalData.MAX_EVT_NUM]; + grp_buf = new EntityGroupEvtData[FinalData.MAX_GROUP_NUM][FinalData.MAX_EVT_NUM]; + grp_num = new int[FinalData.MAX_GROUP_NUM]; + grp_cata_buf = new EntityGroupEvtData[FinalData.MAX_GROUP_NUM][FinalData.MAX_CATA_NUM + + 2][FinalData.MAX_EVT_NUM]; + grp_cata_num = new int[FinalData.MAX_GROUP_NUM][FinalData.MAX_CATA_NUM + 2]; + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityGroupEvtData.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityGroupEvtData.java new file mode 100644 index 0000000..ea0ca88 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityGroupEvtData.java @@ -0,0 +1,120 @@ +package com.njcn.product.advance.eventSource.pojo.dto.eventAggregate; + + + +public class EntityGroupEvtData implements Cloneable,Comparable { + //逻辑节点序号 + private int node; + //事件开始时间时标 + private int start_time; + //类别 + private int cata; + //标注类别 + private int cata2; + //物理节点 + private String nodePhysics; + + private SagEvent sagEvent; + + private String sagReason; + + public EntityGroupEvtData(String nodePhysics, int start_time, int cata, int cata2,SagEvent sagEvent,String sagReason) { + this.nodePhysics = nodePhysics; + this.start_time = start_time; + this.cata = cata; + this.cata2 = cata2; + this.sagEvent = sagEvent; + this.sagReason = sagReason; + } + + public SagEvent getSagEvent() { + return sagEvent; + } + + public void setSagEvent(SagEvent sagEvent) { + this.sagEvent = sagEvent; + } + + public String getNodePhysics() { + return nodePhysics; + } + + public void setNodePhysics(String nodePhysics) { + this.nodePhysics = nodePhysics; + } + + public int getNode() { + return node; + } + + public void setNode(int node) { + this.node = node; + } + + public int getStart_time() { + return start_time; + } + + public void setStart_time(int start_time) { + this.start_time = start_time; + } + + public int getCata() { + return cata; + } + + public void setCata(int cata) { + this.cata = cata; + } + + public int getCata2() { + return cata2; + } + + public void setCata2(int cata2) { + this.cata2 = cata2; + } + + public String getSagReason() { + return sagReason; + } + + public void setSagReason(String sagReason) { + this.sagReason = sagReason; + } + + + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + public Object objClone() { + try { + return clone(); + } catch (CloneNotSupportedException e) { + return new EntityGroupEvtData("-1", -1, -1, -1,null,null); + } + } + + @Override + public String toString() { + return "EntityGroupEvtData{" + + "node=" + node + + ", start_time=" + start_time + + ", cata=" + cata + + ", cata2=" + cata2 + + '}'; + } + + @Override + public int compareTo(EntityGroupEvtData obj) { + if(this.getStart_time() < obj.getStart_time()){ + return -1; + }else if(this.getStart_time() > obj.getStart_time()){ + return 1; + } + + return 0; + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityLogic.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityLogic.java new file mode 100644 index 0000000..802c181 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityLogic.java @@ -0,0 +1,21 @@ +package com.njcn.product.advance.eventSource.pojo.dto.eventAggregate; + + +import lombok.Data; + +@Data +public class EntityLogic { + //物理隔绝变压器策略GUID + private String tPIndex; + //变压器逻辑上节点 + private Integer node_h; + //变压器逻辑下节点 + private Integer node_l; + // 变压器连接方式 + private Integer type; + //变压器物理上节点 + private String nodeBefore; + //变压器物理下节点 + private String nodeNext; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityMtrans.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityMtrans.java new file mode 100644 index 0000000..0467fab --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EntityMtrans.java @@ -0,0 +1,70 @@ +package com.njcn.product.advance.eventSource.pojo.dto.eventAggregate; + +import java.io.Serializable; +import java.util.Arrays; + +public class EntityMtrans implements Serializable { + private static final long serialVersionUID = 1L; + private int Matrixcata0[][]; + private int Matrixcata1[][]; + private int Mtrans[][]; + private int possiable_path[][]; + private int path_num; + + public EntityMtrans() { + super(); + Mtrans = new int[FinalData.NODE_NUM][FinalData.NODE_NUM]; + Matrixcata0 = new int[FinalData.EVT_TYPE_NUM][FinalData.NODE_NUM]; + Matrixcata1 = new int[FinalData.EVT_TYPE_NUM][FinalData.NODE_NUM]; + possiable_path = new int[FinalData.MAX_PATH_NUM][FinalData.NODE_NUM + 1]; + path_num = 0; + } + + public int[][] getMatrixcata0() { + return Matrixcata0; + } + + public void setMatrixcata0(int[][] matrixcata0) { + Matrixcata0 = matrixcata0; + } + + public int[][] getMatrixcata1() { + return Matrixcata1; + } + + public void setMatrixcata1(int[][] matrixcata1) { + Matrixcata1 = matrixcata1; + } + + public int[][] getMtrans() { + return Mtrans; + } + + public void setMtrans(int[][] mtrans) { + Mtrans = mtrans; + } + + public int[][] getPossiable_path() { + return possiable_path; + } + + public void setPossiable_path(int[][] possiable_path) { + this.possiable_path = possiable_path; + } + + public int getPath_num() { + return path_num; + } + + public void setPath_num(int path_num) { + this.path_num = path_num; + } + + @Override + public String toString() { + return "EntityMtrans [Matrixcata0=" + Arrays.toString(Matrixcata0) + ", Matrixcata1=" + + Arrays.toString(Matrixcata1) + ", Mtrans=" + Arrays.toString(Mtrans) + ", possiable_path=" + + Arrays.toString(possiable_path) + ", path_num=" + path_num + "]"; + } +} + diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EventAssObj.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EventAssObj.java new file mode 100644 index 0000000..e739a78 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/EventAssObj.java @@ -0,0 +1,122 @@ +package com.njcn.product.advance.eventSource.pojo.dto.eventAggregate; + + +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +/* + *一个归一化事件包含多个事件(一对多) + *indexEventAss:事件关联分析表Guid + *time:归一化中第一个时间 + *describe:关联事件描述 + *bRange:是否进行范围分析 + *indexUser:用户表Guid + *updateTime:更新时间 + *state:数据状态 + *name:关联事件名称 + *list:属于该归一化事件的暂降事件 + *strTime:字符串时间 +*/ +@Data +public class EventAssObj implements Serializable { + private String indexEventAss; + private LocalDateTime time; + private String describe; + private int bRange; + private String indexUser; + private LocalDateTime updateTime = LocalDateTime.now(); + private int state; + private String name; + private String strTime; + private List list; + + public String getStrTime() { + return strTime; + } + + public void setStrTime(String strTime) { + this.strTime = strTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getIndexEventAss() { + return indexEventAss; + } + + public void setIndexEventAss(String indexEventAss) { + this.indexEventAss = indexEventAss; + } + + public LocalDateTime getTime() { + return time; + } + + public void setTime(LocalDateTime time) { + this.time = time; + } + + public String getDescribe() { + return describe; + } + + public void setDescribe(String describe) { + this.describe = describe; + } + + public int getbRange() { + return bRange; + } + + public void setbRange(int bRange) { + this.bRange = bRange; + } + + public String getIndexUser() { + return indexUser; + } + + public void setIndexUser(String indexUser) { + this.indexUser = indexUser; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + @Override + public String toString() { + return "EventAssObj [indexEventAss=" + indexEventAss + ", time=" + time + ", describe=" + describe + ", bRange=" + + bRange + ", indexUser=" + indexUser + ", updateTime=" + updateTime + ", state=" + state + ", name=" + + name + ", strTime=" + strTime + ", list=" + list + "]"; + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/FinalData.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/FinalData.java new file mode 100644 index 0000000..317f9e2 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/FinalData.java @@ -0,0 +1,25 @@ +package com.njcn.product.advance.eventSource.pojo.dto.eventAggregate; + +public class FinalData { + public static final int TIME_THRESHOLD = 10;//暂降事件按开始时间归集门槛10秒 + public static final int MAX_GROUP_NUM = 1000;//分组的最大组数 + public static final int MAX_CATA_NUM = 7;//类别数 + public static final int MAX_EVT_NUM = 1000;//最大事件个数 + + public static final int QVVR_TYPE_THREE = 9; //三相故障 + public static final int QVVR_TYPE_UNKNOWN = 10; //故障类型未知 + public static final int QVVR_TYPE_OUTOFRANGE = -1; //节点不在网络拓扑中 + public static final int DATA_INF = -1; + public static final int EVT_TYPE_NUM = 6;//故障类型数 + public static final int MAX_PATH_NUM = 50;//最大路径数 + public static int NODE_NUM;//输入节点数 + + // 暂降综合评估算法 + public static final int CLUSER_NUM = 4; // 系统中各监测点分类后的代表节点 + public static final int MAX_LINE_NUM = 1000; // 监测点最多个数 + public static final int MAX_STA_NUM = 120; // 支持的子系统个数 + + static { + NODE_NUM = -1; + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/PlantInfo.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/PlantInfo.java new file mode 100644 index 0000000..ea24ed9 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/PlantInfo.java @@ -0,0 +1,32 @@ +package com.njcn.product.advance.eventSource.pojo.dto.eventAggregate; + +import lombok.Data; + +import java.io.Serializable; + +/** + * pqs + *终端监测点名称信息 + * nameGD:供电公司名称 + * nameBD:变电站名称 + * nameSubV:母线名称 + * namePoint:监测点名称 + * indexPoint:监测点的唯一标识 + * + * 新增add + * xuyang + * 2021.05.11 + * 监测点电压等级:monitorVoltageLevel + * 监测点干扰源类型终:monitorLoadType + */ +@Data +public class PlantInfo implements Serializable { + private String indexPoint; + private String nameGD; + private String nameBD; + private String nameSubV; + private String namePoint; + private String monitorVoltageLevel; + private String monitorLoadType; + private String objName; +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/SagEvent.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/SagEvent.java new file mode 100644 index 0000000..f824407 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/dto/eventAggregate/SagEvent.java @@ -0,0 +1,420 @@ +package com.njcn.product.advance.eventSource.pojo.dto.eventAggregate; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * pqs + * + * @author cdf + * @date 2023/7/21 + */ +public class SagEvent implements Comparable, Serializable { + // 事件的唯一标识 + private String indexEventDetail; + + private Integer waveType; + // 暂降事件发生时间 + private LocalDateTime sagTime; + + // 暂降事件发生时间毫秒 + private Integer msec; + + // 事件描述 + private String events; + + // 持续时间 + private Float persistTime; + + // 发生暂降事件的监测点层级信息 + private PlantInfo plantInfo; + + // 拼接sagTime和msec + private String strTime; + + // 事件发生时刻的毫秒表示 + private Long time; + + // 监测点的唯一标识 + private String indexPoint; + + // 归一化事件的GUID + private String indexEventAss; + + // 特征幅值 + private Float eventValue; + + // 暂降原因 + private String sagReason; + + // 暂降类型 + private String sagType; + + // 暂降类型描述 + private String sagTypeDes; + + public String getSagTypeDes() { + return sagTypeDes; + } + + public void setSagTypeDes(String sagTypeDes) { + this.sagTypeDes = sagTypeDes; + } + + // 暂降深度 + private String strEventValue; + private String strPersist; + + // 事件是否经过高级算法处理(0-未处理,1-已处理,默认为0) + private Integer dealFlag; + + // 事件是否经过高级算法处理中文描述(已处理、未处理) + private String dealFlagDescription; + + // 录波文件是否存在(0-不存在,1-存在,默认为0) + private Integer fileFlag; + + // 录波文件是否存在中文描述(存在、不存在) + private String fileFlagDescription; + + // 高级算法返回dq持续时间 + private Float dqTime; + + // 高级算法处理事件个数记录 + private Integer number; + + // 归一化处理更新时间 + private LocalDateTime dealTime; + + // 高级算法的对应关系 + private int cata; + + // 第一次事件的触发时间 + private LocalDateTime firstTime; + + // 第一次事件的暂降类型 + private String firstType; + + // 第一次事件的触发时间毫秒 + private Integer firstMs; + + // 第一次事件触发时间date->毫秒 + private Long firstTimeMills; + + // 暂降严重度 + private Float severity; + + // 排序方式 + private int sortType = 0; // 初始化默认为0-按照时间排序 新增1-按暂降严重度排序 2-暂降发生时刻排序 3-先根据电压等级排序,如果相等再按照暂降幅值排序 + + //电压等级 + private Double voltage; + + //监测点对象名称 + private String objName; + + public String getObjName() { + return objName; + } + + public void setObjName(String objName) { + this.objName = objName; + } + + public Integer getWaveType() { + return waveType; + } + + public void setWaveType(Integer waveType) { + this.waveType = waveType; + } + + private String strVoltage; + + public Double getVoltage() { + return voltage; + } + + public void setVoltage(Double voltage) { + this.voltage = voltage; + } + + public String getStrVoltage() { + return strVoltage; + } + + public void setStrVoltage(String strVoltage) { + //转为double + strVoltage = strVoltage.toUpperCase(); + String str = strVoltage.substring(0, strVoltage.indexOf("KV")); + this.voltage = Double.parseDouble(str); + } + + public int getSortType() { + return sortType; + } + + public void setSortType(int sortType) { + this.sortType = sortType; + } + + public Float getSeverity() { + return severity; + } + + public void setSeverity(Float severity) { + this.severity = severity; + } + + public Long getFirstTimeMills() { + return firstTimeMills; + } + + public void setFirstTimeMills(Long firstTimeMills) { + this.firstTimeMills = firstTimeMills; + } + + public Integer getFirstMs() { + return firstMs; + } + + public void setFirstMs(Integer firstMs) { + this.firstMs = firstMs; + } + + public LocalDateTime getFirstTime() { + return firstTime; + } + + public void setFirstTime(LocalDateTime firstTime) { + this.firstTime = firstTime; + } + + public String getFirstType() { + return firstType; + } + + public void setFirstType(String firstType) { + this.firstType = firstType; + } + + public Integer getFileFlag() { + return fileFlag; + } + + public void setFileFlag(Integer fileFlag) { + this.fileFlag = fileFlag; + } + + public String getFileFlagDescription() { + return fileFlagDescription; + } + + public void setFileFlagDescription(String fileFlagDescription) { + this.fileFlagDescription = fileFlagDescription; + } + + public int getCata() { + return cata; + } + + public void setCata(int cata) { + this.cata = cata; + } + + public LocalDateTime getDealTime() { + return dealTime; + } + + public void setDealTime(LocalDateTime dealTime) { + this.dealTime = dealTime; + } + + public Float getDqTime() { + return dqTime; + } + + public void setDqTime(Float dqTime) { + this.dqTime = dqTime; + } + + public Integer getNumber() { + return number; + } + + public void setNumber(Integer number) { + this.number = number; + } + + public void setDealFlagDescription(String dealFlagDescription) { + this.dealFlagDescription = dealFlagDescription; + } + + public String getDealFlagDescription() { + return dealFlagDescription; + } + + public Integer getDealFlag() { + return dealFlag; + } + + public void setDealFlag(Integer dealFlag) { + this.dealFlag = dealFlag; + } + + public String getIndexEventDetail() { + return indexEventDetail; + } + + public void setIndexEventDetail(String indexEventDetail) { + this.indexEventDetail = indexEventDetail; + } + + public LocalDateTime getSagTime() { + return sagTime; + } + + public void setSagTime(LocalDateTime sagTime) { + this.sagTime = sagTime; + } + + public Integer getMsec() { + return msec; + } + + public void setMsec(Integer msec) { + this.msec = msec; + } + + public String getEvents() { + return events; + } + + public void setEvents(String events) { + this.events = events; + } + + public Float getPersistTime() { + return persistTime; + } + + public void setPersistTime(Float persistTime) { + if (persistTime == null) { + this.persistTime = 0f; + return; + } + + float f1 = (float) (Math.round(persistTime.floatValue() * 1000)) / 1000; + this.persistTime = new Float(f1); + } + + public PlantInfo getPlantInfo() { + return plantInfo; + } + + public void setPlantInfo(PlantInfo plantInfo) { + this.plantInfo = plantInfo; + } + + public String getStrTime() { + return strTime; + } + + public void setStrTime(String strTime) { + this.strTime = strTime; + } + + public Long getTime() { + return time; + } + + public void setTime(Long time) { + this.time = time; + } + + public String getIndexPoint() { + return indexPoint; + } + + public void setIndexPoint(String indexPoint) { + this.indexPoint = indexPoint; + } + + public String getIndexEventAss() { + return indexEventAss; + } + + public void setIndexEventAss(String indexEventAss) { + this.indexEventAss = indexEventAss; + } + + public Float getEventValue() { + return eventValue; + } + + public void setEventValue(Float eventValue) { + if (eventValue == null) { + this.eventValue = 0f; + return; + } + + this.eventValue = eventValue; + } + + public String getSagReason() { + return sagReason; + } + + public void setSagReason(String sagReason) { + this.sagReason = sagReason; + } + + public String getSagType() { + return sagType; + } + + public void setSagType(String sagType) { + this.sagType = sagType; + } + + public String getStrEventValue() { + return strEventValue; + } + + public void setStrEventValue(String strEventValue) { + this.strEventValue = strEventValue; + } + + public String getStrPersist() { + return strPersist; + } + + public void setStrPersist(String strPersist) { + this.strPersist = strPersist; + } + + + + // 根据设定规则进行排序 + @Override + public int compareTo(SagEvent obj) { + switch (this.getSortType()) { + case 1: + return obj.getSeverity().compareTo(this.getSeverity()); + case 2: + return this.getTime().compareTo(obj.getTime()); + case 3: { + if (obj.getVoltage().compareTo(this.getVoltage()) != 0) { + return obj.getVoltage().compareTo(this.getVoltage()); + } + else { + return this.getEventValue().compareTo(obj.getEventValue()); + } + } + default: + break; + } + + return this.getFirstTimeMills().compareTo(obj.getFirstTimeMills()); + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/enums/AdvanceResponseEnum.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/enums/AdvanceResponseEnum.java new file mode 100644 index 0000000..6de2d45 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/enums/AdvanceResponseEnum.java @@ -0,0 +1,107 @@ +package com.njcn.product.advance.eventSource.pojo.enums; + +import lombok.Getter; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年04月13日 10:50 + */ +@Getter +public enum AdvanceResponseEnum { + + ANALYSIS_USER_DATA_ERROR("A0101","解析用采数据内容失败"), + + INTERNAL_ERROR("A0101","系统内部异常"), + + USER_DATA_EMPTY("A0101","用采数据内容为空"), + + USER_DATA_NOT_FOUND("A0101","未找到用采数据"), + + RESP_DATA_NOT_FOUND("A0101","未找到责任划分数据"), + + WIN_TIME_ERROR("A0101","限值时间小于窗口"), + + CALCULATE_INTERVAL_ERROR("A0101","对齐计算间隔值非法"), + + RESP_RESULT_DATA_NOT_FOUND("A0101","未找到责任划分缓存数据"), + + USER_DATA_P_NODE_PARAMETER_ERROR("A0101","无用采用户或所有用户的完整性均不满足条件"), + + RESPONSIBILITY_PARAMETER_ERROR("A0101","调用接口程序计算失败,参数非法"), + + EVENT_EMPTY("A0102","没有查询到未分析事件"), + + USER_NAME_EXIST("A0103","用户名已存在"), + + DATA_NOT_FOUND("A0104","数据缺失,请根据模版上传近两周数据"), + + DATA_UNDERRUN("A0104","数据量不足,请根据模版上传充足近两周数据"), + + DOCUMENT_FORMAT_ERROR("A0105","数据缺失,导入失败!请检查导入文档的格式是否正确"), + DEVICE_LOST("A0104","用户下缺少设备"), + + USER_LOST("A0106","干扰源用户缺失"), + UNCOMPLETE_STRATEGY("A0106","配置安全,III级预警,II级预警,I级预警4条完整策略"), + EXISTENCE_EVALUATION_RESULT("A0104","存在评结果结果,如要评估,请删除后评估"), + + SG_USER_NAME_REPEAT("A0102","业务用户名重复"), + + SG_PRODUCT_LINE_NAME_REPEAT("A0102","生产线名重复"), + + SG_USER_ID_MISS("A0102","业务用户id缺失"), + + SG_PRODUCT_LINE_ID_MISS("A0102","生产线id缺失"), + + SG_MACHINE_ID_MISS("A0102","设备id缺失"), + + IMPORT_EVENT_DATA_FAIL("A0102","请检查导入数据的准确性"), + + PRODUCT_LINE_DATA_MISS("A0102","生产线数据缺失"), + + MACHINE_DATA_MISS("A0102","设备数据缺失"), + + INCOMING_LINE_DATA_MISS("A0102","进线数据缺失"), + + EVENT_DATA_MISS("A0102","没有可供参考的暂降数据"), + + WIN_DATA_ERROR("A0102","算法校验窗宽超限"), + + DATA_ERROR("A0102","算法校验数据长度超限"), + + INIT_DATA_ERROR("A0102","算法初始化数据失败"), + + USER_HAS_PRODUCT("A0102","当前用户存在生产线"), + + PRODUCT_HAS_MACHINE("A0102","当前生产线存在设备"), + + MACHINE_HAS_UNIT("A0102","当前设备存在元器件"), + + EVENT_TIME_ERROR("A0102","暂降事件时间格式有误,请检查"), + + INVALID_FILE_TYPE("A0102","请选择CSV文件"), + + INSUFFICIENCY_OF_INTEGRITY("A00561","时间范围内谐波数据完整性不足"), + INTERVAL_ERROR("A0102","监测点时间间隔错误"), + + ; + + private final String code; + + private final String message; + + AdvanceResponseEnum(String code, String message) { + this.code = code; + this.message = message; + } + + public static String getCodeByMsg(String msg){ + for (AdvanceResponseEnum userCodeEnum : AdvanceResponseEnum.values()) { + if (userCodeEnum.message.equalsIgnoreCase(msg)) { + return userCodeEnum.code; + } + } + return ""; + } + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/po/PqsRelevanceLog.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/po/PqsRelevanceLog.java new file mode 100644 index 0000000..d290de5 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/po/PqsRelevanceLog.java @@ -0,0 +1,39 @@ +package com.njcn.product.advance.eventSource.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + + +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("pqs_relevancy_log") +public class PqsRelevanceLog extends BaseEntity { + + @TableId("id") + private String id; + /** + * 归一化算法时间 + */ + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timeId; + + + /** + * 归一化算法描述 + */ + private String contentDes; + + private Integer state; + + @TableField(exist = false) + private String createName; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/po/RmpEventDetailAssPO.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/po/RmpEventDetailAssPO.java new file mode 100644 index 0000000..c778c3e --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/pojo/po/RmpEventDetailAssPO.java @@ -0,0 +1,52 @@ +package com.njcn.product.advance.eventSource.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import com.njcn.product.advance.eventSource.pojo.dto.eventAggregate.SagEvent; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2023/8/9 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("r_mp_event_detail_ass") +public class RmpEventDetailAssPO extends BaseEntity { + + /** + *事件关联分析表uuid + */ + @TableId("Event_Ass_Id") + private String eventAssId; + + /** + *发生时间(归一化中第一个时间) + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") + private LocalDateTime timeId; + + /** + *关联事件描述 + */ + private String contentDes; + + /** + *是否进行范围分析(0:分析;1:未分析) + */ + private Integer analyseFlag; + + + @TableField(exist = false) + private List list; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/EventRelevantAnalysisService.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/EventRelevantAnalysisService.java new file mode 100644 index 0000000..c0a7fd2 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/EventRelevantAnalysisService.java @@ -0,0 +1,35 @@ +package com.njcn.product.advance.eventSource.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.RmpEventDetailPO; +import com.njcn.web.pojo.param.BaseParam; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2023/6/30 + */ +public interface EventRelevantAnalysisService extends IService { + + /** + * + * @author cdf + * @date 2023/6/30 + */ + void processEvents(LocalDateTime startTime,LocalDateTime endTime,String deptId); + + + + + + + + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/HistoryHarmonicService.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/HistoryHarmonicService.java new file mode 100644 index 0000000..6b64a62 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/HistoryHarmonicService.java @@ -0,0 +1,25 @@ +package com.njcn.product.advance.eventSource.service; + +import com.njcn.influx.pojo.dto.HarmHistoryDataDTO; +import com.njcn.influx.pojo.po.DataHarmPowerP; +import com.njcn.product.advance.responsility.pojo.bo.UserDataExcel; +import com.njcn.product.advance.responsility.pojo.param.HistoryHarmParam; +import com.njcn.product.advance.responsility.pojo.param.PHistoryHarmParam; + +import java.util.List; + +public interface HistoryHarmonicService { + + /*** + * 按次、监测点获取指定历史谐波数据 + * @author hongawen + * @date 2023/7/19 9:56 + * @param historyHarmParam 请求历史谐波数据参数 + * @return HarmHistoryDataDTO + */ + HarmHistoryDataDTO getHistoryHarmData(HistoryHarmParam historyHarmParam); + + + List getHarmonicPData(PHistoryHarmParam param); + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/RmpEventDetailAssService.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/RmpEventDetailAssService.java new file mode 100644 index 0000000..7c92e92 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/RmpEventDetailAssService.java @@ -0,0 +1,7 @@ +package com.njcn.product.advance.eventSource.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.advance.eventSource.pojo.po.RmpEventDetailAssPO; + +public interface RmpEventDetailAssService extends IService { +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/EventRelevantAnalysisServiceImpl.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/EventRelevantAnalysisServiceImpl.java new file mode 100644 index 0000000..941553f --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/EventRelevantAnalysisServiceImpl.java @@ -0,0 +1,565 @@ +package com.njcn.product.advance.eventSource.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.advance.eventSource.mapper.RelevantLogMapper; +import com.njcn.product.advance.eventSource.mapper.RmpEventAdvanceMapper; +import com.njcn.product.advance.eventSource.pojo.dto.eventAggregate.*; +import com.njcn.product.advance.eventSource.pojo.enums.AdvanceResponseEnum; +import com.njcn.product.advance.eventSource.pojo.po.PqsRelevanceLog; +import com.njcn.product.advance.eventSource.pojo.po.RmpEventDetailAssPO; +import com.njcn.product.advance.eventSource.service.EventRelevantAnalysisService; +import com.njcn.product.advance.eventSource.service.RmpEventDetailAssService; +import com.njcn.product.advance.eventSource.utils.UtilNormalization; +import com.njcn.product.system.dict.mapper.DictDataMapper; +import com.njcn.product.system.dict.pojo.enums.DicDataEnum; +import com.njcn.product.system.dict.pojo.enums.DicDataTypeEnum; +import com.njcn.product.system.dict.pojo.po.DictData; +import com.njcn.product.terminal.mysqlTerminal.mapper.LedgerScaleMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerBaseInfo; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.PqsTflgployass; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.RmpEventDetailPO; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.AdvanceEventDetailVO; +import com.njcn.product.terminal.mysqlTerminal.service.CommGeneralService; +import com.njcn.web.utils.RequestUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.sql.Timestamp; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * pqs + * + * @author cdf + * @date 2023/6/30 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class EventRelevantAnalysisServiceImpl extends ServiceImpl implements EventRelevantAnalysisService { + + + private final DictDataMapper dictDataMapper; + + private final RmpEventAdvanceMapper rmpEventAdvanceMapper; + + private final RelevantLogMapper relevantLogMapper; + + private final CommGeneralService commGeneralService; + + private final RmpEventAdvanceMapper eventAdvanceMapper; + + private final LedgerScaleMapper ledgerScaleMapper; + + private final RmpEventDetailAssService rmpEventDetailAssService; + + + @Override + @Transactional(rollbackFor = Exception.class) + public void processEvents(LocalDateTime startTime, LocalDateTime endTime, String deptId) { + TimeInterval timeInterval = new TimeInterval(); + + //获取节点和变压器配置信息 + Map> nodeMap1 = getBeforeNodeInfo(); + Set>> nodeSort1 = nodeMap1.entrySet(); + System.out.println(nodeSort1); + + Map entityMtransMap1 = getNodeInfo(); + Set> setMtrans1 = entityMtransMap1.entrySet(); + System.out.println(setMtrans1); + + LocalDateTime date = LocalDateTime.now(); + HandleEvent handleEvent = new HandleEvent(); + // 获取并验证基础数据 + List baseList = handleEvent.getData(startTime, endTime, deptId); + if (CollectionUtil.isEmpty(baseList)) { + throw new BusinessException("当前时间段暂无可分析事件"); + } + List otherEventList = new ArrayList<>(baseList); + + + Map entityMtransMap = getNodeInfo(); + Set> setMtrans = entityMtransMap.entrySet(); + System.out.println(setMtrans); + //获取节点和变压器配置信息 + Map> nodeMap = getBeforeNodeInfo(); + Set>> nodeSort = nodeMap.entrySet(); + System.out.println(nodeSort); + //初始化结果容器 + List listSagEvent = new ArrayList<>(); + List rmpEventDetailAssPoList = new ArrayList<>(); + + //获取短路故障字典 + DictData dictData = dictDataMapper.getDicDataByNameAndTypeName(DicDataTypeEnum.EVENT_REASON.getName(), DicDataEnum.SHORT_TROUBLE.getName()); + for (Map.Entry> m : nodeSort) { + List list = new ArrayList<>(); + Set> mapValue = m.getValue().entrySet(); + FinalData.NODE_NUM = m.getValue().size(); + + for (Map.Entry mm : mapValue) { + for (EntityGroupEvtData groupEvtData : baseList) { + if (groupEvtData.getNodePhysics().equals(mm.getKey()) && dictData.getId().equals(groupEvtData.getSagReason())) { + groupEvtData.setNode(mm.getValue()); + list.add(groupEvtData); + } + } + + // 筛选不在矩阵中的事件id + otherEventList.removeIf(entityGroupEvtData -> entityGroupEvtData.getNodePhysics().equals(mm.getKey()) && dictData.getId().equals(entityGroupEvtData.getSagReason())); + } + + EntityGroupEvtData[] entityGroupEvtData = new EntityGroupEvtData[list.size()]; + Collections.sort(list); + list.toArray(entityGroupEvtData); + + + for (Map.Entry mEntry : setMtrans) { + if (mEntry.getKey().equals(m.getKey())) { + //算法最多处理1000条数据,超过限制需分批处理 先将数据根据某种方式进行升序/降序排序,然后分段处理 加入循环处理 + int circulation = entityGroupEvtData.length % FinalData.MAX_EVT_NUM == 0 + ? entityGroupEvtData.length / FinalData.MAX_EVT_NUM + : entityGroupEvtData.length / FinalData.MAX_EVT_NUM + 1; + + for (int i = 0; i < circulation; i++) { + int to = 0; + + if (i == circulation - 1) { + to = entityGroupEvtData.length % FinalData.MAX_EVT_NUM > 0 + ? entityGroupEvtData.length + : (i + 1) * FinalData.MAX_EVT_NUM - 1; + } else { + to = (i + 1) * FinalData.MAX_EVT_NUM - 1; + } + + EntityGroupEvtData[] arrayObj = Arrays.copyOfRange(entityGroupEvtData, + i * FinalData.MAX_EVT_NUM, to); + EntityMtrans entityMtrans = mEntry.getValue(); + EntityGroupData entityGroupData = handleEvent.translate(arrayObj, entityMtrans); + // 处理分析结果 + handleEvent.show_group_info(entityGroupData, listSagEvent, rmpEventDetailAssPoList, date); + } + } + } + } + + // 处理非标准数据 + disposeNonStandardData(handleEvent, otherEventList, rmpEventDetailAssPoList, listSagEvent, date); + rmpEventDetailAssService.saveBatch(rmpEventDetailAssPoList); + + List eventUpdateList = new ArrayList<>(); + for (int i = 0; i < listSagEvent.size(); i++) { + RmpEventDetailPO rmp = new RmpEventDetailPO(); + rmp.setEventId(listSagEvent.get(i).getIndexEventDetail()); + rmp.setEventassIndex(listSagEvent.get(i).getIndexEventAss()); + rmp.setDealTime(listSagEvent.get(i).getDealTime()); + eventUpdateList.add(rmp); + if ((i + 1) % 1000 == 0) { + this.updateBatchById(eventUpdateList); + eventUpdateList.clear(); + } else if (i == listSagEvent.size() - 1) { + this.updateBatchById(eventUpdateList); + } + } + + // 增加策略记录 + String describe = "用户" + RequestUtil.getLoginName() + "进行了关联分析"; + PqsRelevanceLog entityPqsRelevance = new PqsRelevanceLog(); + entityPqsRelevance.setContentDes(describe); + entityPqsRelevance.setState(DataStateEnum.ENABLE.getCode()); + entityPqsRelevance.setTimeId(date); + relevantLogMapper.insert(entityPqsRelevance); + + log.info("事件关联分析用时:" + timeInterval.interval() / 1000 + "秒"); + } + + + /********************************************************************** + * 归集结果与非矩阵事件进行比对 + **********************************************************************/ + public void disposeNonStandardData(HandleEvent handleEvent, List noDealList, List assPoList, List list2, LocalDateTime date) { + Iterator iterator = noDealList.iterator(); + while (iterator.hasNext()) { + EntityGroupEvtData entityGroupEvtData = iterator.next(); + + for (RmpEventDetailAssPO eventAssObj : assPoList) { + long sRange = Math.abs(Duration.between(eventAssObj.getTimeId(), entityGroupEvtData.getSagEvent().getSagTime()).getSeconds()); + if (sRange < 10) { + int b = 0; + int a = 0; + + for (SagEvent sagEvent : eventAssObj.getList()) { + if (sagEvent.getCata() == 9) { + b++; + } else if (sagEvent.getCata() != 10) { + a++; + } + } + + if (b > 0) { + if (entityGroupEvtData.getCata() < 9) { + break; + } + } else if (a > 0) { + if (entityGroupEvtData.getCata() == 9) { + break; + } + } + + iterator.remove(); + entityGroupEvtData.getSagEvent().setIndexEventAss(eventAssObj.getEventAssId()); + entityGroupEvtData.getSagEvent().setDealTime(date); + eventAssObj.getList().add(entityGroupEvtData.getSagEvent()); + String describe = "事件关联分析编号" + eventAssObj.getTimeId() + "共包含" + eventAssObj.getList().size() + "个事件"; + eventAssObj.setContentDes(describe); + list2.add(entityGroupEvtData.getSagEvent()); + break; + } + } + } + + // 如果还有未归集的数据则单独拎为单一事件处理 + for (EntityGroupEvtData entityGroupEvtData : noDealList) { + String strUUID = IdUtil.simpleUUID(); + entityGroupEvtData.getSagEvent().setIndexEventAss(strUUID); + entityGroupEvtData.getSagEvent().setDealTime(date); + + List dealList = new ArrayList<>(); + dealList.add(entityGroupEvtData.getSagEvent()); + handleEvent.processing(dealList, assPoList, date); + list2.add(entityGroupEvtData.getSagEvent()); + } + } + + + class HandleEvent { + public EntityGroupData translate(EntityGroupEvtData[] entityGroupEvtData, EntityMtrans entityMtrans) { + // 获取测试数据的数组长度 + int testLogNum = entityGroupEvtData.length; + + // 实例化EntityGroupData,给其中的数组分配空间 + EntityGroupData groupBuf = new EntityGroupData(); + + // 填入日志 + setMatrixcata(groupBuf, entityMtrans); + create_evt_buf(entityGroupEvtData, groupBuf, testLogNum); + + UtilNormalization.sort_Tstart(groupBuf); // 根据时标进行划分 + // 根据暂降类型进行划分 + for (int i = 0; i < groupBuf.getGrp_all_num(); i++) { + UtilNormalization.sort_cata(groupBuf, i); + } + + return groupBuf; + } + + //获取原始暂降数据 + public List getData(LocalDateTime startTime, LocalDateTime endTime, String deptId) { + List entityGroupEvtDataList = new ArrayList<>(); + + List advanceType = dictDataMapper.getDicDataByTypeCode(DicDataTypeEnum.EVENT_TYPE.getCode()); + Map advanceMap = advanceType.stream().collect(Collectors.toMap(DictData::getId, Function.identity())); + + //获取时间范围内的事件 + List advanceEventDetailVOList = querySagEventsAll(startTime, endTime, deptId); + for (AdvanceEventDetailVO advanceEventDetailVO : advanceEventDetailVOList) { // 获取监测点线路序号 + //母线id + String nodePhysics = advanceEventDetailVO.getBusBarId(); + + // 根据暂降类型获取高级算法对应的编号 + int cata; + int startTimeTemp; + + if (Objects.isNull(advanceEventDetailVO.getFirstType())) { + cata = advanceMap.get(advanceEventDetailVO.getAdvanceType()).getAlgoDescribe(); + long timestampMillis = advanceEventDetailVO.getStartTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + startTimeTemp = (int) (timestampMillis / 1000); + } else { + cata = advanceMap.get(advanceEventDetailVO.getAdvanceType()).getAlgoDescribe(); // 获取类型 + long timestampMillis = advanceEventDetailVO.getFirstTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + startTimeTemp = (int) (timestampMillis / 1000); + } + + // 填充SagEvent对象数据 + SagEvent sagEvent = new SagEvent(); + + sagEvent.setIndexEventDetail(advanceEventDetailVO.getEventId()); + sagEvent.setSagTime(advanceEventDetailVO.getStartTime()); + sagEvent.setFirstTime(advanceEventDetailVO.getFirstTime());// 必须增加,否则序列化出错 + sagEvent.setTime(Timestamp.valueOf(advanceEventDetailVO.getStartTime()).getTime()); + sagEvent.setFirstTimeMills((long) startTimeTemp); + sagEvent.setMsec(advanceEventDetailVO.getDuration()); + PlantInfo plantInfo = new PlantInfo(); + plantInfo.setNameBD(advanceEventDetailVO.getSubName()); + plantInfo.setNameGD(advanceEventDetailVO.getGdName()); + plantInfo.setNamePoint(advanceEventDetailVO.getLineId()); + sagEvent.setPlantInfo(plantInfo); + sagEvent.setIndexPoint(advanceEventDetailVO.getLineId()); + sagEvent.setCata(cata); + + + EntityGroupEvtData entityGroupEvtData = new EntityGroupEvtData(nodePhysics, startTimeTemp, cata, -1, sagEvent, advanceEventDetailVO.getAdvanceReason()); + entityGroupEvtDataList.add(entityGroupEvtData); + } + + return entityGroupEvtDataList; + } + + + public void create_evt_buf(EntityGroupEvtData[] arr, EntityGroupData obj, int len) { + System.arraycopy(arr, 0, obj.getIn_buf(), 0, arr.length); + obj.setEvt_in_num(len); + } + + public void create_matrixcata(List list, EntityMtrans entityMtrans) { + EntityLogic[] node_data = new EntityLogic[list.size()]; + + for (int i = 0; i < list.size(); i++) { + node_data[i] = list.get(i); + } + + int len = node_data.length; + + UtilNormalization.matrixcata_pro(node_data, entityMtrans, len); + } + + public void setMatrixcata(EntityGroupData obj, EntityMtrans entityMtrans) { + int i, j; + for (i = 0; i < (FinalData.MAX_CATA_NUM - 1); i++) { + for (j = 0; j < FinalData.NODE_NUM; j++) { + obj.getMatrixcata()[i][j] = entityMtrans.getMatrixcata1()[i][j]; + } + } + } + + public void show_group_info(EntityGroupData obj, List list, List assEvent, LocalDateTime date) { + int i, j, k; + for (i = 0; i < obj.getGrp_all_num(); i++) { + String strUUID = IdUtil.simpleUUID(); + List listTem = new ArrayList<>(); + + for (j = 0; j < FinalData.MAX_CATA_NUM + 2; j++) { + if (obj.getGrp_cata_num()[i][j] != 0) { + for (k = 0; k < obj.getGrp_cata_num()[i][j]; k++) { + obj.getGrp_cata_buf()[i][j][k].getSagEvent().setIndexEventAss(strUUID); + obj.getGrp_cata_buf()[i][j][k].getSagEvent().setDealTime(date); + listTem.add(obj.getGrp_cata_buf()[i][j][k].getSagEvent()); + list.add(obj.getGrp_cata_buf()[i][j][k].getSagEvent()); + } + } + } + + if (!listTem.isEmpty()) { + processing(listTem, assEvent, date); + } + } + } + + public void processing(List list, List lists, LocalDateTime date) { + // 根据暂降事件发生时间进行排序 + Collections.sort(list); + RmpEventDetailAssPO eventAssObj = new RmpEventDetailAssPO(); + String strUUID = list.get(0).getIndexEventAss(); + + // 归一化处理数据填充 + eventAssObj.setEventAssId(strUUID); + + // 事件发生时间 + eventAssObj.setTimeId(list.get(0).getSagTime()); + + + // 获取当前用户GUID + eventAssObj.setCreateBy(RequestUtil.getUserId()); + + // 是否进行范围分析 默认未分析 + eventAssObj.setAnalyseFlag(1); + + // 更新时间 + eventAssObj.setUpdateTime(date); + + + // 暂降事件描述 + String codeName = LocalDateTimeUtil.format(eventAssObj.getTimeId(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")); + String describe = "事件关联分析编号" + codeName + "共包含" + list.size() + "个事件"; + eventAssObj.setContentDes(describe); + eventAssObj.setList(list); + lists.add(eventAssObj); + } + } + + + Map> getBeforeNodeInfo() { + Map> setNodeSort = new HashMap<>(); + List list = rmpEventAdvanceMapper.getLogic(); + + if (CollectionUtil.isNotEmpty(list)) { + Map> map = getLogicInfo(list); + setNodeSort = nodeSort(map); + } + return setNodeSort; + } + + + /************************************************************************************* + * 获取变压器信息并生成矩阵 + *************************************************************************************/ + public Map getNodeInfo() { + Map entityMtranMap = new HashMap<>(32); + List list = rmpEventAdvanceMapper.getLogic(); + + if (CollectionUtil.isNotEmpty(list)) { + Map> map = getLogicInfo(list); + Map> setNodeSort = nodeSort(map); + + setNodeSort.forEach((key, val) -> { + FinalData.NODE_NUM = val.size(); + List listNew = new ArrayList<>(); + + for (EntityLogic entityLogic : list) { + if (entityLogic.getTPIndex().equals(key)) { + entityLogic.setNode_h(val.get(entityLogic.getNodeBefore())); + entityLogic.setNode_l(val.get(entityLogic.getNodeNext())); + listNew.add(entityLogic); + } + } + + EntityMtrans entityMtrans = new EntityMtrans(); + + HandleEvent handleEvent = new HandleEvent(); + handleEvent.create_matrixcata(listNew, entityMtrans); + entityMtranMap.put(key, entityMtrans); + }); + } + return entityMtranMap; + } + + + /******************************************* + * 增加排序功能并缓存进redis + *******************************************/ + public Map> nodeSort(Map> mapList) { + Set>> sets = mapList.entrySet(); + Map> map = new HashMap<>(); + + for (Map.Entry> m : sets) { + int index = 1; + Map map2 = new HashMap<>(); + + for (String item : m.getValue()) { + map2.put(item, index++); + } + + map.put(m.getKey(), map2); + } + return map; + } + + + /** + * 抽取物理隔绝信息与母线的关系并放入map集合中 + * 与getTflgPloyInfo()方法功能类似 + */ + public Map> getLogicInfo(List list) { + if (list.size() > 0) { + Iterator iterator = getAreaInfo(list).iterator(); + Map> map = new HashMap<>(); + + while (iterator.hasNext()) { + List listLogic = new ArrayList<>(); + String areaString = iterator.next(); + + for (EntityLogic entityLogic : list) { + if (entityLogic.getTPIndex().equals(areaString)) { + listLogic.add(entityLogic.getNodeBefore()); + listLogic.add(entityLogic.getNodeNext()); + } + } + + //去除list中重复数据 + Set set = new TreeSet<>(listLogic); + map.put(areaString, new ArrayList<>(set)); + } + + return map; + } + + return null; + } + + + /** + * 获取物理隔绝编码信息 + * 供getInfo()、getLogicInfo()方法使用 + * 先从list数组中去重,然后获取物理隔绝编码 + */ + public Set getAreaInfo(List list) { + Set set = new HashSet(list); + Iterator iterator = set.iterator(); + Set setReturn = new HashSet(); + + while (iterator.hasNext()) { + Object object = iterator.next(); + + if (object instanceof PqsTflgployass) { + setReturn.add(((PqsTflgployass) object).getTpIndex()); + continue; + } + + setReturn.add(((EntityLogic) object).getTPIndex()); + } + + return setReturn; + } + + + public List querySagEventsAll(LocalDateTime startTime, LocalDateTime endTime, String deptId) { + List result = new ArrayList<>(); + List lineIds = commGeneralService.getRunLineIdsByDept(deptId); + if (CollUtil.isNotEmpty(lineIds)) { + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.and(i -> i.isNull(RmpEventDetailPO::getEventassIndex).or().eq(RmpEventDetailPO::getEventassIndex, "")) + .between(RmpEventDetailPO::getStartTime, startTime, endTime).in(RmpEventDetailPO::getMeasurementPointId,lineIds); + List rmpEventDetailPOList = eventAdvanceMapper.selectList(lambdaQueryWrapper); + if (CollectionUtil.isEmpty(rmpEventDetailPOList)) { + throw new BusinessException(AdvanceResponseEnum.EVENT_EMPTY); + } + List tempLineIds = rmpEventDetailPOList.stream().map(RmpEventDetailPO::getLineId).distinct().collect(Collectors.toList()); + List temLine = ledgerScaleMapper.getLedgerBaseInfo(tempLineIds); + Map map = temLine.stream().collect(Collectors.toMap(LedgerBaseInfo::getLineId, Function.identity())); + + result = BeanUtil.copyToList(rmpEventDetailPOList, AdvanceEventDetailVO.class); + result.forEach(item -> { + if (map.containsKey(item.getLineId())) { + LedgerBaseInfo areaLineInfoVO = map.get(item.getLineId()); + item.setGdName(areaLineInfoVO.getGdName()); + item.setSubName(areaLineInfoVO.getStationName()); + item.setNum(areaLineInfoVO.getNum()); + item.setVoltageId(areaLineInfoVO.getVoltageLevel()); + item.setBusBarId(areaLineInfoVO.getBusBarId()); + } + }); + } + result = result.stream().filter(it -> StrUtil.isNotBlank(it.getBusBarId())).collect(Collectors.toList()); + return result; + } +} + diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/HistoryHarmonicServiceImpl.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/HistoryHarmonicServiceImpl.java new file mode 100644 index 0000000..e46e340 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/HistoryHarmonicServiceImpl.java @@ -0,0 +1,344 @@ +package com.njcn.product.advance.eventSource.service.impl; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.utils.PubUtils; +import com.njcn.influx.imapper.DataHarmPowerPMapper; +import com.njcn.influx.imapper.DataHarmRateVMapper; +import com.njcn.influx.imapper.DataIMapper; +import com.njcn.influx.pojo.constant.InfluxDBTableConstant; +import com.njcn.influx.pojo.dto.HarmData; +import com.njcn.influx.pojo.dto.HarmHistoryDataDTO; +import com.njcn.influx.pojo.po.DataHarmPowerP; +import com.njcn.influx.pojo.po.DataHarmRateV; +import com.njcn.influx.pojo.po.DataI; +import com.njcn.influx.query.InfluxQueryWrapper; +import com.njcn.product.advance.eventSource.pojo.enums.AdvanceResponseEnum; +import com.njcn.product.advance.responsility.imapper.DataHarmP; +import com.njcn.product.advance.responsility.pojo.bo.UserDataExcel; +import com.njcn.product.advance.responsility.pojo.param.HistoryHarmParam; +import com.njcn.product.advance.eventSource.service.HistoryHarmonicService; +import com.njcn.product.advance.responsility.pojo.param.PHistoryHarmParam; +import com.njcn.product.terminal.mysqlTerminal.mapper.LineMapper; +import com.njcn.product.terminal.mysqlTerminal.mapper.OverlimitMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LineDevGetDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Overlimit; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * @Author: cdf + * @CreateTime: 2025-09-08 + * @Description: + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class HistoryHarmonicServiceImpl implements HistoryHarmonicService { + + private final OverlimitMapper overlimitMapper; + + private final LineMapper lineMapper; + + private final DataIMapper dataIMapper; + + private final DataHarmRateVMapper dataHarmRateVMapper; + + private final DataHarmPowerPMapper dataHarmPowerPMapper; + + + + + @Override + public HarmHistoryDataDTO getHistoryHarmData(HistoryHarmParam historyHarmParam) { + List historyData; + float overLimit; + Overlimit overlimit = overlimitMapper.selectById(historyHarmParam.getLineId()); + //判断是电流还是电压谐波 + if (historyHarmParam.getType() == 0) { + historyData = getIHistoryData(historyHarmParam); + overLimit = PubUtils.getValueByMethod(overlimit, "getIharm", historyHarmParam.getTime()); + } else { + historyData = getVHistoryData(historyHarmParam); + overLimit = PubUtils.getValueByMethod(overlimit, "getUharm", historyHarmParam.getTime()); + } + return new HarmHistoryDataDTO(historyData, overLimit); + } + + @Override + public List getHarmonicPData(PHistoryHarmParam historyHarmParam) { + InfluxQueryWrapper influxQueryWrapper = new InfluxQueryWrapper(DataHarmPowerP.class); + influxQueryWrapper + .select(DataHarmPowerP::getP,DataHarmPowerP::getTime,DataHarmPowerP::getLineId) + .eq(DataHarmPowerP::getPhaseType,InfluxDBTableConstant.PHASE_TYPE_T) + .eq(DataHarmPowerP::getValueType,InfluxDBTableConstant.CP95) + .regular(DataHarmPowerP::getLineId,historyHarmParam.getLineIds()) + .between(DataHarmPowerP::getTime,historyHarmParam.getSearchBeginTime().concat(InfluxDBTableConstant.START_TIME), historyHarmParam.getSearchEndTime().concat(InfluxDBTableConstant.END_TIME)); + List dataHarmPowerPList = dataHarmPowerPMapper.selectByQueryWrapper(influxQueryWrapper); + return dataHarmPowerPList; + } + + + /*** + * 获取指定次数 监测点的历史谐波电流数据 + * @author hongawen + * @date 2023/7/19 10:03 + */ + private List getIHistoryData(HistoryHarmParam historyHarmParam) { + LineDevGetDTO lineDetailData = lineMapper.getMonitorDetail(historyHarmParam.getLineId()); + List historyData; + InfluxQueryWrapper influxQueryWrapper = new InfluxQueryWrapper(DataI.class, HarmData.class); + influxQueryWrapper + .select(DataI::getTime) + .max("i_" + historyHarmParam.getTime(), "value") + .between(DataI::getTime, historyHarmParam.getSearchBeginTime().concat(" 00:00:00"), historyHarmParam.getSearchEndTime().concat(" 23:59:59")) + .eq(DataI::getLineId, historyHarmParam.getLineId()) + .or(DataI::getPhaseType, Stream.of(InfluxDBTableConstant.PHASE_TYPE_A, InfluxDBTableConstant.PHASE_TYPE_B, InfluxDBTableConstant.PHASE_TYPE_C).collect(Collectors.toList())) + //以时间分组时,需要加上时间间隔,比如此处需要加上监测点的采样间隔 + .groupBy("time(" + lineDetailData.getInterval() + "m)") + .timeAsc(); + String string = influxQueryWrapper.generateSql(); + historyData = dataIMapper.getIHistoryData(string); + if (CollectionUtils.isEmpty(historyData)) { + //如果数据为空,则提示给用户暂无数据 + throw new BusinessException(CommonResponseEnum.NO_DATA); + } + //最新两条数据的间隔与监测点查出的间隔做对比,返回一个合理的间隔 + historyData = historyData.stream().filter(Objects::nonNull).collect(Collectors.toList()); + int lineInterval = getInterval(lineDetailData.getInterval(), PubUtils.instantToDate(historyData.get(historyData.size() - 1).getTime()), PubUtils.instantToDate(historyData.get(historyData.size() - 2).getTime())); + historyData = dealHistoryData(historyData, lineInterval); + if (CollectionUtils.isEmpty(historyData)) { + //如果数据为空,则提示给用户暂无数据 + throw new BusinessException(CommonResponseEnum.NO_DATA); + } + //根据时间天数,获取理论上多少次用采数据 + List dateStr = PubUtils.getTimes(DateUtil.beginOfDay(DateUtil.parse(historyHarmParam.getSearchBeginTime())), DateUtil.endOfDay(DateUtil.parse(historyHarmParam.getSearchEndTime()))); + int dueTimes = dateStr.size() * 1440 / lineInterval; + int realTimes = historyData.size(); + if (dueTimes != realTimes) { + //期待值与实际值不等,则提示用户时间范围内谐波数据完整性不足 + throw new BusinessException(AdvanceResponseEnum.INSUFFICIENCY_OF_INTEGRITY); + } + return historyData.stream().sorted(Comparator.comparing(HarmData::getTime)).collect(Collectors.toList()); + } + + + /** + * 获取谐波电压的数据 + *

+ * 因历史谐波表data_harmrate_v + */ + private List getVHistoryData(HistoryHarmParam historyHarmParam) { + LineDevGetDTO lineDetailData = lineMapper.getMonitorDetail(historyHarmParam.getLineId()); + List historyData; + InfluxQueryWrapper influxQueryWrapper = new InfluxQueryWrapper(DataHarmRateV.class, HarmData.class); + influxQueryWrapper + .select(DataHarmRateV::getTime) + .max("v_" + historyHarmParam.getTime(), "value") + .between(DataHarmRateV::getTime, historyHarmParam.getSearchBeginTime().concat(" 00:00:00"), historyHarmParam.getSearchEndTime().concat(" 23:59:59")) + .eq(DataHarmRateV::getLineId, historyHarmParam.getLineId()) + .or(DataHarmRateV::getPhaseType, Stream.of(InfluxDBTableConstant.PHASE_TYPE_A, InfluxDBTableConstant.PHASE_TYPE_B, InfluxDBTableConstant.PHASE_TYPE_C).collect(Collectors.toList())) + .groupBy("time(" + lineDetailData.getInterval() + "m)") + .timeAsc(); + + + historyData = dataHarmRateVMapper.getHarmRateVHistoryData(influxQueryWrapper); + if (CollectionUtils.isEmpty(historyData)) { + //如果数据为空,则提示给用户暂无数据 + throw new BusinessException(CommonResponseEnum.NO_DATA); + } + historyData = historyData.stream().filter(Objects::nonNull).collect(Collectors.toList()); + int lineInterval = getInterval(lineDetailData.getInterval(), PubUtils.instantToDate(historyData.get(historyData.size() - 1).getTime()), PubUtils.instantToDate(historyData.get(historyData.size() - 2).getTime())); + //最新两条数据的间隔与监测点查出的间隔做对比,返回一个合理的间隔 + historyData = dealHistoryData(historyData, lineInterval); + if (CollectionUtils.isEmpty(historyData)) { + //如果数据为空,则提示给用户暂无数据 + throw new BusinessException(CommonResponseEnum.NO_DATA); + } + //根据时间天数,获取理论上多少次用采数据 + List dateStr = PubUtils.getTimes(DateUtil.beginOfDay(DateUtil.parse(historyHarmParam.getSearchBeginTime())), DateUtil.endOfDay(DateUtil.parse(historyHarmParam.getSearchEndTime()))); + int dueTimes = dateStr.size() * 1440 / lineInterval; + int realTimes = historyData.size(); + if (dueTimes != realTimes) { + //期待值与实际值不等,则提示用户时间范围内谐波数据完整性不足 + throw new BusinessException(AdvanceResponseEnum.INSUFFICIENCY_OF_INTEGRITY); + } + return historyData.stream().sorted(Comparator.comparing(HarmData::getTime)).collect(Collectors.toList()); + } + + + /** + * 获取合理的测量间隔 + */ + private int getInterval(int lineInterval, Date lastOne, Date lastTwo) { + int interval = 0; + Calendar one = Calendar.getInstance(); + one.setTime(lastOne); + Calendar two = Calendar.getInstance(); + two.setTime(lastTwo); + long oneTime = lastOne.getTime(); + long twoTime = lastTwo.getTime(); + long intvalTime = oneTime - twoTime; + long databaseInterval = lineInterval * 60 * 1000; + if (oneTime < twoTime || intvalTime >= databaseInterval) { + interval = lineInterval; + } + if (intvalTime < databaseInterval) { + interval = (int) (intvalTime / (1000 * 60)); + } + return interval; + } + + + + /** + * 根据库中查询的数据,进行数据补齐操作 + * + * @param beforeDeal 库中实际的历史谐波数据 + */ + private List dealHistoryData(List beforeDeal, int lineInterval) { + List result = new ArrayList<>(); + try { + if (CollectionUtils.isEmpty(beforeDeal)) { + return result; + } else { + //先将查询数据按日进行收集 + Map/*当前天的所有谐波数据*/> dayHistoryDatas = new HashMap<>(); + for (HarmData harmData : beforeDeal) { + Date time = PubUtils.instantToDate(harmData.getTime()); + String date = DateUtil.format(time, DatePattern.NORM_DATE_PATTERN); + if (dayHistoryDatas.containsKey(date)) { + Map harmDataMap = dayHistoryDatas.get(date); + harmDataMap.put(PubUtils.getSecondsAsZero(PubUtils.instantToDate(harmData.getTime())), harmData); + dayHistoryDatas.put(date, harmDataMap); + } else { + Map harmDataMap = new HashMap<>(); + harmDataMap.put(PubUtils.getSecondsAsZero(PubUtils.instantToDate(harmData.getTime())), harmData); + dayHistoryDatas.put(date, harmDataMap); + } + } + //将数据按日期处理后,开始进行完整性判断,满足完整性则进行补齐,否则返回空数据 + Set days = dayHistoryDatas.keySet(); + for (String day : days) { + //获取出当天的历史谐波数据 + Map harmDataMap = dayHistoryDatas.get(day); + if (CollectionUtils.isEmpty(harmDataMap)) { + continue; + } + int dueTimes = 1440 / lineInterval; + int realTimes = harmDataMap.size(); + double integrity = (double) realTimes / (double) dueTimes; + if (integrity < 0.9 || integrity >= 1.0) { + //完整性不足,则返回原数据 + Set dates = harmDataMap.keySet(); + for (Date time : dates) { + result.add(harmDataMap.get(time)); + } + } else if (integrity < 1.0) { + //进行数据补齐,数据补齐需要根据监测点测量间隔,最好是MAP格式 map的key是yyyy-MM-dd HH:mm + List afterDeal = new ArrayList<>(); + String timeTemp = day + " 00:00:00"; + Date date = DateUtil.parse(timeTemp, DatePattern.NORM_DATETIME_PATTERN); + for (int i = 0; i < dueTimes; i++) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.MINUTE, lineInterval * i); + HarmData temp = harmDataMap.get(calendar.getTime()); + if (temp != null && temp.getValue() != null) { + afterDeal.add(temp); + } else { + //递归找到前面的值 + Float preValue = getPreHarmValue(date, calendar.getTime(), harmDataMap, lineInterval); + //递归找到后面的值 + Float appendValue = getAppendHarmValue(date, calendar.getTime(), harmDataMap, lineInterval); + HarmData harmData = new HarmData(); + harmData.setTime(PubUtils.dateToInstant(calendar.getTime())); + //还需要判断前值和后值为空的情况 + if (null == preValue && null == appendValue) { + harmData.setValue(0.0f); + } else if (null == preValue) { + harmData.setValue(appendValue); + } else if (null == appendValue) { + harmData.setValue(preValue); + } else { + harmData.setValue((preValue + appendValue) / 2); + } + afterDeal.add(harmData); + } + } + result.addAll(afterDeal); + } + } + } + } catch (Exception e) { + log.error("开始处理历史电压谐波数据失败,失败原因:{}", e.toString()); + throw new BusinessException(AdvanceResponseEnum.INSUFFICIENCY_OF_INTEGRITY); + } + return result; + } + + /** + * 递归找前值 谐波数据 + * + * @param date 起始时间 + * @param time 当前事件 + * @param beforeDeal 处理前的数据 + */ + private Float getPreHarmValue(Date date, Date time, Map beforeDeal, int interval) { + Float result; + if (date.getTime() >= time.getTime()) { + return null; + } else { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(time); + interval = -interval; + calendar.add(Calendar.MINUTE, interval); + HarmData temp = beforeDeal.get(calendar.getTime()); + if (temp == null || temp.getValue() == null) { + result = getPreHarmValue(date, calendar.getTime(), beforeDeal, Math.abs(interval)); + } else { + result = temp.getValue(); + } + } + return result; + } + + + /** + * 递归找后置 谐波数据 + * + * @param date 起始时间 + * @param time 截止时间 + */ + private Float getAppendHarmValue(Date date, Date time, Map beforeDeal, int interval) { + Float result; + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.DAY_OF_MONTH, 1); + calendar.add(Calendar.MINUTE, -interval); + if (calendar.getTimeInMillis() <= time.getTime()) { + return null; + } else { + Calendar calendar1 = Calendar.getInstance(); + calendar1.setTime(time); + calendar1.add(Calendar.MINUTE, interval); + HarmData temp = beforeDeal.get(calendar1.getTime()); + if (temp == null || temp.getValue() == null) { + result = getAppendHarmValue(date, calendar1.getTime(), beforeDeal, interval); + } else { + result = temp.getValue(); + } + } + return result; + } + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/RmpEventDetailAssServiceImpl.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/RmpEventDetailAssServiceImpl.java new file mode 100644 index 0000000..9ffe70d --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/service/impl/RmpEventDetailAssServiceImpl.java @@ -0,0 +1,18 @@ +package com.njcn.product.advance.eventSource.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.advance.eventSource.mapper.RmpEventDetailAssMapper; +import com.njcn.product.advance.eventSource.pojo.po.RmpEventDetailAssPO; +import com.njcn.product.advance.eventSource.service.RmpEventDetailAssService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * @Author: cdf + * @CreateTime: 2025-09-04 + * @Description: + */ +@Service +@RequiredArgsConstructor +public class RmpEventDetailAssServiceImpl extends ServiceImpl implements RmpEventDetailAssService { +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/eventSource/utils/UtilNormalization.java b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/utils/UtilNormalization.java new file mode 100644 index 0000000..599f906 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/eventSource/utils/UtilNormalization.java @@ -0,0 +1,347 @@ +package com.njcn.product.advance.eventSource.utils; + + +import com.njcn.product.advance.eventSource.pojo.dto.eventAggregate.*; + +public class UtilNormalization { + public static void matrixcata_pro(EntityLogic[] transformer, EntityMtrans entityMtrans, int len) { + int i, j, k; + int node1, node2, con; + int src_node[] = new int[] { 0 }; + + // 连接方式转化为矩阵形式,行、列表示所有节点 + // inf表示两个节点不相连,0表示与自身相连,其他数值表示变压器连接类型 + // 将初始矩阵的元素设为inf,对角线元素设为0 + for (i = 0; i < FinalData.NODE_NUM; i++) { + for (j = 0; j < FinalData.NODE_NUM; j++) { + entityMtrans.getMtrans()[i][j] = FinalData.DATA_INF; + } + entityMtrans.getMtrans()[i][i] = 0; + } + // 根据transformer设置元素 + for (i = 0; i < len; i++) { + node1 = transformer[i].getNode_h(); + node2 = transformer[i].getNode_l(); + con = transformer[i].getType(); + entityMtrans.getMtrans()[node1 - 1][node2 - 1] = con; + entityMtrans.getMtrans()[node2 - 1][node1 - 1] = con; + } + StringBuilder str = new StringBuilder(); + for (i = 0; i < FinalData.NODE_NUM; i++) { + for (j = 0; j < FinalData.NODE_NUM; j++) { + str.append(entityMtrans.getMtrans()[i][j]).append(" "); + if (j == (FinalData.NODE_NUM - 1)) + str.append("\r\n"); + } + } + + // 类型匹配矩阵Matrixcata + // Matrixcata模式匹配矩阵,列为节点数,行为总类别数,元素为第一个节点分别是1-6类别情况下其他节点类别情况。 + // 元素1,2,3,4,5,6 分别对应 Dc,Cb,Da,Cc,Db,Ca + // 设置矩阵第一行元素 + for (i = 0; i < FinalData.NODE_NUM; i++) + entityMtrans.getMatrixcata0()[0][i] = 0; + for (i = 1; i < FinalData.NODE_NUM; i++) { + // 路径缓存清空 + for (j = 0; j < FinalData.MAX_PATH_NUM; j++) { + for (k = 0; k < (FinalData.NODE_NUM + 1); k++) + entityMtrans.getPossiable_path()[j][k] = 0; + } + entityMtrans.setPath_num(0); + // 寻找路径 + src_node[0] = 0; + findPath(entityMtrans, src_node, i, 0, 1, FinalData.NODE_NUM); + if (entityMtrans.getPath_num() != 0) + entityMtrans.getMatrixcata0()[0][i] = entityMtrans.getPossiable_path()[0][FinalData.NODE_NUM]; // 采用第一条路径 + else + entityMtrans.getMatrixcata0()[0][i] = FinalData.DATA_INF; // 找不到路径填大值表示不通 + } + // 构造矩阵其他行元素 + for (i = 1; i < FinalData.EVT_TYPE_NUM; i++) { + for (j = 0; j < FinalData.NODE_NUM; j++) + // EntityGroupData.Matrixcata0[i][j] = + // EntityGroupData.Matrixcata0[0][j] + i; + if (entityMtrans.getMatrixcata0()[0][j] == FinalData.DATA_INF) { + entityMtrans.getMatrixcata0()[i][j] = FinalData.DATA_INF; + } else { + entityMtrans.getMatrixcata0()[i][j] = entityMtrans.getMatrixcata0()[0][j] + i; + } + } + // 将数据归类到0-5 + for (i = 0; i < FinalData.EVT_TYPE_NUM; i++) { + for (j = 0; j < FinalData.NODE_NUM; j++) + entityMtrans.getMatrixcata1()[i][j] = entityMtrans.getMatrixcata0()[i][j] % 6; + } + // 0换成6,将数据归类到1-6 + for (i = 0; i < FinalData.EVT_TYPE_NUM; i++) { + for (j = 0; j < FinalData.NODE_NUM; j++) { + if (entityMtrans.getMatrixcata1()[i][j] == 0) + entityMtrans.getMatrixcata1()[i][j] = 6; + } + } + str.delete(0, str.length()); + for (i = 0; i < FinalData.EVT_TYPE_NUM; i++) { + for (j = 0; j < FinalData.NODE_NUM; j++) { + str.append(entityMtrans.getMatrixcata1()[i][j]).append(" "); + if (j == (FinalData.NODE_NUM - 1)) + str.append("\r\n"); + } + } + + } + + public static int findPath(EntityMtrans entityMtrans, int[] OriginalNode, int destination, int Weight, int src_num, int node_num) // 深度优先搜索 + { + int i, j; + int last_node; + int nextNodes[] = new int[FinalData.NODE_NUM]; + int nextNode_num = 0; + int nextNodes0[] = new int[FinalData.NODE_NUM]; + int nextNode_num0 = 0; + int tmpPath[] = new int[FinalData.NODE_NUM + 1]; + int tmpPath_num; + if (src_num < 1) // 源节点个数不对 + return 1; + last_node = OriginalNode[src_num - 1]; + if (last_node > node_num) // 判断最后一个节点号是否在范围内 + return 1; + for (i = 0; i < node_num; i++) { + // if((Mtrans[last_node][i]>0)&&(Mtrans[last_node][i] 0) // 寻找相同的节点 + { + nextNodes[nextNode_num] = i; + nextNode_num++; + } + } + // 如果一条路的最后一个节点就是目标节点,说明此路径是所有路径中的一条,可以直接return + if (last_node == destination) { + if (entityMtrans.getPath_num() >= FinalData.MAX_PATH_NUM) + return 1; + for (i = 0; i < src_num; i++) + entityMtrans.getPossiable_path()[entityMtrans.getPath_num()][i] = OriginalNode[i]; + entityMtrans.getPossiable_path()[entityMtrans.getPath_num()][FinalData.NODE_NUM] = Weight; // 最后一个节点填入变压器连接 + entityMtrans.setPath_num(entityMtrans.getPath_num() + 1); + } else { + for (i = 0; i < src_num; i++) { + if (destination == OriginalNode[i]) + return 1; + } + } + // 判断下一个节点有没有目的节点 + for (i = 0; i < nextNode_num; i++) { + if (nextNodes[i] == destination) { + // 先清零; + for (j = 0; j < (FinalData.NODE_NUM + 1); j++) + tmpPath[j] = 0; + // 填入源节点 + for (j = 0; j < src_num; j++) + tmpPath[j] = OriginalNode[j]; + tmpPath[src_num] = destination; // 目的节点加在后面 + tmpPath[FinalData.NODE_NUM] = Weight + entityMtrans.getMtrans()[last_node][destination]; // 最后一个点填入变压器累计 + tmpPath_num = src_num + 1; + if (entityMtrans.getPath_num() >= FinalData.MAX_PATH_NUM) + return 1; + for (j = 0; j < (FinalData.NODE_NUM + 1); j++) + entityMtrans.getPossiable_path()[entityMtrans.getPath_num()][j] = tmpPath[j]; // tmpPath为路径的路阻 + entityMtrans.setPath_num(entityMtrans.getPath_num() + 1); + nextNodes[i] = 0; + if (nextNode_num != 0) // if(nextNode_num) + nextNode_num--; + } else { + // 判断如果源节点中有下一个节点,不再寻找处理 + for (j = 0; j < src_num; j++) { + if (nextNodes[i] == OriginalNode[j]) { + nextNodes[i] = 0; + } + } + } + } + // 不是目的节点的下一节点继续寻找 + for (i = 0; i < nextNode_num; i++) { + if (nextNodes[i] != 0) { + nextNodes0[nextNode_num0] = nextNodes[i]; + nextNode_num0++; + } + } + for (i = 0; i < nextNode_num0; i++) { + // 填入源节点 + for (j = 0; j < src_num; j++) + tmpPath[j] = OriginalNode[j]; + tmpPath[src_num] = nextNodes0[i]; // 下一个节点加在后面 + tmpPath_num = src_num + 1; + findPath(entityMtrans, tmpPath, destination, (Weight + entityMtrans.getMtrans()[last_node][nextNodes0[i]]), tmpPath_num, + node_num); + } + return 0; + } + + public static int sort_Tstart(EntityGroupData buf) { + int res_num, out_num; + int idx = 0; + if ((buf == null) || (buf.getEvt_in_num() == 0)) + return 0; + res_num = buf.getEvt_in_num(); + while (res_num > 0) { // while(res_num) + out_num = sort_Tstart_single(buf); + // 输出缓冲填入归集缓冲 + // buf.getGrp_buf()[idx] = buf.getOut_buf(); + System.arraycopy(buf.getOut_buf(), 0, buf.getGrp_buf()[idx], 0, buf.getOut_buf().length); + buf.getGrp_num()[idx] = out_num; + // 未归集填入输入缓冲 + // buf.setIn_buf(buf.getRes_buf()); + System.arraycopy(buf.getRes_buf(), 0, buf.getIn_buf(), 0, buf.getRes_buf().length); + buf.setEvt_in_num(buf.getEvt_res_num()); + idx++; + if (idx >= FinalData.MAX_GROUP_NUM) // 分组超限 + break; + if (out_num <= res_num) + res_num = res_num - out_num; + else + break; // 分组数目超限 + } + buf.setGrp_all_num(idx); + return 1; + } + + public static int sort_Tstart_single(EntityGroupData buf) { + int i; + int start_time; + int thd_time1, thd_time2; + if ((buf == null) || (buf.getEvt_in_num() == 0)) + return 0; + buf.setEvt_out_num(0); + buf.setEvt_res_num(0); + // 如果只有一个事件直接赋值返回 + if (buf.getEvt_in_num() == 1) { + buf.setEvt_out_num(1); + // buf.getOut_buf()[0] = buf.getIn_buf()[0]; + // System.arraycopy(buf.getIn_buf()[0], 0, buf.getOut_buf()[0], 0, + // 1); + buf.getOut_buf()[0] = (EntityGroupEvtData) buf.getIn_buf()[0].objClone(); + return buf.getEvt_out_num(); + } + start_time = buf.getIn_buf()[0].getStart_time(); + thd_time1 = start_time - FinalData.TIME_THRESHOLD; + thd_time2 = start_time + FinalData.TIME_THRESHOLD; + // 判断时标阀值门槛归集 + for (i = 0; i < buf.getEvt_in_num(); i++) { + start_time = buf.getIn_buf()[i].getStart_time(); + // 在阈值范围内 + if ((start_time >= thd_time1) && (start_time <= thd_time2)) { + // buf.getOut_buf()[buf.getEvt_out_num()] = buf.getIn_buf()[i]; + // System.arraycopy(buf.getIn_buf()[i], 0, + // buf.getOut_buf()[buf.getEvt_out_num()], 0, 1); + buf.getOut_buf()[buf.getEvt_out_num()] = (EntityGroupEvtData) buf.getIn_buf()[i].objClone(); + buf.setEvt_out_num(buf.getEvt_out_num() + 1); + } else { + // buf.getRes_buf()[buf.getEvt_res_num()] = buf.getIn_buf()[i]; + // System.arraycopy(buf.getIn_buf()[i], 0, + // buf.getRes_buf()[buf.getEvt_res_num()], 0, 1); + buf.getRes_buf()[buf.getEvt_res_num()] = (EntityGroupEvtData) buf.getIn_buf()[i].objClone(); + buf.setEvt_res_num(buf.getEvt_res_num() + 1); + } + } + return buf.getEvt_out_num(); + } + + public static int sort_cata(EntityGroupData buf, int idx) { + int i, j; + int cata, node; + int odrer[] = new int[FinalData.MAX_CATA_NUM + 2]; + // 针对类别是1-6的数据进行模式匹配,并标注属于哪一个模式 + + for (i = 0; i < (FinalData.MAX_CATA_NUM + 2); i++) + odrer[i] = 0; + // 暂降类型转换 + // 将类型7,8,9转换为6,2,4 + // 其中7,8,9分别对应BC两相接地,AC两相接地,AB两相接地,1,2,3,4,5,6分别对应Dc,Cb,Da,Cc,Db,Ca + /* + * for (i = 0; i < buf.getGrp_num()[idx]; i++) { if + * (buf.getGrp_buf()[idx][i].getCata() == 7) + * buf.getGrp_buf()[idx][i].setCata(6); if + * (buf.getGrp_buf()[idx][i].getCata() == 8) + * buf.getGrp_buf()[idx][i].setCata(2); if + * (buf.getGrp_buf()[idx][i].getCata() == 9) + * buf.getGrp_buf()[idx][i].setCata(4); } + */ + for (i = 0; i < buf.getGrp_num()[idx]; i++) { + /* + * if (buf.getGrp_buf()[idx][i].getCata() == 10) //事件类型未知 + * buf.getGrp_buf()[idx][i].setCata(11); if + * (buf.getGrp_buf()[idx][i].getCata() == 9) //三相 + * buf.getGrp_buf()[idx][i].setCata(10); + */ + + if (buf.getGrp_buf()[idx][i].getCata() == 0) + buf.getGrp_buf()[idx][i].setCata(6); + + if (buf.getGrp_buf()[idx][i].getCata() == 6) + buf.getGrp_buf()[idx][i].setCata(6); + if (buf.getGrp_buf()[idx][i].getCata() == 7) + buf.getGrp_buf()[idx][i].setCata(2); + if (buf.getGrp_buf()[idx][i].getCata() == 8) + buf.getGrp_buf()[idx][i].setCata(4); + } + + // 将数据进行模式匹配,并标注属于哪一个模式 + for (i = 0; i < buf.getGrp_num()[idx]; i++) { + cata = buf.getGrp_buf()[idx][i].getCata(); + node = buf.getGrp_buf()[idx][i].getNode(); + + if ((node > FinalData.NODE_NUM) || (buf.getMatrixcata()[0][node - 1] == FinalData.DATA_INF)) { + buf.getGrp_buf()[idx][i].setCata2(FinalData.QVVR_TYPE_OUTOFRANGE); + // buf.getGrp_cata_buf()[idx][FinalData.MAX_CATA_NUM + + // 1][odrer[FinalData.MAX_CATA_NUM + 1]] = + // buf.getGrp_buf()[idx][i]; + // System.arraycopy(buf.getGrp_buf()[idx][i], 0, + // buf.getGrp_cata_buf()[idx][FinalData.MAX_CATA_NUM + + // 1][odrer[FinalData.MAX_CATA_NUM + 1]], 0, 1); + buf.getGrp_cata_buf()[idx][FinalData.MAX_CATA_NUM + 1][odrer[FinalData.MAX_CATA_NUM + + 1]] = (EntityGroupEvtData) buf.getGrp_buf()[idx][i].objClone(); + odrer[FinalData.MAX_CATA_NUM + 1]++; + } else if (cata == FinalData.QVVR_TYPE_UNKNOWN) { + buf.getGrp_buf()[idx][i].setCata2(FinalData.QVVR_TYPE_UNKNOWN); + // buf.getGrp_cata_buf()[idx][FinalData.MAX_CATA_NUM][odrer[FinalData.MAX_CATA_NUM]] + // = buf.getGrp_buf()[idx][i]; + // System.arraycopy(buf.getGrp_buf()[idx][i], 0, + // buf.getGrp_cata_buf()[idx][FinalData.MAX_CATA_NUM][odrer[FinalData.MAX_CATA_NUM]], + // 0, 1); + buf.getGrp_cata_buf()[idx][FinalData.MAX_CATA_NUM][odrer[FinalData.MAX_CATA_NUM]] = (EntityGroupEvtData) buf + .getGrp_buf()[idx][i].objClone(); + odrer[FinalData.MAX_CATA_NUM]++; + } else if (cata == FinalData.QVVR_TYPE_THREE) // ÈıÏàÔݽµ¹éÀà + { + buf.getGrp_buf()[idx][i].setCata2(FinalData.QVVR_TYPE_THREE); + // buf.getGrp_cata_buf()[idx][FinalData.MAX_CATA_NUM - + // 1][odrer[FinalData.MAX_CATA_NUM - 1]] = + // buf.getGrp_buf()[idx][i]; + // System.arraycopy(buf.getGrp_buf()[idx][i], 0, + // buf.getGrp_cata_buf()[idx][FinalData.MAX_CATA_NUM - + // 1][odrer[FinalData.MAX_CATA_NUM - 1]], 0, 1); + buf.getGrp_cata_buf()[idx][FinalData.MAX_CATA_NUM - 1][odrer[FinalData.MAX_CATA_NUM + - 1]] = (EntityGroupEvtData) buf.getGrp_buf()[idx][i].objClone(); + odrer[FinalData.MAX_CATA_NUM - 1]++; + } else // 1-6类暂降归类 + { + for (j = 0; j < FinalData.MAX_CATA_NUM; j++) { + if (cata == buf.getMatrixcata()[j][node - 1])// 判断数据类别属于第几行 + { + buf.getGrp_buf()[idx][i].setCata2(j + 1); + // 进行归类 + // buf.getGrp_cata_buf()[idx][j][odrer[j]] = + // buf.getGrp_buf()[idx][i]; + // System.arraycopy(buf.getGrp_buf()[idx][i], 0, + // buf.getGrp_cata_buf()[idx][j][odrer[j]], 0, 1); + buf.getGrp_cata_buf()[idx][j][odrer[j]] = (EntityGroupEvtData) buf.getGrp_buf()[idx][i] + .objClone(); + odrer[j]++; + } + } + } + } + for (i = 0; i < FinalData.MAX_CATA_NUM + 2; i++) + buf.getGrp_cata_num()[idx][i] = odrer[i]; + return 0; + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/controller/HarmonicUpController.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/controller/HarmonicUpController.java new file mode 100644 index 0000000..2c0825f --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/controller/HarmonicUpController.java @@ -0,0 +1,91 @@ +package com.njcn.product.advance.harmonicUp.controller; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.advance.harmonicUp.pojo.po.UpHarmonicDetail; +import com.njcn.product.advance.harmonicUp.pojo.vo.UpTableInfo; +import com.njcn.product.advance.harmonicUp.service.HarmonicUpService; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.LargeScreenCountParam; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @Author: cdf + * @CreateTime: 2025-09-12 + * @Description: + */ +@Slf4j +@RestController +@RequestMapping("harmonicUp") +@Api(tags = "谐波放大") +@RequiredArgsConstructor +public class HarmonicUpController extends BaseController { + + private final HarmonicUpService harmonicUpService; + + @GetMapping("analyzePreData") + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("谐波放大算法预处理") + public HttpResult analyzePreData(@RequestParam("date")String date) { + String methodDescribe = getMethodDescribe("analyzePreData"); + harmonicUpService.analyzePreData(date); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + + @PostMapping("getDetail") + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("获取监测点谐波放大详情") + public HttpResult> getDetail(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("getDetail"); + checkParam(param); + Page result = harmonicUpService.getDetail(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @PostMapping("getInfoList") + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("谐波放大实时数据列表") + public HttpResult> getInfoList(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("getInfoList"); + Page result = harmonicUpService.getInfoList(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + @PostMapping("tableInfo") + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("谐波放大热力图") + public HttpResult tableInfo(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("tableInfo"); + UpTableInfo result = harmonicUpService.tableInfo(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + + + private void checkParam(LargeScreenCountParam param){ + if(StrUtil.isBlank(param.getSearchBeginTime())){ + throw new BusinessException(CommonResponseEnum.FAIL,"时间不可为空!"); + } + if(StrUtil.isBlank(param.getLineId())){ + throw new BusinessException(CommonResponseEnum.FAIL,"监测点id不可为空!"); + } + /* if(StrUtil.isBlank(param.getDeptId())){ + throw new BusinessException(CommonResponseEnum.FAIL,"部门id不可为空!"); + }*/ + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/imapper/DataIUpToMapper.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/imapper/DataIUpToMapper.java new file mode 100644 index 0000000..91b6969 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/imapper/DataIUpToMapper.java @@ -0,0 +1,16 @@ +package com.njcn.product.advance.harmonicUp.imapper; + +import com.njcn.influx.base.InfluxDbBaseMapper; +import com.njcn.product.advance.harmonicUp.pojo.po.DataIUp; + + +/** + * @author hongawen + * @version 1.0 + * @data 2024/11/7 18:49 + */ +public interface DataIUpToMapper extends InfluxDbBaseMapper { + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/imapper/DataVUpToMapper.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/imapper/DataVUpToMapper.java new file mode 100644 index 0000000..2d4b616 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/imapper/DataVUpToMapper.java @@ -0,0 +1,18 @@ +package com.njcn.product.advance.harmonicUp.imapper; + + +import com.njcn.influx.base.InfluxDbBaseMapper; +import com.njcn.product.advance.harmonicUp.pojo.po.DataVUp; + +/** + * @author hongawen + * @version 1.0 + * @data 2024/11/7 18:49 + */ +public interface DataVUpToMapper extends InfluxDbBaseMapper { + + + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/mapper/UpHarmonicDetailMapper.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/mapper/UpHarmonicDetailMapper.java new file mode 100644 index 0000000..d8c7a24 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/mapper/UpHarmonicDetailMapper.java @@ -0,0 +1,22 @@ +package com.njcn.product.advance.harmonicUp.mapper; + +/** + * @Author: cdf + * @CreateTime: 2025-09-12 + * @Description: + */ + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.advance.harmonicUp.pojo.po.UpHarmonicDetail; + +/** + *

+ * 谐波放大详情表 Mapper 接口 + *

+ * + * @author + * @since 2025-09-12 + */ +public interface UpHarmonicDetailMapper extends BaseMapper { + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/param/HistoryParam.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/param/HistoryParam.java new file mode 100644 index 0000000..370e611 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/param/HistoryParam.java @@ -0,0 +1,45 @@ +package com.njcn.product.advance.harmonicUp.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.product.advance.eventSource.pojo.constant.HarmonicValidMessage; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +/** + * @author denghuajun + * @date 2022/3/11 + * + */ +@Data +public class HistoryParam { + @ApiModelProperty("开始时间") + @NotBlank(message = HarmonicValidMessage.DATA_NOT_BLANK) + private String searchBeginTime; + + @ApiModelProperty("结束时间") + @NotBlank(message = HarmonicValidMessage.DATA_NOT_BLANK) + private String searchEndTime; + + @ApiModelProperty("监测点id集合") + private String[] lineId; + + @ApiModelProperty("指标集合") + private String[] condition; + + @ApiModelProperty("谐波次数") + private Integer harmonic; + + @ApiModelProperty("间谐波次数") + private Integer inHarmonic; + + @ApiModelProperty("类型(1-平均值;2-最小值;3-最大值;4-CP95值)") + private Integer valueType; + + @ApiModelProperty("接线方式") + @NotNull(message = "接线方式不可为空") + private Integer ptType; +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/DataIUp.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/DataIUp.java new file mode 100644 index 0000000..46f421e --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/DataIUp.java @@ -0,0 +1,194 @@ +package com.njcn.product.advance.harmonicUp.pojo.po; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.njcn.influx.utils.InstantDateSerializer; +import lombok.Data; +import org.influxdb.annotation.Column; +import org.influxdb.annotation.Measurement; +import org.influxdb.annotation.TimeColumn; + +import java.time.Instant; + + +/** + * 类的介绍: + * + */ +@Data +@Measurement(name = "data_i_up") +public class DataIUp { + + @Column(name = "time",tag =true) + @JsonSerialize(using = InstantDateSerializer.class) + @TimeColumn + private Instant time; + + @Column(name = "line_id",tag = true) + private String lineId; + + @Column(name = "phasic_type",tag = true) + private String phasicType; + + @Column(name = "quality_flag",tag = true) + private String qualityFlag="0"; + + @Column(name = "value_type",tag = true) + private String valueType; + + //是否是异常指标数据,0否1是 + @Column(name = "abnormal_flag") + private Integer abnormalFlag; + + + @Column(name = "i_1") + private Double i1; + + @Column(name = "i_2") + private Double i2; + + @Column(name = "i_3") + private Double i3; + + @Column(name = "i_4") + private Double i4; + + @Column(name = "i_5") + private Double i5; + + @Column(name = "i_6") + private Double i6; + + @Column(name = "i_7") + private Double i7; + + @Column(name = "i_8") + private Double i8; + + @Column(name = "i_9") + private Double i9; + + @Column(name = "i_10") + private Double i10; + + @Column(name = "i_11") + private Double i11; + + @Column(name = "i_12") + private Double i12; + + @Column(name = "i_13") + private Double i13; + + @Column(name = "i_14") + private Double i14; + + @Column(name = "i_15") + private Double i15; + + @Column(name = "i_16") + private Double i16; + + @Column(name = "i_17") + private Double i17; + + @Column(name = "i_18") + private Double i18; + + @Column(name = "i_19") + private Double i19; + + @Column(name = "i_20") + private Double i20; + + @Column(name = "i_21") + private Double i21; + + @Column(name = "i_22") + private Double i22; + + @Column(name = "i_23") + private Double i23; + + @Column(name = "i_24") + private Double i24; + + @Column(name = "i_25") + private Double i25; + + @Column(name = "i_26") + private Double i26; + + @Column(name = "i_27") + private Double i27; + + @Column(name = "i_28") + private Double i28; + + @Column(name = "i_29") + private Double i29; + + @Column(name = "i_30") + private Double i30; + + @Column(name = "i_31") + private Double i31; + + @Column(name = "i_32") + private Double i32; + + @Column(name = "i_33") + private Double i33; + + @Column(name = "i_34") + private Double i34; + + @Column(name = "i_35") + private Double i35; + + @Column(name = "i_36") + private Double i36; + + @Column(name = "i_37") + private Double i37; + + @Column(name = "i_38") + private Double i38; + + @Column(name = "i_39") + private Double i39; + + @Column(name = "i_40") + private Double i40; + + @Column(name = "i_41") + private Double i41; + + @Column(name = "i_42") + private Double i42; + + @Column(name = "i_43") + private Double i43; + + @Column(name = "i_44") + private Double i44; + + @Column(name = "i_45") + private Double i45; + + @Column(name = "i_46") + private Double i46; + + @Column(name = "i_47") + private Double i47; + + @Column(name = "i_48") + private Double i48; + + @Column(name = "i_49") + private Double i49; + + @Column(name = "i_50") + private Double i50; + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/DataVUp.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/DataVUp.java new file mode 100644 index 0000000..50530c1 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/DataVUp.java @@ -0,0 +1,200 @@ +package com.njcn.product.advance.harmonicUp.pojo.po; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.njcn.influx.utils.InstantDateSerializer; +import lombok.Data; +import org.influxdb.annotation.Column; +import org.influxdb.annotation.Measurement; +import org.influxdb.annotation.TimeColumn; +import org.springframework.beans.BeanUtils; + +import java.time.Instant; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 类的介绍: + * + */ +@Data +@Measurement(name = "data_v_up") +public class DataVUp { + + @TimeColumn + @Column(name = "time", tag = true) + @JsonSerialize(using = InstantDateSerializer.class) + private Instant time; + + @Column(name = "line_id", tag = true) + private String lineId; + + @Column(name = "phasic_type", tag = true) + private String phasicType; + + @Column(name = "value_type", tag = true) + private String valueType; + + @Column(name = "quality_flag", tag = true) + private String qualityFlag="0"; + + //是否是异常指标数据,0否1是 + @Column(name = "abnormal_flag") + private Integer abnormalFlag; + + @Column(name = "v_1") + private Double v1; + + @Column(name = "v_2") + private Double v2; + + @Column(name = "v_3") + private Double v3; + + @Column(name = "v_4") + private Double v4; + + @Column(name = "v_5") + private Double v5; + + @Column(name = "v_6") + private Double v6; + + @Column(name = "v_7") + private Double v7; + + @Column(name = "v_8") + private Double v8; + + @Column(name = "v_9") + private Double v9; + + @Column(name = "v_10") + private Double v10; + + @Column(name = "v_11") + private Double v11; + + @Column(name = "v_12") + private Double v12; + + @Column(name = "v_13") + private Double v13; + + @Column(name = "v_14") + private Double v14; + + @Column(name = "v_15") + private Double v15; + + @Column(name = "v_16") + private Double v16; + + @Column(name = "v_17") + private Double v17; + + @Column(name = "v_18") + private Double v18; + + @Column(name = "v_19") + private Double v19; + + @Column(name = "v_20") + private Double v20; + + @Column(name = "v_21") + private Double v21; + + @Column(name = "v_22") + private Double v22; + + @Column(name = "v_23") + private Double v23; + + @Column(name = "v_24") + private Double v24; + + @Column(name = "v_25") + private Double v25; + + @Column(name = "v_26") + private Double v26; + + @Column(name = "v_27") + private Double v27; + + @Column(name = "v_28") + private Double v28; + + @Column(name = "v_29") + private Double v29; + + @Column(name = "v_30") + private Double v30; + + @Column(name = "v_31") + private Double v31; + + @Column(name = "v_32") + private Double v32; + + @Column(name = "v_33") + private Double v33; + + @Column(name = "v_34") + private Double v34; + + @Column(name = "v_35") + private Double v35; + + @Column(name = "v_36") + private Double v36; + + @Column(name = "v_37") + private Double v37; + + @Column(name = "v_38") + private Double v38; + + @Column(name = "v_39") + private Double v39; + + @Column(name = "v_40") + private Double v40; + + @Column(name = "v_41") + private Double v41; + + @Column(name = "v_42") + private Double v42; + + @Column(name = "v_43") + private Double v43; + + @Column(name = "v_44") + private Double v44; + + @Column(name = "v_45") + private Double v45; + + @Column(name = "v_46") + private Double v46; + + @Column(name = "v_47") + private Double v47; + + @Column(name = "v_48") + private Double v48; + + @Column(name = "v_49") + private Double v49; + + @Column(name = "v_50") + private Double v50; + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/UpHarmonicDetail.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/UpHarmonicDetail.java new file mode 100644 index 0000000..c540e1b --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/po/UpHarmonicDetail.java @@ -0,0 +1,110 @@ +package com.njcn.product.advance.harmonicUp.pojo.po; + +/** + * @Author: cdf + * @CreateTime: 2025-09-12 + * @Description: + */ + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + *

+ * 谐波放大详情表 + *

+ * + * @author + * @since 2025-09-12 + */ +@Data +@TableName("up_harmonic_detail") +public class UpHarmonicDetail implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 事件id + */ + @TableField(value = "id") + private String id; + + /** + * 监测点id + */ + @TableField("monitor_id") + private String monitorId; + + /** + * 开始时间 + */ + @TableField("start_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + /** + * 结束时间 + */ + @TableField("end_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + /** + * 谐波放大持续时间s + */ + @TableField("duration") + private Double duration; + + /** + * 谐波次数 + */ + @TableField("harmonic_count") + private Integer harmonicCount; + + /** + * 相别 + */ + @TableField("phase") + private String phase; + + /** + * 电压标准值 + */ + @TableField("v_Avg_Value") + private Double vAvgValue; + + /** + * 电流标准值 + */ + + @TableField("i_Avg_Value") + private Double iAvgValue; + + @TableField("up_scheme") + private String upScheme; + + /** + * 创建时间 + */ + @TableField("create_time") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + + @TableField(exist = false) + private String stationName; + @TableField(exist = false) + private String monitorName; + @TableField(exist = false) + private String objName; + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/HistoryDataResultVO.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/HistoryDataResultVO.java new file mode 100644 index 0000000..1509bf9 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/HistoryDataResultVO.java @@ -0,0 +1,55 @@ +package com.njcn.product.advance.harmonicUp.pojo.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @author denghuajun + * @date 2022/3/11 + * + */ +@Data +public class HistoryDataResultVO implements Serializable { + + @ApiModelProperty("监测点名称") + private String lineName; + + @ApiModelProperty("指标名称") + private String targetName; + + @ApiModelProperty("相别") + private List phaiscType; + + @ApiModelProperty("单位") + private List unit; + + @ApiModelProperty("谐波次数") + private Integer harmNum; + + @ApiModelProperty("数值") + private List> value; + + @ApiModelProperty("最小值") + private Float minValue; + + + @ApiModelProperty("最大值") + private Float maxValue; + + @ApiModelProperty("上限") + private Float topLimit; + + @ApiModelProperty("下限") + private Float lowerLimit; + + @ApiModelProperty("接线方式 0.星型 1.星三角 2.三角") + private String wiringMethod; + + /* @ApiModelProperty("暂降事件详情") + private List eventDetail;*/ + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/QueryResultLimitVO.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/QueryResultLimitVO.java new file mode 100644 index 0000000..ddd3528 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/QueryResultLimitVO.java @@ -0,0 +1,30 @@ +package com.njcn.product.advance.harmonicUp.pojo.vo; + +import com.njcn.influx.pojo.bo.HarmonicHistoryData; +import lombok.Data; +import org.influxdb.dto.QueryResult; + +import java.io.Serializable; +import java.util.List; + +/** + * @author denghuajun + * @date 2022/3/15 + * 存值 + */ +@Data +public class QueryResultLimitVO implements Serializable { + private QueryResult queryResult; + private List harmonicHistoryDataList; + private Float topLimit; + private Float lowerLimit; + private String lineName; + private String targetName; + private List phaiscType; + private List unit; + private Integer harmNum; + /** + * 接线方式 0.星型 1.星三角 2.三角 + */ + private String wiringMethod; +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/UpTableInfo.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/UpTableInfo.java new file mode 100644 index 0000000..31c19cb --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/pojo/vo/UpTableInfo.java @@ -0,0 +1,32 @@ +package com.njcn.product.advance.harmonicUp.pojo.vo; + +import lombok.Data; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-09-15 + * @Description: + */ +@Data +public class UpTableInfo { + + private List date; + + private List monitorList; + + private List inner; + + + @Data + public static class Inner{ + private String date; + + private String lineId; + + private String monitorName; + + private Integer count = 0; + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/HarmonicUpService.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/HarmonicUpService.java new file mode 100644 index 0000000..cbeb32f --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/HarmonicUpService.java @@ -0,0 +1,28 @@ +package com.njcn.product.advance.harmonicUp.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.advance.harmonicUp.pojo.po.UpHarmonicDetail; +import com.njcn.product.advance.harmonicUp.pojo.vo.UpTableInfo; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.LargeScreenCountParam; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-09-11 + * @Description: 谐波放大 + */ +public interface HarmonicUpService extends IService { + + void analyzePreData(String date); + + Page getDetail(LargeScreenCountParam param); + + Page getInfoList(LargeScreenCountParam param); + + + UpTableInfo tableInfo(LargeScreenCountParam param); + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/HistoryResultService.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/HistoryResultService.java new file mode 100644 index 0000000..5205435 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/HistoryResultService.java @@ -0,0 +1,27 @@ +package com.njcn.product.advance.harmonicUp.service; + +import com.njcn.common.pojo.param.StatisticsBizBaseParam; + +import com.njcn.influx.pojo.dto.HarmHistoryDataDTO; +import com.njcn.product.advance.harmonicUp.pojo.param.HistoryParam; +import com.njcn.product.advance.harmonicUp.pojo.vo.HistoryDataResultVO; + +import java.util.List; + +/** + * 稳态数据 + * @author denghuajun + * @date 2022/3/14 + * + */ +public interface HistoryResultService { + + /** + * 稳态数据分析 + * @param historyParam 参数 + * @return 结果 + */ + List getHistoryResult(HistoryParam historyParam); + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/impl/HarmonicUpServiceImpl.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/impl/HarmonicUpServiceImpl.java new file mode 100644 index 0000000..64222c0 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/impl/HarmonicUpServiceImpl.java @@ -0,0 +1,719 @@ +package com.njcn.product.advance.harmonicUp.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.*; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.influx.imapper.DataIMapper; +import com.njcn.influx.imapper.DataVMapper; +import com.njcn.influx.pojo.constant.InfluxDBTableConstant; +import com.njcn.influx.pojo.po.DataI; +import com.njcn.influx.pojo.po.DataV; +import com.njcn.influx.query.InfluxQueryWrapper; +import com.njcn.product.advance.harmonicUp.imapper.DataIUpToMapper; +import com.njcn.product.advance.harmonicUp.imapper.DataVUpToMapper; +import com.njcn.product.advance.harmonicUp.mapper.UpHarmonicDetailMapper; +import com.njcn.product.advance.harmonicUp.pojo.po.DataIUp; +import com.njcn.product.advance.harmonicUp.pojo.po.DataVUp; +import com.njcn.product.advance.harmonicUp.pojo.po.UpHarmonicDetail; +import com.njcn.product.advance.harmonicUp.pojo.vo.UpTableInfo; +import com.njcn.product.advance.harmonicUp.service.HarmonicUpService; +import com.njcn.product.terminal.mysqlTerminal.mapper.LedgerScaleMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerBaseInfo; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.LargeScreenCountParam; +import com.njcn.product.terminal.mysqlTerminal.service.CommGeneralService; +import com.njcn.web.factory.PageFactory; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @Author: cdf + * @CreateTime: 2025-09-11 + * @Description: + */ +/** + * @Author: cdf + * @CreateTime: 2025-09-11 + * @Description: 谐波分析服务实现类,仅使用up_harmonic_detail表存储事件信息 + */ +@Service +@EnableScheduling +@RequiredArgsConstructor +@Slf4j +public class HarmonicUpServiceImpl extends ServiceImpl implements HarmonicUpService { + + private final DataVMapper dataVMapper; + private final DataIMapper dataIMapper; + private final DataVUpToMapper dataVUpToMapper; + private final DataIUpToMapper dataIUpToMapper; + private final CommGeneralService commGeneralService; + private final LedgerScaleMapper ledgerScaleMapper; + + // 常量定义 + final String DAY_FORMAT = DatePattern.NORM_DATE_PATTERN; + final String MONTH_FORMAT = DatePattern.NORM_MONTH_PATTERN; + + // 配置参数 + @Value("${harmonic.voltage.change.rate.threshold:0.20}") + private Double voltageChangeRateThreshold; + + //电流 + @Value("${harmonic.current.change.rate.threshold:0.10}") + private Double currentChangeRateThreshold; + + @Value("${harmonic.continuous.anomaly.count:10}") + private Integer continuousAnomalyCount; + + // 需要分析的特定谐波次数,可根据需求扩展 + private List specificHarmonicOrders = Arrays.asList(3, 5, 7, 11, 13); + + /** + * 每日凌晨2点执行谐波分析(分析前一天的数据) + */ + @Scheduled(cron = "0 0 2 * * ?") + public void dailyHarmonicAnalysis() { + analyzePreData(""); + } + + @Override + public void analyzePreData(String date) { + log.info("开始执行谐波分析,日期: {}", date); + + Map> avgVMap = new HashMap<>(); + Map> avgIMap = new HashMap<>(); + + // 计算并保存电压和电流变化率数据 + List dataVUpList = calculateAndSaveDataVUp(date,avgVMap); + List dataIUpList = calculateAndSaveDataIUp(date,avgIMap); + + // 分析谐波放大事件并直接保存到up_harmonic_detail表 + analyzeAndSaveHarmonicEvents(dataVUpList, dataIUpList,avgVMap,avgIMap); + + log.info("谐波分析执行完成,日期: {}", date); + } + + @Override + public Page getDetail(LargeScreenCountParam param) { + Page resultPage = new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)); + DateTime date; + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + + try { + date = DateUtil.parse(param.getSearchBeginTime(),DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)); + lambdaQueryWrapper.between(UpHarmonicDetail::getStartTime,DateUtil.beginOfDay(date),DateUtil.endOfDay(date)); + + }catch (Exception e){ + date = DateUtil.parse(param.getSearchBeginTime(),DateTimeFormatter.ofPattern(DatePattern.NORM_MONTH_PATTERN)); + lambdaQueryWrapper.between(UpHarmonicDetail::getStartTime,DateUtil.beginOfMonth(date),DateUtil.endOfMonth(date)); + } + lambdaQueryWrapper.eq(UpHarmonicDetail::getMonitorId,param.getLineId()).orderByAsc(UpHarmonicDetail::getHarmonicCount,UpHarmonicDetail::getPhase,UpHarmonicDetail::getStartTime); + Page result = this.page(new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)),lambdaQueryWrapper); + if(CollUtil.isEmpty(result.getRecords())){ + return resultPage; + } + List ledgerBaseInfoList = ledgerScaleMapper.getLedgerBaseInfo(Stream.of(param.getLineId()).collect(Collectors.toList())); + if(CollUtil.isEmpty(ledgerBaseInfoList)){ + throw new BusinessException(CommonResponseEnum.FAIL,"查询台账为空"); + } + LedgerBaseInfo ledgerBaseInfo = ledgerBaseInfoList.get(0); + result.getRecords().forEach(it->{ + it.setMonitorName(ledgerBaseInfo.getLineName()); + it.setStationName(ledgerBaseInfo.getStationName()); + it.setObjName(ledgerBaseInfo.getObjName()); + it.setVAvgValue(BigDecimal.valueOf(it.getVAvgValue()*1000).setScale(2,RoundingMode.HALF_UP).doubleValue()); + + }); + return result; + } + + @Override + public Page getInfoList(LargeScreenCountParam param) { + Page result = new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)); + + DateTime start = DateUtil.beginOfDay(DateUtil.parse(param.getSearchBeginTime())); + DateTime end = DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime())); + List lineIds = commGeneralService.getRunLineIdsByDept(param.getDeptId()); + if(CollUtil.isEmpty(lineIds)){ + return result; + } + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.between(UpHarmonicDetail::getStartTime,start,end) + .in(UpHarmonicDetail::getMonitorId,lineIds).orderByDesc(UpHarmonicDetail::getStartTime); + result = this.page(new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)),lambdaQueryWrapper); + if(CollUtil.isEmpty(result.getRecords())){ + return result; + } + List ids = result.getRecords().stream().map(UpHarmonicDetail::getMonitorId).distinct().collect(Collectors.toList()); + List ledgerBaseInfoList = ledgerScaleMapper.getLedgerBaseInfo(ids); + if(CollUtil.isEmpty(ledgerBaseInfoList)){ + throw new BusinessException(CommonResponseEnum.FAIL,"查询台账为空"); + } + Map ledgerBaseInfoMap = ledgerBaseInfoList.stream().collect(Collectors.toMap(LedgerBaseInfo::getLineId,line->line)); + result.getRecords().forEach(it->{ + LedgerBaseInfo ledgerBaseInfo = ledgerBaseInfoMap.get(it.getMonitorId()); + it.setMonitorName(ledgerBaseInfo.getLineName()); + it.setStationName(ledgerBaseInfo.getStationName()); + it.setObjName(ledgerBaseInfo.getObjName()); + it.setVAvgValue(BigDecimal.valueOf(it.getVAvgValue()*1000).setScale(2,RoundingMode.HALF_UP).doubleValue()); + }); + return result; + } + + @Override + public UpTableInfo tableInfo(LargeScreenCountParam param) { + // 初始化结果对象 + UpTableInfo result = new UpTableInfo(); + List dateList = new ArrayList<>(); + List innerList = new ArrayList<>(); + + // 1. 获取线路ID和台账基础信息 + List lineIds = commGeneralService.getRunLineIdsByDept(param.getDeptId()); + List ledgerBaseInfoList = ledgerScaleMapper.getLedgerBaseInfo(lineIds); + + if (CollUtil.isEmpty(ledgerBaseInfoList)) { + throw new BusinessException(CommonResponseEnum.FAIL, "查询台账为空"); + } + + // 2. 构建线路ID到名称的映射 & 提取线路名称列表 + Map ledgerBaseInfoMap = ledgerBaseInfoList.stream() + .collect(Collectors.toMap(LedgerBaseInfo::getLineId, Function.identity())); + List ledgerList = ledgerBaseInfoList.stream().map(it-> { + if(StrUtil.isBlank(it.getObjName())){ + return it.getLineName(); + } + return strTranslate(it.getObjName()); + + }).collect(Collectors.toList()); + + // 3. 处理日期范围 + DateTime start = DateUtil.beginOfDay(DateUtil.parse(param.getSearchBeginTime())); + DateTime end = DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime())); + long dayDiff = DateUtil.betweenDay(start, end, false); + + // 确定日期范围和格式 + DateRange dateRange; + String dateFormat; + if (dayDiff <= 31) { + dateRange = DateUtil.range(start, end, DateField.DAY_OF_MONTH); + dateFormat = DAY_FORMAT; + } else { + dateRange = DateUtil.range(start, end, DateField.MONTH); + dateFormat = MONTH_FORMAT; + } + + // 4. 查询数据 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.between(UpHarmonicDetail::getStartTime, start, end) + .in(UpHarmonicDetail::getMonitorId, lineIds) + .orderByDesc(UpHarmonicDetail::getStartTime); + List data = this.list(queryWrapper); + + // 5. 处理数据(分空数据和非空数据情况) + if (CollUtil.isEmpty(data)) { + // 无数据时填充默认值 + fillDefaultData(dateRange, dateFormat, ledgerBaseInfoList, dateList, innerList); + } else { + // 有数据时按日期和线路分组统计 + processExistingData(dateRange, dateFormat, ledgerBaseInfoMap, data, dateList, innerList); + } + + // 6. 设置结果 + result.setDate(dateList); + result.setMonitorList(ledgerList); + result.setInner(innerList); + return result; + } + + /** + * 填充默认数据(当查询结果为空时) + */ + private void fillDefaultData(DateRange dateRange, String dateFormat, + List ledgerList, List dateList, + List innerList) { + for (DateTime range : dateRange) { + String time = DateUtil.format(range, dateFormat); + dateList.add(time); + + ledgerList.forEach(line -> { + UpTableInfo.Inner inner = new UpTableInfo.Inner(); + inner.setDate(time); + inner.setMonitorName(StrUtil.isNotBlank(line.getObjName())?strTranslate(line.getObjName()):line.getLineName()); + inner.setLineId(line.getLineId()); + inner.setCount(0); + innerList.add(inner); + }); + } + } + + /** + * 处理查询到的数据 + */ + private void processExistingData(DateRange dateRange, String dateFormat, + Map ledgerBaseInfoMap, + List data, + List dateList, + List innerList) { + // 按监控ID分组 + Map> upMonitorMap = data.stream() + .collect(Collectors.groupingBy(UpHarmonicDetail::getMonitorId)); + + Map temMap = ObjectUtil.cloneByStream(ledgerBaseInfoMap); + + for (DateTime range : dateRange) { + String time = DateUtil.format(range, dateFormat); + dateList.add(time); + + upMonitorMap.forEach((lineKey, details) -> { + temMap.remove(lineKey); + UpTableInfo.Inner inner = new UpTableInfo.Inner(); + inner.setDate(time); + if(ledgerBaseInfoMap.containsKey(lineKey)){ + LedgerBaseInfo ledgerBaseInfo = ledgerBaseInfoMap.get(lineKey); + inner.setMonitorName(StrUtil.isNotBlank(ledgerBaseInfo.getObjName())?strTranslate(ledgerBaseInfo.getObjName()):ledgerBaseInfo.getLineName()); + inner.setLineId(ledgerBaseInfo.getLineId()); + } + // 统计当前日期下的记录数 + long count = details.stream() + .filter(detail -> DateUtil.format(detail.getStartTime(),dateFormat).equals(time)) + .count(); + inner.setCount((int) count); + innerList.add(inner); + }); + + //针对未发生谐波放大的测点 + temMap.forEach((k,v)->{ + UpTableInfo.Inner inner = new UpTableInfo.Inner(); + inner.setDate(time); + inner.setMonitorName(StrUtil.isNotBlank(v.getObjName())?strTranslate(v.getObjName()):v.getLineName()); + inner.setLineId(v.getLineId()); + inner.setCount(0); + innerList.add(inner); + }); + } + } + + + private String strTranslate(String str){ + return str.replace("无锡市", "").replace("无锡", ""); + } + + + /** + * 计算并保存电压变化率数据 + */ + private List calculateAndSaveDataVUp(String date,Map> avgVMap) { + InfluxQueryWrapper influxQueryWrapperV = new InfluxQueryWrapper(DataV.class); + influxQueryWrapperV.between(DataV::getTime, date.concat(InfluxDBTableConstant.START_TIME), date.concat(InfluxDBTableConstant.END_TIME)) + .eq(DataV::getValueType, InfluxDBTableConstant.CP95) + .ne(DataV::getPhaseType, InfluxDBTableConstant.PHASE_TYPE_T); + + List dataVList = dataVMapper.selectByQueryWrapper(influxQueryWrapperV); + if(CollUtil.isEmpty(dataVList)){ + log.error("data_v谐波放大算法查询原始数据为空!"); + } + Map> lineVMap = dataVList.stream() + .collect(Collectors.groupingBy(DataV::getLineId)); + + List dataVUpList = new ArrayList<>(); + lineVMap.forEach((lineId, list) -> { + // 计算所有字段的平均值(V1-V50) + Map avgMap = calculateFieldAveragesV(list, 50); + avgVMap.put(lineId,avgMap); + // 按相别分组处理 + Map> phaseList = list.stream() + .collect(Collectors.groupingBy(DataV::getPhaseType)); + + phaseList.forEach((phase, pList) -> { + pList.forEach(dataV -> { + DataVUp dataVUp = new DataVUp(); + dataVUp.setPhasicType(phase); + dataVUp.setTime(dataV.getTime()); + dataVUp.setLineId(dataV.getLineId()); + dataVUp.setValueType(dataV.getValueType()); + + // 动态设置 V1-V50 的相对变化率 + avgMap.forEach((field, avg) -> { + try { + // 获取 DataV 的当前字段值 + Method getter = DataV.class.getMethod("get" + field); + double value = (double) getter.invoke(dataV); + + // 计算相对变化率 + double relativeChange = (avg == 0) ? 0 : + BigDecimal.valueOf((value - avg) / avg) + .setScale(6, RoundingMode.HALF_UP) + .doubleValue(); + + // 设置到 DataVUp + Method setter = DataVUp.class.getMethod("set" + field, Double.class); + setter.invoke(dataVUp, relativeChange); + + // 判断是否为异常值 + if (specificHarmonicOrders.contains(getHarmonicOrderFromField(field)) + && relativeChange > voltageChangeRateThreshold) { + dataVUp.setAbnormalFlag(1); + } else if (dataVUp.getAbnormalFlag() == null) { + dataVUp.setAbnormalFlag(0); + } + } catch (NoSuchMethodException e) { + log.error("字段 {} 的 getter/setter 方法不存在", field, e); + } catch (Exception e) { + log.error("反射调用字段 {} 失败: {}", field, e.getMessage(), e); + } + }); + dataVUpList.add(dataVUp); + }); + }); + }); + + if(CollUtil.isNotEmpty(dataVUpList)) { + List result = dataVUpList.stream() + .sorted(Comparator.comparing(DataVUp::getTime).thenComparing(DataVUp::getPhasicType)) + .collect(Collectors.toList()); + dataVUpToMapper.insertBatch(result); + } + + return dataVUpList; + } + + /** + * 计算并保存电流变化率数据 + */ + private List calculateAndSaveDataIUp(String date,Map> avgIMap) { + InfluxQueryWrapper influxQueryWrapperI = new InfluxQueryWrapper(DataI.class); + influxQueryWrapperI.between(DataI::getTime, date.concat(InfluxDBTableConstant.START_TIME), date.concat(InfluxDBTableConstant.END_TIME)) + .eq(DataI::getValueType, InfluxDBTableConstant.CP95) + .ne(DataI::getPhaseType, InfluxDBTableConstant.PHASE_TYPE_T); + + List dataIList = dataIMapper.selectByQueryWrapper(influxQueryWrapperI); + if(CollUtil.isEmpty(dataIList)){ + log.error("data_i谐波放大算法查询原始数据为空!"); + } + Map> lineIMap = dataIList.stream() + .collect(Collectors.groupingBy(DataI::getLineId)); + + List dataIUpList = new ArrayList<>(); + lineIMap.forEach((lineId, list) -> { + // 计算所有字段的平均值(I1-I50) + Map avgMap = calculateFieldAveragesI(list, 50); + avgIMap.put(lineId,avgMap); + + // 按相别分组处理 + Map> phaseList = list.stream() + .collect(Collectors.groupingBy(DataI::getPhaseType)); + + phaseList.forEach((phase, pList) -> { + pList.forEach(dataI -> { + DataIUp dataIUp = new DataIUp(); + dataIUp.setPhasicType(phase); + dataIUp.setTime(dataI.getTime()); + dataIUp.setLineId(dataI.getLineId()); + dataIUp.setValueType(dataI.getValueType()); + + // 动态设置 I1-I50 的相对变化率 + avgMap.forEach((field, avg) -> { + try { + // 获取 DataI 的当前字段值 + Method getter = DataI.class.getMethod("get" + field); + double value = (double) getter.invoke(dataI); + double relativeChange = 3.1415926; + if(value>0.01){ + // 计算相对变化率 + relativeChange = (avg == 0) ? 0 : + BigDecimal.valueOf(Math.abs(value - avg) / avg) + .setScale(6, RoundingMode.HALF_UP) + .doubleValue(); + } + + + + // 设置到 DataIUp + Method setter = DataIUp.class.getMethod("set" + field, Double.class); + setter.invoke(dataIUp, relativeChange); + } catch (NoSuchMethodException e) { + log.error("字段 {} 的 getter/setter 方法不存在", field, e); + } catch (Exception e) { + log.error("反射调用字段 {} 失败: {}", field, e.getMessage(), e); + } + + }); + dataIUpList.add(dataIUp); + + }); + }); + }); + + if(CollUtil.isNotEmpty(dataIUpList)) { + List result = dataIUpList.stream() + .sorted(Comparator.comparing(DataIUp::getTime).thenComparing(DataIUp::getPhasicType)) + .collect(Collectors.toList()); + dataIUpToMapper.insertBatch(result); + } + + return dataIUpList; + } + + /** + * 分析谐波放大事件并保存到up_harmonic_detail表 + */ + private void analyzeAndSaveHarmonicEvents(List dataVUpList, List dataIUpList,Map> avgVMap,Map> avgIMap) { + // 按线路、时间、相别分组电流数据,便于查询 + Map iUpMap = dataIUpList.stream() + .collect(Collectors.toMap( + item -> item.getLineId() + StrUtil.C_UNDERLINE + item.getTime() + StrUtil.C_UNDERLINE + item.getPhasicType(), + item -> item + )); + + // 存储所有异常点信息 + Map> anomalyMap = new HashMap<>(); + + // 遍历电压数据,检查异常点 + Set lineSet = new HashSet<>(); + for (DataVUp vUp : dataVUpList) { + lineSet.add(vUp.getLineId()); + String key = vUp.getLineId() + StrUtil.C_UNDERLINE + vUp.getTime() + StrUtil.C_UNDERLINE + vUp.getPhasicType(); + + // 检查是否有对应的电流数据 + if (!iUpMap.containsKey(key)) { + continue; + } + + DataIUp iUp = iUpMap.get(key); + // 检查每个特定谐波次数 + for (int order : specificHarmonicOrders) { + try { + // 获取电压变化率 + String vField = "v" + order; + Method vGetter = DataVUp.class.getMethod("get" + Character.toUpperCase(vField.charAt(0)) + vField.substring(1)); + double vChangeRate = (double) vGetter.invoke(vUp); + + // 获取电流变化率 + String iField = "i" + order; + Method iGetter = DataIUp.class.getMethod("get" + Character.toUpperCase(iField.charAt(0)) + iField.substring(1)); + double iChangeRate = (double) iGetter.invoke(iUp); + + // 检查是否满足异常条件 + if (vChangeRate > voltageChangeRateThreshold && iChangeRate < currentChangeRateThreshold && iChangeRate!=3.1415926) { + String anomalyKey = vUp.getLineId() + StrUtil.C_UNDERLINE + vUp.getPhasicType() + StrUtil.C_UNDERLINE + order; + + AnomalyInfo info = new AnomalyInfo(); + info.setLineId(vUp.getLineId()); + info.setPhase(vUp.getPhasicType()); + info.setHarmonicOrder(order); + info.setTime(vUp.getTime()); + info.setVoltageChangeRate(vChangeRate); + info.setCurrentChangeRate(iChangeRate); + + if (!anomalyMap.containsKey(anomalyKey)) { + anomalyMap.put(anomalyKey, new ArrayList<>()); + } + anomalyMap.get(anomalyKey).add(info); + } + } catch (Exception e) { + log.error("分析谐波次数 {} 时发生错误", order, e); + } + } + } + + List ledgerBaseInfoList = ledgerScaleMapper.getLedgerBaseInfo(new ArrayList<>(lineSet)); + Map ledgerBaseInfoMap = ledgerBaseInfoList.stream().collect(Collectors.toMap(LedgerBaseInfo::getLineId,Function.identity())); + + // 处理异常点,生成谐波放大事件 + List harmonicDetails = new ArrayList<>(); + + for (List anomalies : anomalyMap.values()) { + if (anomalies.size() < continuousAnomalyCount) { + continue; // 连续异常点数量不足,不生成事件 + } + + // 按时间排序 + List sortedAnomalies = anomalies.stream() + .sorted(Comparator.comparing(AnomalyInfo::getTime)) + .collect(Collectors.toList()); + + if(sortedAnomalies.get(0).getLineId().equals("9686e66738bab8516ff2c2e9fedc0518")){ + System.out.println(555); + } + + // 检查是否连续 + List> infoList =findAllContinuousAnomalies(sortedAnomalies,ledgerBaseInfoMap); + + if (!infoList.isEmpty()) { + for (List temList : infoList) { + AnomalyInfo first = temList.get(0); + AnomalyInfo last = temList.get(temList.size() - 1); + + // 创建谐波放大事件记录 + UpHarmonicDetail detail = new UpHarmonicDetail(); + detail.setId(IdWorker.get32UUID()); + detail.setMonitorId(first.getLineId()); + detail.setPhase(first.getPhase()); + detail.setHarmonicCount(first.getHarmonicOrder()); + + // 转换时间格式 + detail.setStartTime(LocalDateTime.ofInstant(first.getTime(), ZoneId.systemDefault())); + detail.setEndTime(LocalDateTime.ofInstant(last.getTime(), ZoneId.systemDefault())); + + // 计算持续时间(秒) + long duration = ChronoUnit.SECONDS.between( + detail.getStartTime(), + detail.getEndTime() + ); + detail.setDuration((double) duration); + detail.setVAvgValue(avgVMap.get(first.lineId).get("V"+first.getHarmonicOrder())); + detail.setIAvgValue(avgIMap.get(first.lineId).get("I"+first.getHarmonicOrder())); + + detail.setCreateTime(LocalDateTime.now()); + if(first.getHarmonicOrder()>5){ + detail.setUpScheme("选取5% - 6%电抗率"); + }else { + detail.setUpScheme("选取12%的电抗率(或选取5% - 6%与12%两种电抗率混装方式)"); + } + + harmonicDetails.add(detail); + + log.info("发现谐波放大事件: 监测点={}, 谐波次数={}, 持续时间={}秒, 开始时间={}", + first.getLineId(), first.getHarmonicOrder(), duration, detail.getStartTime()); + } + } + } + + // 批量保存事件 + if (CollUtil.isNotEmpty(harmonicDetails)) { + this.saveBatch(harmonicDetails); + } + } + + /** + * 检查数据是否连续(每分钟一条记录) + */ + /** + * 查找所有连续10条记录(每条间隔50~70秒)的事件 + */ + private List> findAllContinuousAnomalies(List anomalies,Map ledgerBaseInfoMap) { + List> groups = new ArrayList<>(); + List currentGroup = new ArrayList<>(); + + int timeInterval = ledgerBaseInfoMap.get(anomalies.get(0).getLineId()).getTimeInterval(); + + for (AnomalyInfo current : anomalies) { + if (currentGroup.isEmpty()) { + currentGroup.add(current); + } else { + AnomalyInfo lastInGroup = currentGroup.get(currentGroup.size() - 1); + long minutesDiff = ChronoUnit.MINUTES.between(lastInGroup.getTime(), current.getTime()); + + if (minutesDiff == timeInterval) { + currentGroup.add(current); + } else { + // 不连续,检查当前组是否满足 >=10 + if (currentGroup.size() >= continuousAnomalyCount) { + groups.add(new ArrayList<>(currentGroup)); + } + // 重置当前组 + currentGroup.clear(); + currentGroup.add(current); + } + } + } + return groups; + } + + /** + * 从字段名中提取谐波次数 + */ + private int getHarmonicOrderFromField(String field) { + try { + return Integer.parseInt(field.substring(1)); + } catch (Exception e) { + return -1; + } + } + + /** + * 计算 DataV 对象中 V1-Vmax 的平均值 + */ + private Map calculateFieldAveragesV(List list, Integer maxField) { + Map avgMap = new HashMap<>(); + for (int i = 1; i <= maxField; i++) { + String field = "V" + i; + double avg = list.stream() + .mapToDouble(dataV -> { + try { + Method getter = DataV.class.getMethod("get" + field); + return (double) getter.invoke(dataV); + } catch (Exception e) { + return 0d; + } + }) + .average() + .orElse(0); + avgMap.put(field, avg); + } + return avgMap; + } + + /** + * 计算 DataI 对象中 I1-Imax 的平均值 + */ + private Map calculateFieldAveragesI(List list, Integer maxField) { + Map avgMap = new HashMap<>(); + for (int i = 1; i <= maxField; i++) { + String field = "I" + i; + double avg = list.stream() + .mapToDouble(dataI -> { + try { + Method getter = DataI.class.getMethod("get" + field); + return (double) getter.invoke(dataI); + } catch (Exception e) { + return 0d; + } + }) + .average() + .orElse(0); + avgMap.put(field, avg); + } + return avgMap; + } + + /** + * 内部类:用于临时存储异常点信息 + */ + @Data + private static class AnomalyInfo { + private String lineId; + private String phase; + private int harmonicOrder; + private Instant time; + private double voltageChangeRate; + private double currentChangeRate; + + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/impl/HistoryResultServiceImpl.java b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/impl/HistoryResultServiceImpl.java new file mode 100644 index 0000000..634fe21 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/harmonicUp/service/impl/HistoryResultServiceImpl.java @@ -0,0 +1,777 @@ +package com.njcn.product.advance.harmonicUp.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.njcn.common.pojo.constant.BizParamConstant; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.param.StatisticsBizBaseParam; +import com.njcn.common.utils.PubUtils; + +import com.njcn.influx.imapper.CommonMapper; +import com.njcn.influx.imapper.DataHarmRateVMapper; +import com.njcn.influx.imapper.DataIMapper; +import com.njcn.influx.pojo.bo.HarmonicHistoryData; +import com.njcn.influx.pojo.constant.InfluxDBTableConstant; +import com.njcn.product.advance.harmonicUp.pojo.param.HistoryParam; +import com.njcn.product.advance.harmonicUp.pojo.vo.HistoryDataResultVO; +import com.njcn.product.advance.harmonicUp.pojo.vo.QueryResultLimitVO; +import com.njcn.product.advance.harmonicUp.service.HistoryResultService; +import com.njcn.product.system.dict.mapper.DictDataMapper; + +import com.njcn.product.terminal.mysqlTerminal.mapper.LineMapper; +import com.njcn.product.terminal.mysqlTerminal.mapper.OverlimitMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LineDevGetDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Overlimit; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.PqsDeviceUnit; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author denghuajun + * @date 2022/3/14 + */ +@Slf4j +@Service +@AllArgsConstructor +public class HistoryResultServiceImpl implements HistoryResultService { + + + private final CommonMapper commonMapper; + + private final DataIMapper dataIMapper; + + private final DataHarmRateVMapper dataHarmRateVMapper; + + private final LineMapper lineMapper; + private final OverlimitMapper overlimitMapper; + private final DictDataMapper dictDataMapper; + + + @Override + public List getHistoryResult(HistoryParam historyParam) { + List historyDataResultVOList = new ArrayList<>(); + //获取监测点 + String[] points = historyParam.getLineId(); + Integer number = 0; + for (int i = 0; i < points.length; i++) { + HistoryDataResultVO historyDataResultVO; + + //获取指标 + String[] contions = historyParam.getCondition(); + for (int j = 0; j < contions.length; j++) { + if ("40".equals(contions[j]) || "41".equals(contions[j]) || "42".equals(contions[j]) || "43".equals(contions[j]) + || "44".equals(contions[j]) || "45".equals(contions[j]) || "50".equals(contions[j]) || "51".equals(contions[j]) + || "52".equals(contions[j])) { + number = historyParam.getHarmonic(); + } + if ("46".equals(contions[j]) || "47".equals(contions[j]) || "48".equals(contions[j]) || "49".equals(contions[j])) { + number = historyParam.getInHarmonic(); + } + historyDataResultVO = getCondition(historyParam.getSearchBeginTime(), historyParam.getSearchEndTime(), points[i], contions[j], number, historyParam.getValueType(), historyParam.getPtType()); + + historyDataResultVOList.add(historyDataResultVO); + + } + } + return historyDataResultVOList; + } + + + + /** + * influxDB相关操作 + * 查询稳态数据分析 + */ + @SneakyThrows + private HistoryDataResultVO getCondition(String startTime, String endTime, String lineId, String contion, Integer number, Integer valueType, Integer ptType) { + HistoryDataResultVO historyDataResultVO = new HistoryDataResultVO(); + QueryResultLimitVO queryResultLimitVO = getQueryResult(startTime, endTime, lineId, contion, number, valueType, ptType); + List harmonicHistoryDataList = queryResultLimitVO.getHarmonicHistoryDataList(); + BeanUtil.copyProperties(queryResultLimitVO, historyDataResultVO); + //时间轴 + List time = new ArrayList<>(); + //A相值 + List aValue; + //B相值 + List bValue = new ArrayList<>(); + //C相值 + List cValue = new ArrayList<>(); + //针对统计相别为T时存放的数据 + List fValue = new ArrayList<>(); + List> objectListData = new ArrayList<>(); + if (CollectionUtil.isNotEmpty(harmonicHistoryDataList)) { + //相别统计为T时,业务数据处理 + if (StrUtil.isBlank(harmonicHistoryDataList.get(0).getPhasicType()) || harmonicHistoryDataList.get(0).getPhasicType().equalsIgnoreCase("t")) { + for (HarmonicHistoryData harmonicHistoryData : harmonicHistoryDataList) { + time.add(PubUtils.instantToDate(harmonicHistoryData.getTime())); + fValue.add(BigDecimal.valueOf(harmonicHistoryData.getAValue()).setScale(4, RoundingMode.HALF_UP).floatValue()); + //返回结果有多个值,需要额外处理下 + if (Integer.parseInt(contion) == 14) { + bValue.add(BigDecimal.valueOf(harmonicHistoryData.getBValue()).setScale(4, RoundingMode.HALF_UP).floatValue()); + cValue.add(BigDecimal.valueOf(harmonicHistoryData.getCValue()).setScale(4, RoundingMode.HALF_UP).floatValue()); + } + } + //组装二维数组 + for (int i = 0; i < time.size(); i++) { + List objects = new ArrayList<>(); + objects.add(time.get(i)); + objects.add(fValue.get(i)); + if (Integer.parseInt(contion) == 14) { + objects.add(bValue.get(i)); + objects.add(cValue.get(i)); + } + objectListData.add(objects); + } + historyDataResultVO.setTopLimit(queryResultLimitVO.getTopLimit()); + historyDataResultVO.setLowerLimit(queryResultLimitVO.getLowerLimit()); + historyDataResultVO.setMinValue(Collections.min(fValue)); + historyDataResultVO.setMaxValue(Collections.max(fValue)); + historyDataResultVO.setValue(objectListData); + } else { + //按时间分组 + Map> map = harmonicHistoryDataList.stream().collect(Collectors.groupingBy(HarmonicHistoryData::getTime, TreeMap::new, Collectors.toList())); + + Float maxI = null; + Float minI = null; + for (Map.Entry> entry : map.entrySet()) { + List val = entry.getValue(); + Object[] objects = {PubUtils.instantToDate(entry.getKey()), 0, 0, 0}; + //需要保证val的长度为3 + if (val.size() != 3) { + for (int i = 0; i < 3 - val.size(); i++) { + HarmonicHistoryData tem = new HarmonicHistoryData(); + tem.setAValue(0f); + val.add(tem); + } + } + + for (HarmonicHistoryData harmonicHistoryData : val) { + if (InfluxDBTableConstant.PHASE_TYPE_A.equalsIgnoreCase(harmonicHistoryData.getPhasicType())) { + + BigDecimal a = BigDecimal.valueOf(harmonicHistoryData.getAValue()).setScale(4, RoundingMode.HALF_UP); + objects[1] = a; + maxI = max(maxI, a.floatValue()); + minI = min(minI, a.floatValue()); + + } else if (InfluxDBTableConstant.PHASE_TYPE_B.equalsIgnoreCase(harmonicHistoryData.getPhasicType())) { + BigDecimal b = BigDecimal.valueOf(harmonicHistoryData.getAValue()).setScale(4, RoundingMode.HALF_UP); + objects[2] = b; + maxI = max(maxI, b.floatValue()); + minI = min(minI, b.floatValue()); + + } else if (InfluxDBTableConstant.PHASE_TYPE_C.equalsIgnoreCase(harmonicHistoryData.getPhasicType())) { + BigDecimal c = BigDecimal.valueOf(harmonicHistoryData.getAValue()).setScale(4, RoundingMode.HALF_UP); + objects[3] = c; + maxI = max(maxI, c.floatValue()); + minI = min(minI, c.floatValue()); + + } + } + List list = new ArrayList<>(Arrays.asList(objects)); + + objectListData.add(list); + + } + + historyDataResultVO.setMaxValue(maxI); + historyDataResultVO.setMinValue(minI); + historyDataResultVO.setTopLimit(queryResultLimitVO.getTopLimit()); + historyDataResultVO.setLowerLimit(queryResultLimitVO.getLowerLimit()); + historyDataResultVO.setValue(objectListData); + } + + } else { + return historyDataResultVO; + } + return historyDataResultVO; + } + + + private Float max(Float ding, Float a) { + if (Objects.isNull(ding)) { + ding = a; + } + if (a > ding) { + ding = a; + } + return ding; + } + + private Float min(Float ding, Float a) { + if (Objects.isNull(ding)) { + ding = a; + } + if (a < ding) { + ding = a; + } + return ding; + } + + private QueryResultLimitVO getQueryResult(String startTime, String endTime, String lineId, String contion, Integer number, Integer valueType, Integer ptType) { + PqsDeviceUnit pqsDeviceUnit = new PqsDeviceUnit(); + QueryResultLimitVO queryResultLimitVO = new QueryResultLimitVO(); + if (!lineId.isEmpty()) { + Float topLimit = 0f; + Float lowerLimit = 0f; + + + //获取监测点信息 + LineDevGetDTO lineDetailData = lineMapper.getMonitorDetail(lineId); + //获取限值 + Overlimit overlimit = overlimitMapper.selectById(lineId); + + //组装sql语句 + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(InfluxDBTableConstant.TIME + " >= '").append(startTime).append("' and ").append(InfluxDBTableConstant.TIME).append(" <= '").append(endTime).append("' and ("); + //sql语句 + stringBuilder.append(InfluxDBTableConstant.LINE_ID + "='").append(lineId).append("')"); + String valueTypeName = ""; + switch (valueType) { + case 1: + valueTypeName = "AVG"; + break; + case 2: + valueTypeName = "MIN"; + break; + case 3: + valueTypeName = "MAX"; + break; + case 4: + valueTypeName = "CP95"; + break; + default: + break; + } + if (!Integer.valueOf(contion).equals(60) && !Integer.valueOf(contion).equals(61) && !Integer.valueOf(contion).equals(62)) { + stringBuilder.append(" and ").append(InfluxDBTableConstant.VALUE_TYPE + "='").append(valueTypeName).append("'"); + } + String sql = ""; + List phasicType = new ArrayList<>(); + List unit = new ArrayList<>(); + String targetName = ""; + switch (Integer.parseInt(contion)) { + case 10: + //相电压有效值 + sql = "SELECT time as time, rms as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + unit.add(pqsDeviceUnit.getPhaseVoltage()); + targetName = "相电压有效值"; + break; + case 11: + //线电压有效值 + sql = "SELECT time as time, rms_lvr as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C')order by time asc tz('Asia/Shanghai');"; + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + unit.add(pqsDeviceUnit.getLineVoltage()); + targetName = "线电压有效值"; + break; + case 12: + //电压偏差 + sql = "SELECT time as time, vu_dev as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + topLimit = overlimit.getVoltageDev(); + lowerLimit = overlimit.getUvoltageDev(); + if (ptType == 0) { + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + } else { + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + } + unit.add("%"); + targetName = "电压偏差"; + break; + case 13: + //三相电压不平衡度 + sql = "SELECT time as time, v_unbalance as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_v WHERE " + stringBuilder + + " and phasic_type ='T' order by time asc tz('Asia/Shanghai');"; + topLimit = overlimit.getUbalance(); + phasicType.add("三相电压不平衡度"); + unit.add("%"); + targetName = "三相电压不平衡度"; + break; + case 14: + //电压不平衡 + sql = "SELECT time as time, v_zero as aValue, v_pos as bValue, v_neg as cValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_v WHERE " + stringBuilder + + " and (phasic_type ='T') order by time asc tz('Asia/Shanghai');"; + phasicType.add("零序电压"); + phasicType.add("正序电压"); + phasicType.add("负序电压"); + unit.add(pqsDeviceUnit.getNoPositiveV()); + unit.add(pqsDeviceUnit.getPositiveV()); + unit.add(pqsDeviceUnit.getNoPositiveV()); + targetName = "电压不平衡"; + break; + case 15: + //电压总谐波畸变率 + sql = "SELECT time as time, v_thd as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + topLimit = overlimit.getUaberrance(); + if (ptType == 0) { + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + } else { + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + } + unit.add("%"); + targetName = "电压总谐波畸变率"; + break; + case 20: + //电流有效值 + sql = "SELECT time as time, rms as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_i WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + unit.add("A"); + targetName = "电流有效值"; + break; + case 21: + //电流总畸变率 + sql = "SELECT time as time, i_thd as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_i WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + unit.add("%"); + targetName = "电流总谐波畸变率"; + break; + case 22: + //负序电流 + sql = "SELECT time as time, i_neg as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_i WHERE " + stringBuilder + + " and phasic_type ='T' order by time asc tz('Asia/Shanghai');"; + topLimit = overlimit.getINeg(); + phasicType.add("负序电流"); + unit.add("A"); + targetName = "负序电流"; + break; + case 30: + //频率 V9暂时代表Freq + sql = "SELECT time as time, freq as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_v WHERE " + stringBuilder + + " and phasic_type ='T' order by time asc tz('Asia/Shanghai');"; + topLimit = 50 + overlimit.getFreqDev(); + lowerLimit = 50 - overlimit.getFreqDev(); + phasicType.add("频率"); + unit.add("Hz"); + targetName = "频率"; + break; + case 40: + //谐波电压含有率 + if (number == 1) { + targetName = "基波电压幅值"; + //修改幅值表 + sql = "SELECT time as time, v_1 as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + unit.add(pqsDeviceUnit.getPhaseVoltage()); + } else { + targetName = "谐波电压含有率"; + sql = "SELECT time as time, v_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmrate_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + if (number < 26) { + topLimit = PubUtils.getValueByMethod(overlimit, "getUharm", number); + } + unit.add("%"); + } + if (ptType == 0) { + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + } else { + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + } + + + break; + case 41: + //谐波电流含有率 + if (number == 1) { + targetName = "谐波电流幅值"; + sql = "SELECT time as time, i_1 as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmrate_i WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + + unit.add("A"); + } else { + targetName = "谐波电流含有率"; + sql = "SELECT time as time, i_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmrate_i WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + unit.add("%"); + } + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + + + break; + case 42: + //谐波电压幅值 + if (number == 1) { + sql = "SELECT time as time, v_1 as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + } else { + sql = "SELECT time as time, v_" + number + "*1000 as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + } + if (ptType == 0) { + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + } else { + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + } + if (number == 1) { + unit.add(pqsDeviceUnit.getVfundEffective()); + targetName = "基波电压幅值"; + + } else { + unit.add("V"); + targetName = "谐波电压幅值"; + + } + break; + case 43: + //谐波电流幅值 + if (number == 1) { + sql = "SELECT time as time, i_1 as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_i WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + targetName = "基波电流幅值"; + + } else { + sql = "SELECT time as time, i_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_i WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + if (number < 26) { + topLimit = PubUtils.getValueByMethod(overlimit, "getIharm", number); + } + targetName = "谐波电流幅值"; + + } + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + unit.add("A"); + break; + case 44: + //谐波电压相角 + if (number == 1) { + targetName = "基波电压相角"; + + sql = "SELECT time as time, v_1 as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmphasic_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + } else { + targetName = "谐波电压相角"; + sql = "SELECT time as time, v_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmphasic_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + } + if (ptType == 0) { + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + } else { + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + } + unit.add("°"); + break; + case 45: + //谐波电流相角 + if (number == 1) { + sql = "SELECT time as time, i_1 as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmphasic_i WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + targetName = "基波电流相角"; + + } else { + sql = "SELECT time as time, i_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmphasic_i WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + targetName = "谐波电流相角"; + + } + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + unit.add("°"); + break; + case 46: + //间谐波电压含有率 + sql = "SELECT time as time, v_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_inharm_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + if(number<17){ + topLimit = PubUtils.getValueByMethod(overlimit, "getInuharm", number); + }else { + topLimit = 0.0f; + } + if (ptType == 0) { + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + } else { + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + } + unit.add("%"); + targetName = "间谐波电压含有率"; + break; + case 47: + //间谐波电流含有率 + sql = "SELECT time as time, i_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_inharm_i WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + unit.add("%"); + targetName = "间谐波电流含有率"; + break; + case 48: + //间谐波电压幅值 + sql = "SELECT time as time, v_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_inharm_v WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + targetName = "间谐波电压幅值"; + if (ptType == 0) { + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + } else { + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + } + unit.add(pqsDeviceUnit.getPhaseVoltage()); + break; + case 49: + //间谐波电流幅值 + sql = "SELECT time as time, i_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_inharm_i WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + unit.add("A"); + targetName = "间谐波电流幅值"; + break; + case 50: + //谐波有功功率 + sql = "SELECT time as time, p_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_p WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + if (number == 1) { + unit.add(pqsDeviceUnit.getFundActiveP()); + } else { + unit.add("W"); + } + targetName = "谐波有功功率"; + break; + case 51: + //谐波无功功率 + sql = "SELECT time as time, q_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_q WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + if (number == 1) { + unit.add(pqsDeviceUnit.getTotalNoP()); + } else { + unit.add("Var"); + } + targetName = "谐波无功功率"; + break; + case 52: + //谐波视在功率 + sql = "SELECT time as time, s_" + number + " as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_s WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + if (number == 1) { + unit.add(pqsDeviceUnit.getTotalViewP()); + } else { + unit.add("VA"); + } + targetName = "谐波视在功率"; + break; + case 53: + //三相有功功率 + sql = "SELECT time as time, p as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_p WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + unit.add(pqsDeviceUnit.getTotalActiveP()); + targetName = "三相有功功率"; + break; + case 54: + //三相无功功率 + sql = "SELECT time as time, q as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_q WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + unit.add(pqsDeviceUnit.getTotalNoP()); + targetName = "三相无功功率"; + break; + case 55: + //三相视在功率 + sql = "SELECT time as time, s as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_s WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + unit.add(pqsDeviceUnit.getTotalViewP()); + targetName = "三相视在功率"; + break; + case 56: + //三相总有功功率 + sql = "SELECT time as time, p as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_p WHERE " + stringBuilder + + " and phasic_type ='T' order by time asc tz('Asia/Shanghai');"; + phasicType.add("三相总有功功率"); + unit.add(pqsDeviceUnit.getTotalActiveP()); + targetName = "三相总有功功率"; + break; + case 57: + //三相总无功功率 + sql = "SELECT time as time, q as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_q WHERE " + stringBuilder + + " and phasic_type ='T' order by time asc tz('Asia/Shanghai');"; + phasicType.add("三相总无功功率"); + unit.add(pqsDeviceUnit.getTotalNoP()); + targetName = "三相总无功功率"; + break; + case 58: + //三相总视在功率 + sql = "SELECT time as time, s as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_s WHERE " + stringBuilder + + " and phasic_type ='T' order by time asc tz('Asia/Shanghai');"; + phasicType.add("三相总视在功率"); + unit.add(pqsDeviceUnit.getTotalViewP()); + targetName = "三相总视在功率"; + break; + case 59: + //视在功率因数 + sql = "SELECT time as time, pf as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_p WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') group by phasic_type order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + targetName = "视在功率因数"; + break; + case 591: + //位移功率因数 + sql = "SELECT time as time, df as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_p WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + targetName = "位移功率因数"; + break; + case 592: + //总视在功率因数 + sql = "SELECT time as time, pf as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_p WHERE " + stringBuilder + + " and phasic_type ='T' order by time asc tz('Asia/Shanghai');"; + phasicType.add("总视在功率因数"); + targetName = "总视在功率因数"; + break; + case 593: + //总位移功率因数 + sql = "SELECT time as time, df as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_harmpower_p WHERE " + stringBuilder + + " and phasic_type ='T' order by time asc tz('Asia/Shanghai');"; + phasicType.add("总位移功率因数"); + targetName = "总位移功率因数"; + break; + case 61: + //长时闪变 + sql = "SELECT time as time, plt as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_plt WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + if (ptType == 0) { + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + } else { + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + } + topLimit = overlimit.getFlicker(); + targetName = "长时闪变"; + break; + case 60: + //短时闪变 + sql = "SELECT time as time, pst as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_flicker WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + topLimit = overlimit.getFlicker(); + if (ptType == 0) { + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + } else { + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + } + targetName = "短时闪变"; + break; + case 62: + //电压波动 + sql = "SELECT time as time, fluc as aValue ," + InfluxDBTableConstant.PHASIC_TYPE + " FROM data_fluc WHERE " + stringBuilder + + " and (phasic_type ='A' or phasic_type ='B' or phasic_type ='C') order by time asc tz('Asia/Shanghai');"; + if (ptType == 0) { + phasicType.add("A相"); + phasicType.add("B相"); + phasicType.add("C相"); + } else { + phasicType.add("AB相"); + phasicType.add("BC相"); + phasicType.add("CA相"); + } + targetName = "电压波动"; + unit.add("%"); + break; + default: + break; + } + //大致有3种类型 + //1、一次查询返回3条记录,分别为A/B/C三相的结果 + //2、一次查询返回一条记录,以T相为条件,返回某3个指标值 + //3、一次查询返回一条记录,以T相为条件,返回某1个指标值 + List harmonicHistoryData = commonMapper.getHistoryResult(sql); + queryResultLimitVO.setHarmonicHistoryDataList(harmonicHistoryData); + queryResultLimitVO.setTopLimit(topLimit); + queryResultLimitVO.setLowerLimit(lowerLimit); + queryResultLimitVO.setPhaiscType(phasicType); + queryResultLimitVO.setUnit(unit); + queryResultLimitVO.setLineName(lineDetailData.getPointName()); + queryResultLimitVO.setHarmNum(number); + queryResultLimitVO.setTargetName(targetName); + queryResultLimitVO.setWiringMethod(lineDetailData.getWiringMethod()); + } else { + return queryResultLimitVO; + } + return queryResultLimitVO; + } + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/analysis/CanonicalCorrelationAnalysis.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/analysis/CanonicalCorrelationAnalysis.java new file mode 100644 index 0000000..65bd810 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/analysis/CanonicalCorrelationAnalysis.java @@ -0,0 +1,382 @@ +package com.njcn.product.advance.responsility.analysis; + + +import com.njcn.product.advance.responsility.pojo.constant.HarmonicConstants; +import com.njcn.product.advance.responsility.utils.MathUtils; +import org.apache.commons.math3.linear.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 典则相关分析类 + * 实现典则相关系数的计算 + * + * @author hongawen + * @version 1.0 + */ +public class CanonicalCorrelationAnalysis { + + private static final Logger logger = LoggerFactory.getLogger(CanonicalCorrelationAnalysis.class); + + /** + * 计算典则相关系数 + * 对应C代码中的TransCancor函数 + * + * @param powerData 功率数据矩阵 [时间][节点] + * @param harmonicData 谐波数据向量 + * @param windowSize 窗口大小 + * @param nodeCount 节点数量 + * @return 典则相关系数 + */ + public static float computeCanonicalCorrelation(float[][] powerData, float[] harmonicData, + int windowSize, int nodeCount) { + logger.info("===== 开始典型相关分析 ====="); + logger.info("输入参数: windowSize={}, nodeCount={}", windowSize, nodeCount); + + try { + // 提取窗口数据 + double[][] x = new double[windowSize][nodeCount]; + double[] y = new double[windowSize]; + + // ===== 数据质量统计(只统计,不影响计算) ===== + int nanCountPower = 0, infiniteCountPower = 0, zeroCountPower = 0; + int nanCountHarmonic = 0, infiniteCountHarmonic = 0, zeroCountHarmonic = 0; + double powerSum = 0, harmonicSum = 0; + double powerMin = Double.MAX_VALUE, powerMax = -Double.MAX_VALUE; + double harmonicMin = Double.MAX_VALUE, harmonicMax = -Double.MAX_VALUE; + + for (int i = 0; i < windowSize; i++) { + for (int j = 0; j < nodeCount; j++) { + float val = powerData[i][j]; + x[i][j] = val; + + // 仅统计,不改变原逻辑 + if (Float.isNaN(val)) { + nanCountPower++; + } else if (Float.isInfinite(val)) { + infiniteCountPower++; + } else if (val == 0.0f) { + zeroCountPower++; + } else { + powerSum += val; + powerMin = Math.min(powerMin, val); + powerMax = Math.max(powerMax, val); + } + } + + float harmonicVal = harmonicData[i]; + y[i] = harmonicVal; + + // 仅统计,不改变原逻辑 + if (Float.isNaN(harmonicVal)) { + nanCountHarmonic++; + } else if (Float.isInfinite(harmonicVal)) { + infiniteCountHarmonic++; + } else if (harmonicVal == 0.0f) { + zeroCountHarmonic++; + } else { + harmonicSum += harmonicVal; + harmonicMin = Math.min(harmonicMin, harmonicVal); + harmonicMax = Math.max(harmonicMax, harmonicVal); + } + } + + // ===== 数据质量报告(只记录日志) ===== + int totalPowerCount = windowSize * nodeCount; + int totalHarmonicCount = windowSize; + + logger.info("功率数据质量分析:"); + logger.info(" 总数据点: {}", totalPowerCount); + logger.info(" NaN数量: {} ({:.2f}%)", nanCountPower, nanCountPower * 100.0 / totalPowerCount); + logger.info(" 无穷大数量: {} ({:.2f}%)", infiniteCountPower, infiniteCountPower * 100.0 / totalPowerCount); + logger.info(" 零值数量: {} ({:.2f}%)", zeroCountPower, zeroCountPower * 100.0 / totalPowerCount); + logger.info(" 有效数据范围: [{}, {}]", powerMin == Double.MAX_VALUE ? "N/A" : powerMin, + powerMax == -Double.MAX_VALUE ? "N/A" : powerMax); + + logger.info("谐波数据质量分析:"); + logger.info(" 总数据点: {}", totalHarmonicCount); + logger.info(" NaN数量: {} ({:.2f}%)", nanCountHarmonic, nanCountHarmonic * 100.0 / totalHarmonicCount); + logger.info(" 无穷大数量: {} ({:.2f}%)", infiniteCountHarmonic, infiniteCountHarmonic * 100.0 / totalHarmonicCount); + logger.info(" 零值数量: {} ({:.2f}%)", zeroCountHarmonic, zeroCountHarmonic * 100.0 / totalHarmonicCount); + logger.info(" 有效数据范围: [{}, {}]", harmonicMin == Double.MAX_VALUE ? "N/A" : harmonicMin, + harmonicMax == -Double.MAX_VALUE ? "N/A" : harmonicMax); + + // 只记录警告,不停止计算 + if (nanCountPower > 0 || infiniteCountPower > 0) { + logger.warn("功率数据包含异常值!NaN: {}, Infinite: {}", nanCountPower, infiniteCountPower); + } + if (nanCountHarmonic > 0 || infiniteCountHarmonic > 0) { + logger.warn("谐波数据包含异常值!NaN: {}, Infinite: {}", nanCountHarmonic, infiniteCountHarmonic); + } + + // 计算协方差矩阵 SXX + logger.info("===== 开始协方差计算 ====="); + double[][] sxxMatrix = MathUtils.covarianceMatrix(x, windowSize, nodeCount); + + // ===== SXX矩阵诊断(只记录日志)===== + double sxxTrace = 0; + double sxxFrobeniusNorm = 0; + boolean sxxHasNaN = false, sxxHasInfinite = false; + + for (int i = 0; i < nodeCount; i++) { + sxxTrace += sxxMatrix[i][i]; + for (int j = 0; j < nodeCount; j++) { + double val = sxxMatrix[i][j]; + if (Double.isNaN(val)) { + sxxHasNaN = true; + } + if (Double.isInfinite(val)) { + sxxHasInfinite = true; + } + sxxFrobeniusNorm += val * val; + } + } + sxxFrobeniusNorm = Math.sqrt(sxxFrobeniusNorm); + + logger.info("SXX矩阵诊断:"); + logger.info(" 维度: {}x{}", nodeCount, nodeCount); + logger.info(" 迹(trace): {}", sxxTrace); + logger.info(" Frobenius范数: {}", sxxFrobeniusNorm); + logger.info(" 包含NaN: {}", sxxHasNaN); + logger.info(" 包含无穷大: {}", sxxHasInfinite); + logger.info(" 对角线元素: {}", java.util.Arrays.toString( + java.util.stream.IntStream.range(0, nodeCount) + .mapToDouble(i -> sxxMatrix[i][i]) + .toArray())); + + // 计算协方差 SYY + double syyMatrix = MathUtils.covariance(y, y, windowSize); + logger.info("SYY协方差: {}", syyMatrix); + + if (Math.abs(syyMatrix) < HarmonicConstants.MIN_COVARIANCE) { + logger.warn("SYY过小 ({}), 调整为最小值: {}", syyMatrix, HarmonicConstants.MIN_COVARIANCE); + syyMatrix = HarmonicConstants.MIN_COVARIANCE; + } + + // 计算协方差向量 SXY + double[] sxyVector = MathUtils.covarianceVector(x, y, windowSize, nodeCount); + + // ===== SXY向量诊断(只记录日志)===== + double sxyNorm = 0; + boolean sxyHasNaN = false, sxyHasInfinite = false; + for (double val : sxyVector) { + if (Double.isNaN(val)) { + sxyHasNaN = true; + } + if (Double.isInfinite(val)) { + sxyHasInfinite = true; + } + sxyNorm += val * val; + } + sxyNorm = Math.sqrt(sxyNorm); + + logger.info("SXY向量诊断:"); + logger.info(" 长度: {}", sxyVector.length); + logger.info(" L2范数: {}", sxyNorm); + logger.info(" 包含NaN: {}", sxyHasNaN); + logger.info(" 包含无穷大: {}", sxyHasInfinite); + logger.info(" 向量值: {}", java.util.Arrays.toString(sxyVector)); + + // 使用Apache Commons Math进行矩阵运算 + logger.info("===== 开始矩阵分解 ====="); + RealMatrix sxx = new Array2DRowRealMatrix(sxxMatrix); + RealVector sxy = new ArrayRealVector(sxyVector); + + // 计算 SXX^(-1) + logger.info("准备计算SXX逆矩阵..."); + DecompositionSolver solver = new LUDecomposition(sxx).getSolver(); + RealMatrix invSxx; + + // 检查矩阵奇异性 + double sxxDet = new LUDecomposition(sxx).getDeterminant(); + logger.info("SXX矩阵行列式: {}", sxxDet); + + if (Math.abs(sxxDet) < 1e-15) { + logger.warn("SXX矩阵几乎奇异 (det={})", sxxDet); + } + + if (!solver.isNonSingular()) { + // 如果矩阵奇异,使用伪逆 + logger.warn("SXX matrix is singular, using pseudo-inverse"); + try { + SingularValueDecomposition svd = new SingularValueDecomposition(sxx); + invSxx = svd.getSolver().getInverse(); + } catch (Exception svdException) { + logger.error("SVD pseudo-inverse failed, using regularized inverse", svdException); + // 添加正则化项 + RealMatrix identity = MatrixUtils.createRealIdentityMatrix(sxx.getRowDimension()); + RealMatrix regularized = sxx.add(identity.scalarMultiply(1e-10)); + invSxx = new LUDecomposition(regularized).getSolver().getInverse(); + } + } else { + invSxx = solver.getInverse(); + } + + // 计算 U = SXX^(-1) * SXY * (1/SYY) * SXY' + RealVector temp = invSxx.operate(sxy); + double scale = 1.0 / syyMatrix; + RealMatrix uMatrix = temp.outerProduct(sxy).scalarMultiply(scale); + + // 计算特征值 - 添加数值稳定性处理 + double maxEigenvalue = 0.0; + + try { + // 首先检查矩阵是否有效 + double[][] uMatrixData = uMatrix.getData(); + boolean hasNaN = false; + boolean hasInfinite = false; + + for (int i = 0; i < uMatrixData.length; i++) { + for (int j = 0; j < uMatrixData[i].length; j++) { + if (Double.isNaN(uMatrixData[i][j])) { + hasNaN = true; + } + if (Double.isInfinite(uMatrixData[i][j])) { + hasInfinite = true; + } + } + } + + if (hasNaN || hasInfinite) { + logger.warn("U matrix contains NaN or Infinite values, returning 0"); + return 0.0f; + } + + // 检查矩阵条件数 + SingularValueDecomposition svdCheck = new SingularValueDecomposition(uMatrix); + double conditionNumber = svdCheck.getConditionNumber(); + + if (conditionNumber > 1e12) { + logger.warn("U matrix is ill-conditioned (condition number: {}), using SVD approach", conditionNumber); + + // 使用SVD方法获取最大奇异值的平方作为最大特征值 + double[] singularValues = svdCheck.getSingularValues(); + if (singularValues.length > 0) { + maxEigenvalue = singularValues[0] * singularValues[0]; + } + } else { + // 正常的特征值分解 + EigenDecomposition eigenDecomposition = new EigenDecomposition(uMatrix); + double[] eigenvalues = eigenDecomposition.getRealEigenvalues(); + + // 找最大特征值 + for (double eigenvalue : eigenvalues) { + maxEigenvalue = Math.max(maxEigenvalue, Math.abs(eigenvalue)); + } + } + + } catch (Exception eigenException) { + logger.warn("EigenDecomposition failed, trying alternative approach: {}", eigenException.getMessage()); + + // 备用方案:使用SVD方法 + try { + SingularValueDecomposition svd = new SingularValueDecomposition(uMatrix); + double[] singularValues = svd.getSingularValues(); + if (singularValues.length > 0) { + maxEigenvalue = singularValues[0] * singularValues[0]; + } + } catch (Exception svdException) { + logger.error("Both EigenDecomposition and SVD failed, returning 0", svdException); + return 0.0f; + } + } + + // 典则相关系数是最大特征值的平方根 + double canonicalCorr = Math.sqrt(Math.abs(maxEigenvalue)); + + // 限制在[0,1]范围内 + if (canonicalCorr > 1.0) { + canonicalCorr = 1.0; + } + + logger.info("===== 典型相关分析计算完成 ====="); + logger.info("最大特征值: {}", maxEigenvalue); + logger.info("典型相关系数: {}", canonicalCorr); + logger.info("是否被截断到1.0: {}", canonicalCorr >= 1.0); + + return (float) canonicalCorr; + + } catch (Exception e) { + logger.error("Error computing canonical correlation", e); + logger.error("异常详情: {}", e.getMessage()); + logger.error("异常类型: {}", e.getClass().getSimpleName()); + return 0.0f; + } + } + + /** + * 滑动窗口计算典则相关系数序列 + * 对应C代码中的SlideCanCor函数 + * + * @param powerData 功率数据矩阵 [时间][节点] + * @param harmonicData 谐波数据向量 + * @param windowSize 窗口大小 + * @param nodeCount 节点数量 + * @param dataLength 数据总长度 + * @return 典则相关系数序列 + */ + public static float[] slidingCanonicalCorrelation(float[][] powerData, float[] harmonicData, + int windowSize, int nodeCount, int dataLength) { + int slideLength = dataLength - windowSize; + if (slideLength <= 0) { + throw new IllegalArgumentException("Data length must be greater than window size"); + } + + float[] slideCanCor = new float[slideLength]; + + logger.info("Starting sliding canonical correlation analysis, slide length: {}", slideLength); + + for (int i = 0; i < slideLength; i++) { + // 提取窗口数据 + float[][] windowPower = new float[windowSize][nodeCount]; + float[] windowHarmonic = new float[windowSize]; + + for (int j = 0; j < windowSize; j++) { + System.arraycopy(powerData[i + j], 0, windowPower[j], 0, nodeCount); + windowHarmonic[j] = harmonicData[i + j]; + } + + // 计算当前窗口的典则相关系数 + slideCanCor[i] = computeCanonicalCorrelation(windowPower, windowHarmonic, + windowSize, nodeCount); + + if (i % 10 == 0) { + logger.debug("Processed window {}/{}", i, slideLength); + } + } + + logger.info("Sliding canonical correlation analysis completed"); + + return slideCanCor; + } + + /** + * 计算包含/不包含背景的动态相关系数 + * 对应C代码中的SlideCor函数 + * + * @param powerData 功率数据(单个节点) + * @param harmonicData 谐波数据 + * @param slideCanCor 滑动典则相关系数 + * @param windowSize 窗口大小 + * @return 动态相关系数序列 + */ + public static float[] slidingCorrelation(float[] powerData, float[] harmonicData, + float[] slideCanCor, int windowSize) { + int slideLength = slideCanCor.length; + float[] slideCor = new float[slideLength]; + + for (int i = 0; i < slideLength; i++) { + float[] tempPower = new float[windowSize]; + float[] tempHarmonic = new float[windowSize]; + + for (int j = 0; j < windowSize; j++) { + tempPower[j] = powerData[i + j]; + tempHarmonic[j] = harmonicData[i + j] * slideCanCor[i]; + } + + slideCor[i] = MathUtils.pearsonCorrelation(tempHarmonic, tempPower, windowSize); + } + + return slideCor; + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/calculator/HarmonicCalculationEngine.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/calculator/HarmonicCalculationEngine.java new file mode 100644 index 0000000..becb25d --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/calculator/HarmonicCalculationEngine.java @@ -0,0 +1,425 @@ +package com.njcn.product.advance.responsility.calculator; + + +import com.njcn.product.advance.responsility.analysis.CanonicalCorrelationAnalysis; +import com.njcn.product.advance.responsility.model.HarmonicData; +import com.njcn.product.advance.responsility.pojo.constant.CalculationMode; +import com.njcn.product.advance.responsility.pojo.constant.CalculationStatus; +import com.njcn.product.advance.responsility.pojo.constant.HarmonicConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 谐波责任计算主引擎 + * 严格对应C代码中的harm_res系列函数 + * + * @author hongawen + * @version 2.0 - 修复版本,严格对照C代码实现 + */ +public class HarmonicCalculationEngine { + + private static final Logger logger = LoggerFactory.getLogger(HarmonicCalculationEngine.class); + + // 对应C代码中的全局变量 + private int P; // 节点数 p_node + private int TL; // 功率数据长度 p_num + private int LL; // 谐波数据长度 harm_num + private int JIANGE; // 数据间隔比例 + private int width; // 窗口大小 + private float XIANE; // 谐波门槛 + + /** + * 主计算入口 + * 对应C代码中的harm_res函数 + * + * @param data 谐波数据对象 + * @return 计算是否成功 + */ + public boolean calculate(HarmonicData data) { + logger.info("Starting harmonic calculation, mode: {}", data.getCalculationMode()); + + try { + if (data.getCalculationMode() == CalculationMode.FULL_CALCULATION) { + return fullCalculation(data); + } else { + return partialCalculation(data); + } + } catch (Exception e) { + logger.error("Calculation failed with exception: " + e.getMessage(), e); + e.printStackTrace(); + data.setCalculationStatus(CalculationStatus.FAILED); + return false; + } + } + + /** + * 完整计算模式 + * 严格对应C代码中的harm_res_all函数 + * + * @param data 谐波数据对象 + * @return 计算是否成功 + */ + private boolean fullCalculation(HarmonicData data) { + logger.info("Executing full calculation mode"); + + // 1. 数据初始化 - 对应 data_init_all() + if (!initializeFullCalculationData(data)) { + logger.error("Data initialization failed"); + data.setCalculationStatus(CalculationStatus.FAILED); + return false; + } + + // 2. 创建工作数组 - 对应C代码行536-540 + float[][] a = new float[TL][P]; // 功率数据副本 + float[] b = new float[LL]; // 谐波数据副本 + float[] u = new float[TL]; // 对齐后的谐波数据 + + // 3. 复制数据 - 对应C代码行542-552 + for (int i = 0; i < TL; i++) { + for (int j = 0; j < P; j++) { + a[i][j] = data.getPowerData()[i][j]; + } + } + for (int i = 0; i < LL; i++) { + b[i] = data.getHarmonicData()[i]; + } + + // 4. 数据对齐处理 - 严格对应C代码行554-562 + // 注意:C代码是原地修改数组b + for (int i = 0; i < LL; i += JIANGE) { + float tempt = 0.0f; + for (int j = 0; j < JIANGE; j++) { + tempt += b[i + j]; + } + b[i] = tempt / JIANGE; // 覆盖原位置 + } + + // 5. 构建Udata - 严格对应C代码行570-580 + // 注意:使用 i*JIANGE 索引 + for (int i = 0; i < TL; i++) { + u[i] = b[i * JIANGE]; // 关键:使用 i*JIANGE 索引 + } + + int slcorlength = TL - width; + + // 6. 计算滑动典则相关系数 - 对应C代码行584 + logger.info("Computing sliding canonical correlation"); + float[] cancorrelation = CanonicalCorrelationAnalysis.slidingCanonicalCorrelation( + a, u, width, P, TL + ); + + // 7. 保存典则相关系数 - 对应C代码行592-601 + float[] Core = new float[slcorlength]; + float[] BjCore = new float[slcorlength]; + for (int i = 0; i < slcorlength; i++) { + Core[i] = cancorrelation[i]; + BjCore[i] = 1 - cancorrelation[i]; + } + data.setCanonicalCorrelation(Core); + data.setBackgroundCanonicalCorr(BjCore); + + // 8. 计算动态相关系数矩阵 - 对应C代码行605-635 + logger.info("Computing correlation matrix"); + float[][] simCor = new float[slcorlength][P]; + + // 对应C代码行618-632:对每个节点计算动态相关系数 + for (int i = 0; i < P; i++) { + // 提取第i个节点的功率数据 + float[] xe = new float[TL]; + for (int m = 0; m < TL; m++) { + xe[m] = a[m][i]; // 对应 Pdata.block(0, i, TL, 1) + } + + // 计算该节点的滑动相关系数 + float[] slidecor = CanonicalCorrelationAnalysis.slidingCorrelation( + xe, u, cancorrelation, width + ); + + // 存储结果 + for (int j = 0; j < slcorlength; j++) { + simCor[j][i] = slidecor[j]; + } + } + data.setCorrelationData(simCor); + + // 9. 计算EK值 - 对应C代码行642-654 + logger.info("Computing EK values"); + float[][] EKdata = ResponsibilityCalculator.computeEK( + simCor, a, width, P, TL + ); + + // 10. 计算FK值 - 对应C代码行660-673 + logger.info("Computing FK values"); + float[][] FKdata = ResponsibilityCalculator.computeFK( + EKdata, width, P, TL + ); + data.setFkData(FKdata); + + // 11. 计算HK值 - 对应C代码行678-691 + logger.info("Computing HK values"); + float[][] HKdata = ResponsibilityCalculator.computeHK( + BjCore, EKdata, width, P, TL + ); + data.setHkData(HKdata); + + // 12. 设置结果数量 - 对应C代码行693 + data.setResponsibilityDataCount(slcorlength); + + // 13. 统计超限时段的责任 - 对应C代码行696-724 + logger.info("Computing responsibility sums"); + + // 重要修正:C代码的SumHK函数中,虽然Udata长度是TL,但是循环只遍历前slg(=TL-width)个元素 + // 所以我们需要传入完整的u数组(长度TL),让sumResponsibility内部处理 + // 对应C代码:VectorXd Udata(TL); 以及 SumHK函数调用 + + // 统计HK责任(包含背景)- 对应C代码行698-710 + // 注意:传入完整的u数组(TL长度),而不是截取的数组 + float[] sumHK = ResponsibilityCalculator.sumResponsibility( + HKdata, u, XIANE, width, P + 1, TL + ); + data.setSumHKData(sumHK); + + // 统计FK责任(不包含背景)- 对应C代码行712-724 + // 同样传入完整的u数组和TL参数 + float[] sumFK = ResponsibilityCalculator.sumResponsibility( + FKdata, u, XIANE, width, P, TL + ); + data.setSumFKData(sumFK); + + // 14. 标记计算成功 - 对应C代码行739 + data.setCalculationStatus(CalculationStatus.CALCULATED); + logger.info("Full calculation completed successfully"); + + return true; + } + + /** + * 初始化完整计算数据 + * 对应C代码中的data_init_all函数 + */ + private boolean initializeFullCalculationData(HarmonicData data) { + // 设置全局变量 - 对应C代码行478-483 + P = data.getPowerNodeCount(); + TL = data.getPowerCount(); + LL = data.getHarmonicCount(); + // 对应C代码第481行:JIANGE = pq_buf.harm_num/pq_buf.p_num; + // 重要修正:JIANGE应该是 谐波数量/功率点数,不是谐波数量/节点数 + JIANGE = LL / TL; // 这个是正确的:harm_num / p_num (其中p_num是功率点数) + width = data.getWindowSize(); + XIANE = data.getHarmonicThreshold(); + + // 验证数据 - 对应C代码行485-504 + if (JIANGE * TL != LL || JIANGE < 1) { + logger.error("Data length mismatch: JIANGE({}) * TL({}) != LL({})", + JIANGE, TL, LL); + return false; + } + + if (width < HarmonicConstants.MIN_WIN_LEN || width > HarmonicConstants.MAX_WIN_LEN) { + logger.error("Invalid window size: {}", width); + return false; + } + + if (TL < 2 * width) { + logger.error("Power data length {} is too short for window size {}", TL, width); + return false; + } + + if (P > HarmonicConstants.MAX_P_NODE || TL > HarmonicConstants.MAX_P_NUM || + LL > HarmonicConstants.MAX_HARM_NUM) { + logger.error("Data size exceeds limits"); + return false; + } + + return true; + } + + /** + * 部分重算模式 + * 对应C代码中的harm_res_part函数 + * + * @param data 谐波数据对象 + * @return 计算是否成功 + */ + private boolean partialCalculation(HarmonicData data) { + logger.info("Executing partial calculation mode"); + + // 1. 数据初始化 - 对应 data_init_part() + if (!initializePartialCalculationData(data)) { + logger.error("Data initialization failed for partial calculation"); + data.setCalculationStatus(CalculationStatus.FAILED); + return false; + } + + // 2. 准备Udata - 对应C代码行816-818 + // C代码:VectorXd Udata(TL); 并从pq_buf.harm_data复制TL个元素 + int res_num = data.getResponsibilityDataCount(); + + // 验证责任数据行数 + if (res_num != TL - width) { + logger.warn("责任数据行数({})与期望值(TL-width={})不匹配", res_num, TL - width); + res_num = TL - width; // 使用正确的值 + } + + // 重要修正:与C代码保持一致,Udata长度应该是TL,而不是res_num + // C代码:VectorXd Udata(TL); + float[] Udata = new float[TL]; + + // 从harm_data复制TL个元素到Udata + // C代码:for (int j = 0; j < TL; j++) Udata[j] = pq_buf.harm_data[j]; + if (data.getHarmonicData().length < TL) { + logger.warn("谐波数据长度({})小于TL({}), 将补零", + data.getHarmonicData().length, TL); + System.arraycopy(data.getHarmonicData(), 0, Udata, 0, data.getHarmonicData().length); + // 剩余部分自动补零 + } else { + System.arraycopy(data.getHarmonicData(), 0, Udata, 0, TL); + } + + logger.debug("准备Udata完成: 长度={} (对应C代码TL), 责任数据行数={}", Udata.length, res_num); + + // 3. 统计HK责任 - 对应C代码行806-830 + logger.info("Recalculating HK responsibility sums"); + + // 对应C代码第808-814行:创建新的HKdata矩阵,只包含RES_NUM行 + // C代码:MatrixXd HKdata(RES_NUM, (P + 1)); + + // 添加数据验证 + if (data.getHkData() == null || data.getHkData().length == 0) { + logger.error("HK数据为空或长度为0"); + data.setCalculationStatus(CalculationStatus.FAILED); + return false; + } + + // 重要:C代码创建了新的RES_NUM行的HKdata,从原始数据复制前RES_NUM行 + // 对应C代码第808-814行 + float[][] HKdataForCalc = new float[res_num][P + 1]; + int copyRows = Math.min(res_num, data.getHkData().length); + + logger.debug("创建用于计算的HK数据矩阵: {}x{}, 从原始数据复制{}行", + res_num, P + 1, copyRows); + + for (int i = 0; i < copyRows; i++) { + for (int j = 0; j < P + 1; j++) { + if (j < data.getHkData()[i].length) { + HKdataForCalc[i][j] = data.getHkData()[i][j]; + } + } + } + + + logger.debug("调用HK sumResponsibility参数: HKdata[{}x{}], Udata[{}], TL={}, width={}", + HKdataForCalc.length, HKdataForCalc.length > 0 ? HKdataForCalc[0].length : 0, + Udata.length, TL, width); + + try { + // 对应C代码第819行:arrHKsum = SumHK(HKdata, Udata, wdith, colK, TL); + float[] sumHK = ResponsibilityCalculator.sumResponsibility( + HKdataForCalc, // 使用新创建的RES_NUM行的HK数据 + Udata, // 长度为TL的数组 + XIANE, + width, + P + 1, + TL // 传入TL参数 + ); + data.setSumHKData(sumHK); + logger.debug("HK责任计算完成,结果长度: {}", sumHK != null ? sumHK.length : "null"); + } catch (Exception e) { + logger.error("HK责任计算失败: " + e.getMessage(), e); + throw e; + } + + // 4. 统计FK责任 - 对应C代码行839-851 + logger.info("Recalculating FK responsibility sums"); + + // 对应C代码:虽然没有显式创建新的FKdata,但逻辑相同 + + // 添加数据验证 + if (data.getFkData() == null || data.getFkData().length == 0) { + logger.error("FK数据为空或长度为0"); + data.setCalculationStatus(CalculationStatus.FAILED); + return false; + } + + // 创建用于计算的FK数据矩阵(RES_NUM行) + float[][] FKdataForCalc = new float[res_num][P]; + int copyRowsFK = Math.min(res_num, data.getFkData().length); + + logger.debug("创建用于计算的FK数据矩阵: {}x{}, 从原始数据复制{}行", + res_num, P, copyRowsFK); + + for (int i = 0; i < copyRowsFK; i++) { + for (int j = 0; j < P; j++) { + if (j < data.getFkData()[i].length) { + FKdataForCalc[i][j] = data.getFkData()[i][j]; + } + } + } + + + logger.debug("调用FK sumResponsibility参数: FKdata[{}x{}], Udata[{}], TL={}, width={}", + FKdataForCalc.length, FKdataForCalc.length > 0 ? FKdataForCalc[0].length : 0, + Udata.length, TL, width); + + try { + // 对应C代码第840行:arrHKsum = SumHK(FKdata, Udata, wdith, colK, TL); + float[] sumFK = ResponsibilityCalculator.sumResponsibility( + FKdataForCalc, // 使用新创建的RES_NUM行的FK数据 + Udata, // 使用相同的Udata(长度TL) + XIANE, + width, + P, + TL // 传入TL参数 + ); + data.setSumFKData(sumFK); + logger.debug("FK责任计算完成,结果长度: {}", sumFK != null ? sumFK.length : "null"); + } catch (Exception e) { + logger.error("FK责任计算失败: " + e.getMessage(), e); + throw e; + } + + // 5. 标记计算成功 - 对应C代码行858 + data.setCalculationStatus(CalculationStatus.CALCULATED); + logger.info("Partial calculation completed successfully"); + + return true; + } + + /** + * 初始化部分计算数据 + * 对应C代码中的data_init_part函数 + */ + private boolean initializePartialCalculationData(HarmonicData data) { + // 设置变量 - 对应C代码行762-766 + int RES_NUM = data.getResponsibilityDataCount(); + P = data.getPowerNodeCount(); + TL = data.getWindowSize() + RES_NUM; + width = data.getWindowSize(); + XIANE = data.getHarmonicThreshold(); + + // 验证数据 - 对应C代码行756-778 + if ((RES_NUM + width) != data.getHarmonicCount()) { + logger.error("Data length mismatch: res_num({}) + win({}) != harm_num({})", + RES_NUM, width, data.getHarmonicCount()); + return false; + } + + if (width < HarmonicConstants.MIN_WIN_LEN || width > HarmonicConstants.MAX_WIN_LEN) { + logger.error("Invalid window size: {}", width); + return false; + } + + if (P > HarmonicConstants.MAX_P_NODE || TL > HarmonicConstants.MAX_P_NUM) { + logger.error("Data size exceeds limits"); + return false; + } + + // 验证FK和HK数据存在 + if (data.getFkData() == null || data.getHkData() == null) { + logger.error("FK or HK data is null"); + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/calculator/ResponsibilityCalculator.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/calculator/ResponsibilityCalculator.java new file mode 100644 index 0000000..027b8de --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/calculator/ResponsibilityCalculator.java @@ -0,0 +1,429 @@ +package com.njcn.product.advance.responsility.calculator; + +import com.njcn.product.advance.responsility.analysis.CanonicalCorrelationAnalysis; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 责任指标计算类 + * 计算谐波责任的各项指标 + * 严格对应C代码实现 + * + * @author hongawen + * @version 2.0 - 修复版本,严格对照C代码实现 + */ +public class ResponsibilityCalculator { + + private static final Logger logger = LoggerFactory.getLogger(ResponsibilityCalculator.class); + + /** + * 计算EK值(动态责任指标) + * 严格对应C代码中的DyEKCom函数(行300-357) + * + * @param correlationData 动态相关系数矩阵 [时间][节点] + * @param powerData 功率数据矩阵 [时间][节点] + * @param windowSize 窗口大小 + * @param nodeCount 节点数量 + * @param dataLength 数据长度 + * @return EK值矩阵 + */ + public static float[][] computeEK(float[][] correlationData, float[][] powerData, + int windowSize, int nodeCount, int dataLength) { + int slideLength = dataLength - windowSize; + float[][] ekData = new float[slideLength][nodeCount]; + float[][] akData = new float[slideLength][nodeCount]; + + logger.info("Computing EK values, slide length: {}", slideLength); + + // 计算AK值 - 对应C代码行307-319 + for (int i = 0; i < slideLength; i++) { + float sumPower = 0; + + // 计算功率总和 - 对应C代码行309-313 + for (int j = 0; j < nodeCount; j++) { + sumPower += powerData[i][j]; // 注意:这里用的是powerData[i][j] + } + + // 计算AK值 - 对应C代码行314-318 + for (int j = 0; j < nodeCount; j++) { + if (sumPower > 0) { + akData[i][j] = correlationData[i][j] * (powerData[i][j] / sumPower); + } else { + akData[i][j] = 0; + } + } + } + + // 归一化处理得到EK值 - 对应C代码行320-342 + for (int i = 0; i < slideLength; i++) { + // 重要:C代码初始化为0,而不是Float.MIN_VALUE/MAX_VALUE + // 对应C代码行322-323 + float maxValue = 0; + float minValue = 0; + + // 找最大最小值 - 对应C代码行322-334 + for (int j = 0; j < nodeCount; j++) { + if (akData[i][j] > maxValue) { + maxValue = akData[i][j]; + } + if (akData[i][j] < minValue) { + minValue = akData[i][j]; + } + } + + float range = maxValue - minValue; + + // 归一化 - 对应C代码行338-341 + for (int j = 0; j < nodeCount; j++) { + if (Math.abs(range) > 1e-10) { + ekData[i][j] = (akData[i][j] - minValue) / range; + } else { + ekData[i][j] = 0; + } + } + } + + logger.info("EK computation completed"); + + return ekData; + } + + /** + * 计算FK值(不包含背景的责任指标) + * 严格对应C代码中的DyFKCom函数(行358-389) + * + * @param ekData EK值矩阵 + * @param windowSize 窗口大小 + * @param nodeCount 节点数量 + * @param dataLength 数据长度 + * @return FK值矩阵 + */ + public static float[][] computeFK(float[][] ekData, int windowSize, + int nodeCount, int dataLength) { + int slideLength = dataLength - windowSize; + float[][] fkData = new float[slideLength][nodeCount]; + + logger.info("Computing FK values"); + + // 对应C代码行364-376 + for (int i = 0; i < slideLength; i++) { + float sumEK = 0; + + // 计算EK总和 - 对应C代码行366-370 + for (int j = 0; j < nodeCount; j++) { + sumEK += ekData[i][j]; + } + + // 计算FK值(归一化)- 对应C代码行372-375 + for (int j = 0; j < nodeCount; j++) { + if (sumEK > 0) { + fkData[i][j] = ekData[i][j] / sumEK; + } else { + fkData[i][j] = 0; + } + } + } + + logger.info("FK computation completed"); + + return fkData; + } + + /** + * 计算HK值(包含背景的责任指标) + * 严格对应C代码中的DyHKCom函数(行390-429) + * + * @param backgroundCanCor 背景典则相关系数(1-典则相关系数) + * @param ekData EK值矩阵 + * @param windowSize 窗口大小 + * @param nodeCount 节点数量 + * @param dataLength 数据长度 + * @return HK值矩阵 + */ + public static float[][] computeHK(float[] backgroundCanCor, float[][] ekData, + int windowSize, int nodeCount, int dataLength) { + int slideLength = dataLength - windowSize; + float[][] hkData = new float[slideLength][nodeCount + 1]; + float[][] newEK = new float[slideLength][nodeCount + 1]; + + logger.info("Computing HK values"); + + // 构建包含背景的EK矩阵 - 对应C代码行396-403 + for (int i = 0; i < slideLength; i++) { + // 复制原有EK值 + for (int j = 0; j < nodeCount; j++) { + newEK[i][j] = ekData[i][j]; + } + // 添加背景值 + newEK[i][nodeCount] = backgroundCanCor[i]; + } + + // 计算HK值 - 对应C代码行405-416 + for (int i = 0; i < slideLength; i++) { + float sumEK = 0; + + // 计算总和 - 对应C代码行407-411 + for (int j = 0; j < nodeCount + 1; j++) { + sumEK += newEK[i][j]; + } + + // 归一化得到HK值 - 对应C代码行412-415 + for (int j = 0; j < nodeCount + 1; j++) { + if (sumEK > 0) { + hkData[i][j] = newEK[i][j] / sumEK; + } else { + hkData[i][j] = 0; + } + } + } + + logger.info("HK computation completed"); + + return hkData; + } + + /** + * 计算超限时段的责任总和 + * 严格对应C代码中的SumHK函数(行431-461) + * + * @param responsibilityData 责任数据矩阵(FK或HK)[时间][节点] + * @param harmonicData 谐波数据(Udata) - 长度为TL + * @param threshold 谐波门槛(XIANE) + * @param windowSize 窗口大小 + * @param columnCount 列数(节点数或节点数+1) + * @param tl_num 对应C代码的TL参数(总数据点数) + * @return 各节点的责任总和百分比 + */ + public static float[] sumResponsibility(float[][] responsibilityData, float[] harmonicData, + float threshold, int windowSize, int columnCount, int tl_num) { + // 对应C代码:int slg = tl_num - width; + int slideLength = tl_num - windowSize; // 使用传入的tl_num计算,而不是从responsibilityData.length推断 + float[] sumData = new float[columnCount]; // 对应C代码中的 arrHKsum + double[] HKSum = new double[columnCount]; // 对应C代码中的 VectorXd HKSum - 使用double精度 + int exceedCount = 0; // 对应C代码中的 coutt + + // ===== 添加详细调试日志 ===== + logger.info("======= 开始 sumResponsibility 计算 ======="); + logger.info("输入参数:"); + logger.info(" threshold(阈值): {}", threshold); + logger.info(" windowSize(窗口大小): {}", windowSize); + logger.info(" columnCount(列数): {}", columnCount); + logger.info(" tl_num(总数据长度): {}", tl_num); + logger.info(" slideLength(滑动长度): {}", slideLength); + logger.info(" responsibilityData维度: {}x{}", + responsibilityData != null ? responsibilityData.length : "null", + responsibilityData != null && responsibilityData.length > 0 ? responsibilityData[0].length : "null"); + logger.info(" harmonicData长度: {}", harmonicData != null ? harmonicData.length : "null"); + + // 数据验证 + if (harmonicData == null) { + logger.error("错误: harmonicData为null!"); + throw new NullPointerException("harmonicData不能为null"); + } + + if (responsibilityData == null) { + logger.error("错误: responsibilityData为null!"); + throw new NullPointerException("responsibilityData不能为null"); + } + + + // 关键验证:检查数组长度是否充足 + // C代码中Udata长度是TL,循环遍历slg=TL-width个元素 + logger.info("数据验证: slideLength={}, harmonicData.length={}, responsibilityData.length={}", + slideLength, harmonicData.length, responsibilityData.length); + + if (harmonicData.length < slideLength) { + logger.error("!!!谐波数据长度不足!!!"); + logger.error("需要访问harmonicData[0]到harmonicData[{}], 但数组长度只有{}", + slideLength - 1, harmonicData.length); + throw new IllegalArgumentException( + String.format("谐波数据长度不足: 需要%d, 实际%d", slideLength, harmonicData.length)); + } + + if (responsibilityData.length < slideLength) { + logger.error("!!!责任数据行数不足!!!"); + logger.error("需要访问responsibilityData[0]到responsibilityData[{}], 但数组长度只有{}", + slideLength - 1, responsibilityData.length); + throw new IllegalArgumentException( + String.format("责任数据行数不足: 需要%d, 实际%d", slideLength, responsibilityData.length)); + } + + // ===== 添加数据分布统计 ===== + logger.info("谐波数据分析:"); + float harmonicMin = Float.MAX_VALUE, harmonicMax = Float.MIN_VALUE; + double harmonicSum = 0; + int preliminaryExceedCount = 0; + for (int i = 0; i < Math.min(slideLength, harmonicData.length); i++) { + float val = harmonicData[i]; + harmonicMin = Math.min(harmonicMin, val); + harmonicMax = Math.max(harmonicMax, val); + harmonicSum += val; + if (val > threshold) { + preliminaryExceedCount++; + } + } + double harmonicAvg = harmonicSum / Math.min(slideLength, harmonicData.length); + logger.info(" 谐波数据范围: [{}, {}]", harmonicMin, harmonicMax); + logger.info(" 谐波数据平均值: {}", harmonicAvg); + logger.info(" 设定阈值: {}", threshold); + logger.info(" 初步统计超限个数: {}/{} ({:.2f}%)", + preliminaryExceedCount, Math.min(slideLength, harmonicData.length), + preliminaryExceedCount * 100.0 / Math.min(slideLength, harmonicData.length)); + + // ===== 责任数据分析 ===== + logger.info("责任数据分析(检查前5行的归一化情况):"); + for (int i = 0; i < Math.min(5, responsibilityData.length); i++) { + float rowSum = 0; + for (int j = 0; j < responsibilityData[i].length; j++) { + rowSum += responsibilityData[i][j]; + } + logger.info(" 第{}行和: {} (期望值: 1.0, 偏差: {})", i, rowSum, Math.abs(rowSum - 1.0f)); + } + + // 统计超限时段的责任 - 对应C代码行437-449 + // 重要:C代码中有一个设计缺陷:coutt在每个j循环中被重置, + // 但最后计算百分比时使用的是最后一次j循环的coutt值 + // 为了严格保持一致,我们也要复现这个逻辑 + logger.info("===== 开始循环计算每列的累加值 ====="); + int[] exceedCountPerColumn = new int[columnCount]; // 记录每列的超限次数,用于调试 + + for (int j = 0; j < columnCount; j++) { + HKSum[j] = 0; + exceedCount = 0; // 对应C代码行440: coutt = 0; + logger.info("开始计算第{}列 (共{}列)", j, columnCount); + + double columnSum = 0; // 用于调试 + int columnExceedCount = 0; // 用于调试 + + for (int i = 0; i < slideLength; i++) { + // 添加越界检查 + if (i >= harmonicData.length) { + logger.error("!!!数组越界!!! 尝试访问harmonicData[{}], 但数组长度只有{}", + i, harmonicData.length); + logger.error("发生在: j={}, i={}", j, i); + throw new ArrayIndexOutOfBoundsException( + String.format("访问harmonicData[%d]越界, 数组长度=%d", i, harmonicData.length)); + } + + // 对应C代码行443-447 + if (harmonicData[i] > threshold) { + double currentResponsibility = responsibilityData[i][j]; + HKSum[j] += currentResponsibility; // 对应C代码行445 + exceedCount++; // 对应C代码行446 + columnSum += currentResponsibility; + columnExceedCount++; + + // 只打印前几个超限情况的详细信息 + if (columnExceedCount <= 3) { + logger.info(" 时刻i={}: 谐波值={} > 阈值={}, 责任值={}, 累加到{}", + i, harmonicData[i], threshold, currentResponsibility, HKSum[j]); + } + } + } + + exceedCountPerColumn[j] = columnExceedCount; + logger.info("第{}列计算完成: 累加值={}, 超限次数={}", j, HKSum[j], columnExceedCount); + } + // 注意:这里exceedCount保留的是最后一列(j=columnCount-1)的超限次数 + // 这与C代码的行为一致 + + logger.info("===== 循环计算完成 ====="); + logger.info("最终exceedCount={} (来自最后一列的计算)", exceedCount); + logger.info("各列超限次数对比: {}", java.util.Arrays.toString(exceedCountPerColumn)); + logger.info("各列累加值: {}", java.util.Arrays.toString(HKSum)); + + // 计算平均责任百分比 - 对应C代码行453-459 + logger.info("===== 开始计算最终百分比 ====="); + for (int i = 0; i < columnCount; i++) { + sumData[i] = 0; // 对应C代码行454 + } + + double totalPercentage = 0; // 用于统计总和 + for (int i = 0; i < columnCount; i++) { + if (exceedCount > 0) { + // 对应C代码行458: arrHKsum[i] = 100 * (HKSum(i)/coutt); + // 使用double进行计算,然后转换为float + double percentage = 100.0 * (HKSum[i] / (double)exceedCount); + sumData[i] = (float)percentage; + totalPercentage += percentage; + + logger.info("节点{}: 累加值={}, 除以超限次数={}, 百分比={}%", + i, HKSum[i], exceedCount, percentage); + } else { + logger.warn("节点{}: 超限次数为0,百分比设为0", i); + } + } + + logger.info("===== 计算结果汇总 ====="); + logger.info("使用的超限次数(分母): {}", exceedCount); + logger.info("各节点百分比: {}", java.util.Arrays.toString(sumData)); + logger.info("百分比总和: {}% (期望100%)", totalPercentage); + logger.info("偏差: {}%", Math.abs(totalPercentage - 100.0)); + + if (Math.abs(totalPercentage - 100.0) > 1.0) { + logger.warn("!!!注意!!! 百分比总和偏离100%超过1%,可能存在问题"); + } + + logger.info("======= sumResponsibility 计算完成 ======="); + return sumData; + } + + /** + * 计算超限时段的责任总和(兼容版本) + * 为了向后兼容,保留不带tl_num参数的版本 + * + * @param responsibilityData 责任数据矩阵(FK或HK)[时间][节点] + * @param harmonicData 谐波数据(Udata) + * @param threshold 谐波门槛(XIANE) + * @param windowSize 窗口大小 + * @param columnCount 列数(节点数或节点数+1) + * @return 各节点的责任总和百分比 + */ + public static float[] sumResponsibility(float[][] responsibilityData, float[] harmonicData, + float threshold, int windowSize, int columnCount) { + // 如果没有提供tl_num,从数据推断 + int tl_num = responsibilityData.length + windowSize; + return sumResponsibility(responsibilityData, harmonicData, threshold, windowSize, columnCount, tl_num); + } + + /** + * 计算所有节点的动态相关系数矩阵 + * 这个函数在主引擎中已经内联实现,这里保留作为辅助方法 + * + * @param powerData 功率数据矩阵 + * @param harmonicData 谐波数据 + * @param canonicalCorr 典则相关系数序列 + * @param windowSize 窗口大小 + * @param nodeCount 节点数量 + * @return 动态相关系数矩阵 + */ + public static float[][] computeCorrelationMatrix(float[][] powerData, float[] harmonicData, + float[] canonicalCorr, int windowSize, + int nodeCount) { + int slideLength = canonicalCorr.length; + float[][] correlationMatrix = new float[slideLength][nodeCount]; + + logger.info("Computing correlation matrix for all nodes"); + + for (int nodeIdx = 0; nodeIdx < nodeCount; nodeIdx++) { + // 提取单个节点的功率数据 + float[] nodePower = new float[powerData.length]; + for (int i = 0; i < powerData.length; i++) { + nodePower[i] = powerData[i][nodeIdx]; + } + + // 计算该节点的动态相关系数 + float[] nodeCorr = CanonicalCorrelationAnalysis + .slidingCorrelation(nodePower, harmonicData, canonicalCorr, windowSize); + + // 存储结果 + for (int i = 0; i < slideLength; i++) { + correlationMatrix[i][nodeIdx] = nodeCorr[i]; + } + } + + logger.info("Correlation matrix computation completed"); + + return correlationMatrix; + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/HistoryHarmonicController.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/HistoryHarmonicController.java new file mode 100644 index 0000000..0a0f00d --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/HistoryHarmonicController.java @@ -0,0 +1,68 @@ +package com.njcn.product.advance.responsility.controller; + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.influx.pojo.dto.HarmHistoryDataDTO; +import com.njcn.product.advance.harmonicUp.pojo.param.HistoryParam; +import com.njcn.product.advance.harmonicUp.pojo.vo.HistoryDataResultVO; +import com.njcn.product.advance.harmonicUp.service.HistoryResultService; +import com.njcn.product.advance.responsility.pojo.param.HistoryHarmParam; +import com.njcn.product.advance.eventSource.service.HistoryHarmonicService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-09-08 + * @Description: + */ +@Validated +@Slf4j +@RestController +@RequestMapping("/harmonic") +@Api(tags = "稳态数据分析") +@RequiredArgsConstructor +public class HistoryHarmonicController extends BaseController { + + private final HistoryHarmonicService historyHarmonicService; + + private final HistoryResultService historyResultService; + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/getHistoryHarmData") + @ApiOperation("获取谐波历史数据") + @ApiImplicitParam(name = "historyHarmParam", value = "谐波历史数据请求参数", required = true) + public HttpResult getHistoryHarmData(@RequestBody @Validated HistoryHarmParam historyHarmParam) { + String methodDescribe = getMethodDescribe("getHistoryHarmData"); + HarmHistoryDataDTO harmHistoryDataDTO = historyHarmonicService.getHistoryHarmData(historyHarmParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, harmHistoryDataDTO, methodDescribe); + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/getHistoryResult") + @ApiOperation("稳态数据分析") + @ApiImplicitParam(name = "historyParam", value = "稳态数据分析参数", required = true) + public HttpResult> getHistoryResult(@RequestBody @Validated HistoryParam historyParam) { + String methodDescribe = getMethodDescribe("getHistoryResult"); + List list = historyResultService.getHistoryResult(historyParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe); + } + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/ResponsibilityController.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/ResponsibilityController.java new file mode 100644 index 0000000..a8dbb8a --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/ResponsibilityController.java @@ -0,0 +1,114 @@ +package com.njcn.product.advance.responsility.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.product.advance.responsility.pojo.dto.RespDataDTO; +import com.njcn.product.advance.responsility.pojo.dto.ResponsibilityResult; +import com.njcn.product.advance.responsility.pojo.param.RespBaseParam; +import com.njcn.product.advance.responsility.pojo.param.ResponsibilityCalculateParam; +import com.njcn.product.advance.responsility.pojo.param.ResponsibilitySecondCalParam; +import com.njcn.product.advance.responsility.service.IRespDataResultService; +import com.njcn.product.advance.responsility.service.IRespDataService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月21日 10:06 + */ +@RestController +@RequestMapping("responsibility") +@Api(tags = "谐波责任划分-谐波责任数据处理") +@RequiredArgsConstructor +public class ResponsibilityController extends BaseController { + + private final IRespDataService respDataService; + + private final IRespDataResultService respDataResultService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/responsibilityList") + @ApiOperation("查询责任划分列表分页") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult> responsibilityList(@RequestBody @Validated RespBaseParam queryParam) { + String methodDescribe = getMethodDescribe("responsibilityList"); + Page list = respDataService.responsibilityList(queryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/deleteByIds") + @ApiOperation("删除责任划分数据") + @ApiImplicitParam(name = "ids", value = "待删除的责任id集合", required = true) + public HttpResult> deleteByIds(@RequestBody List ids) { + String methodDescribe = getMethodDescribe("deleteByIds"); + respDataService.deleteByIds(ids); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + + + + @PostMapping("getDynamicData") + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("动态谐波责任划分") + @ApiImplicitParam(name = "responsibilityCalculateParam", value = "谐波责任动态划分参数", required = true) + public HttpResult getDynamicData(@RequestBody @Validated ResponsibilityCalculateParam responsibilityCalculateParam) { + String methodDescribe = getMethodDescribe("getDynamicData"); + ResponsibilityResult datas = respDataService.getDynamicData(responsibilityCalculateParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, datas, methodDescribe); + } + + @PostMapping("getResponsibilityData") + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("二次计算责任划分") + @ApiImplicitParam(name = "responsibilitySecondCalParam", value = "二次计算责任划分参数", required = true) + public HttpResult getResponsibilityData(@RequestBody @Validated ResponsibilitySecondCalParam responsibilitySecondCalParam) { + String methodDescribe = getMethodDescribe("getResponsibilityData"); + ResponsibilityResult datas = respDataService.getResponsibilityData(responsibilitySecondCalParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, datas, methodDescribe); + } + + + @GetMapping("displayHistoryData") + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("回显历史责任划分结果") + @ApiImplicitParam(name = "id", value = "责任数据id", required = true) + public HttpResult> displayHistoryData(String id,Integer time) { + String methodDescribe = getMethodDescribe("displayHistoryData"); + List datas = respDataResultService.displayHistoryData(id,time); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, datas, methodDescribe); + } + + + + @PostMapping("systemDynamicData") + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("动态谐波责任划分") + @ApiImplicitParam(name = "responsibilityCalculateParam", value = "谐波责任动态划分参数", required = true) + public HttpResult systemDynamicData(@RequestBody @Validated ResponsibilityCalculateParam responsibilityCalculateParam) { + String methodDescribe = getMethodDescribe("getDynamicData"); + ResponsibilityResult datas = respDataService.getDynamicData(responsibilityCalculateParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, datas, methodDescribe); + } + + + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/UserDataController.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/UserDataController.java new file mode 100644 index 0000000..fd9acc4 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/controller/UserDataController.java @@ -0,0 +1,114 @@ +package com.njcn.product.advance.responsility.controller; + + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.dto.SelectOption; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.product.advance.responsility.pojo.dto.RespDataDTO; +import com.njcn.product.advance.responsility.pojo.param.UserDataIntegrityParam; +import com.njcn.product.advance.responsility.pojo.po.RespUserData; +import com.njcn.product.advance.responsility.pojo.po.RespUserDataIntegrity; +import com.njcn.product.advance.responsility.service.IRespUserDataIntegrityService; +import com.njcn.product.advance.responsility.service.IRespUserDataService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月13日 14:11 + */ +@RestController +@RequestMapping("responsibility") +@Api(tags = "谐波责任划分-用采数据处理") +@RequiredArgsConstructor +public class UserDataController extends BaseController { + + + private final IRespUserDataService respUserDataService; + + private final IRespUserDataIntegrityService respUserDataIntegrityService; + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/userDataList") + @ApiOperation("查询用户列表分页") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult> userDataList(@RequestBody @Validated BaseParam queryParam) { + String methodDescribe = getMethodDescribe("userDataList"); + Page list = respUserDataService.userDataList(queryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe); + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/userDataIntegrityList") + @ApiOperation("用采完整性不足列表") + @ApiImplicitParam(name = "userDataIntegrityParam", value = "查询参数", required = true) + public HttpResult> userDataIntegrityList(@RequestBody @Validated UserDataIntegrityParam userDataIntegrityParam) { + String methodDescribe = getMethodDescribe("userDataIntegrityList"); + Page list = respUserDataIntegrityService.userDataIntegrityList(userDataIntegrityParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/deleteUserDataByIds") + @ApiOperation("删除用采数据") + @ApiImplicitParam(name = "ids", value = "待删除用采数据id集合", required = true) + public HttpResult> deleteUserDataByIds(@RequestBody List ids) { + String methodDescribe = getMethodDescribe("deleteUserDataByIds"); + respUserDataService.deleteUserDataByIds(ids); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @GetMapping("/userDataSelect") + @ApiOperation("用采数据下拉") + public HttpResult> userDataSelect() { + String methodDescribe = getMethodDescribe("userDataSelect"); + List listOption = respUserDataService.userDataSelect(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, listOption, methodDescribe); + } + + /** + * 上传用采数据,并对用采数据进行数据分析并缓存 + * + * @param file 上传的表格 + */ + @PostMapping("uploadUserData") + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("上传用采数据") + public HttpResult uploadUserData(@ApiParam(value = "文件", required = true) @RequestPart("file") MultipartFile file, HttpServletResponse response) { + String methodDescribe = getMethodDescribe("uploadUserData"); + String fileName = file.getOriginalFilename(); + long fileSize = file.getSize() / 1024; + //判断文件大小 + if (fileSize > 3072) { + throw new BusinessException(CommonResponseEnum.FILE_SIZE_ERROR); + } + assert fileName != null; + if (!fileName.matches("^.+\\.(?i)(xlsx)$") && !fileName.matches("^.+\\.(?i)(xls)$")) { + throw new BusinessException(CommonResponseEnum.FILE_XLSX_ERROR); + } + respUserDataService.uploadUserData(file, response); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/imapper/DataHarmP.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/imapper/DataHarmP.java new file mode 100644 index 0000000..283b719 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/imapper/DataHarmP.java @@ -0,0 +1,30 @@ +package com.njcn.product.advance.responsility.imapper; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.njcn.influx.ano.IgnoreData; +import com.njcn.influx.utils.InstantDateDeserializer; +import com.njcn.influx.utils.InstantDateSerializer; +import lombok.Data; +import org.influxdb.annotation.Column; + +import java.time.Instant; + +/** + * @Author: cdf + * @CreateTime: 2025-09-18 + * @Description: + */ +@Data +public class DataHarmP { + @Column(name = "time") + @JsonSerialize(using = InstantDateSerializer.class) + @JsonDeserialize(using = InstantDateDeserializer.class) + private Instant time; + + @Column(name = "line_id") + private String lineId; + + @IgnoreData(true) + private Float value; +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespDataMapper.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespDataMapper.java new file mode 100644 index 0000000..0b36de1 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespDataMapper.java @@ -0,0 +1,26 @@ +package com.njcn.product.advance.responsility.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import com.njcn.product.advance.responsility.pojo.dto.RespDataDTO; +import com.njcn.product.advance.responsility.pojo.po.RespData; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2023-07-21 + */ +public interface RespDataMapper extends BaseMapper { + + Page page(@Param("page") Page objectPage, @Param("ew")QueryWrapper queryWrapper); + + void deleteByIds(@Param("ids") List ids); +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespDataResultMapper.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespDataResultMapper.java new file mode 100644 index 0000000..69ceb23 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespDataResultMapper.java @@ -0,0 +1,16 @@ +package com.njcn.product.advance.responsility.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.advance.responsility.pojo.po.RespDataResult; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2023-07-24 + */ +public interface RespDataResultMapper extends BaseMapper { + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespUserDataIntegrityMapper.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespUserDataIntegrityMapper.java new file mode 100644 index 0000000..45d739f --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespUserDataIntegrityMapper.java @@ -0,0 +1,20 @@ +package com.njcn.product.advance.responsility.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.advance.responsility.pojo.po.RespUserDataIntegrity; +import org.apache.ibatis.annotations.Param; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2023-07-13 + */ +public interface RespUserDataIntegrityMapper extends BaseMapper { + + Page page(@Param("page") Page objectPage, @Param("ew") QueryWrapper lambdaQueryWrapper); +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespUserDataMapper.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespUserDataMapper.java new file mode 100644 index 0000000..8aaf7ab --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/RespUserDataMapper.java @@ -0,0 +1,24 @@ +package com.njcn.product.advance.responsility.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.advance.responsility.pojo.po.RespUserData; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2023-07-13 + */ +public interface RespUserDataMapper extends BaseMapper { + + Page page(@Param("page")Page objectPage, @Param("ew")QueryWrapper respUserDataQueryWrapper); + + void deleteUserDataByIds(@Param("ids") List ids); +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespDataMapper.xml b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespDataMapper.xml new file mode 100644 index 0000000..17ffb84 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespDataMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + update + pqs_resp_data + set state = 0 + where + id + in + + #{item} + + + + diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespDataResultMapper.xml b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespDataResultMapper.xml new file mode 100644 index 0000000..7d2127c --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespDataResultMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespUserDataIntegrityMapper.xml b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespUserDataIntegrityMapper.xml new file mode 100644 index 0000000..ed8eb5f --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespUserDataIntegrityMapper.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespUserDataMapper.xml b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespUserDataMapper.xml new file mode 100644 index 0000000..306c59e --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/mapper/mapping/RespUserDataMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + update + pqs_resp_user_data + set state = 0 + where + id + in + + #{item} + + + + + diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/CacheQvvrData.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/CacheQvvrData.java new file mode 100644 index 0000000..246e38e --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/CacheQvvrData.java @@ -0,0 +1,47 @@ +package com.njcn.product.advance.responsility.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 当根据动态责任数据获取用户责任量化结果时,将需要的一些参数进行缓存 + * 比如 harmNum,pNode,HKData,FKData,HarmData,监测点的测量间隔,win窗口,最小公倍数 + * 以及FKData每个时间点的p对应的用户List + * + * @author hongawen + * @Date: 2019/4/29 16:06 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CacheQvvrData implements Serializable { + + private int pNode; + + private int harmNum; + + private float[] harmData; + + private float[][] fKData; + + private float[][] hKData; + + private List names; + + private int lineInterval; + + private int win; + + //最小公倍数 + private int minMultiple; + + //横轴时间 + private List times; + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HIKSDKStructure.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HIKSDKStructure.java new file mode 100644 index 0000000..fc82168 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HIKSDKStructure.java @@ -0,0 +1,33 @@ +package com.njcn.product.advance.responsility.model; + + +import com.sun.jna.Structure; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +/** + * @author: tw + * @date: 2022/1/12 10:57 + */ +public class HIKSDKStructure extends Structure { + protected List getFieldOrder(){ + List fieldOrderList = new ArrayList(); + for (Class cls = getClass(); + !cls.equals(HIKSDKStructure.class); + cls = cls.getSuperclass()) { + Field[] fields = cls.getDeclaredFields(); + int modifiers; + for (Field field : fields) { + modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { + continue; + } + fieldOrderList.add(field.getName()); + } + } + return fieldOrderList; + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HKDataStruct.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HKDataStruct.java new file mode 100644 index 0000000..00bb3a1 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HKDataStruct.java @@ -0,0 +1,37 @@ +package com.njcn.product.advance.responsility.model; + +import com.sun.jna.Structure; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +public class HKDataStruct extends Structure implements Serializable { + public float hk[] = new float[QvvrStruct.MAX_P_NODE + 1]; + + public HKDataStruct() { + } + + @Override + protected List getFieldOrder() { + return Collections.singletonList("hk"); + } + + public HKDataStruct(double[] hk) { + for (int i = 0; i < hk.length; i++) { + this.hk[i] = (float) hk[i]; + } + } + + public static class ByReference extends HKDataStruct implements Structure.ByReference { + public ByReference(double[] p) { + super(p); + } + } + + public static class ByValue extends HKDataStruct implements Structure.ByValue { + public ByValue(double[] p) { + super(p); + } + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HarmonicData.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HarmonicData.java new file mode 100644 index 0000000..9df294b --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/HarmonicData.java @@ -0,0 +1,287 @@ +package com.njcn.product.advance.responsility.model; + + +import com.njcn.product.advance.responsility.pojo.constant.CalculationMode; +import com.njcn.product.advance.responsility.pojo.constant.CalculationStatus; +import com.njcn.product.advance.responsility.pojo.constant.HarmonicConstants; + +/** + * 谐波数据结构类 + * 对应C语言中的harm_data_struct结构体 + * + * @author hongawen + * @version 1.0 + */ +public class HarmonicData { + + // 输入参数 + private CalculationMode calculationMode; // 计算标志 + private int harmonicCount; // 谐波数据个数 + private int powerCount; // 功率数据个数 + private int powerNodeCount; // 功率负荷节点数 + private int windowSize; // 数据窗大小 + private int responsibilityDataCount; // 代入的责任数据个数 + private float harmonicThreshold; // 谐波电压门槛 + + // 数据数组 + private float[] harmonicData; // 谐波数据序列 + private float[][] powerData; // 功率数据序列 + + // 输入输出数据 + private float[][] correlationData; // 动态相关系数数据序列 + private float[][] fkData; // 不包含背景动态谐波责任数据序列 + private float[][] hkData; // 包含背景动态谐波责任数据序列 + private float[] canonicalCorrelation; // 典则相关系数 + private float[] backgroundCanonicalCorr; // 包含背景典则相关系数 + + // 输出结果 + private CalculationStatus calculationStatus; // 计算状态 + private float[] sumFKData; // 不包含背景谐波责任 + private float[] sumHKData; // 包含背景谐波责任 + + /** + * 默认构造函数 + */ + public HarmonicData() { + this.calculationMode = CalculationMode.FULL_CALCULATION; + this.calculationStatus = CalculationStatus.NOT_CALCULATED; + this.windowSize = HarmonicConstants.DEFAULT_WINDOW_SIZE; + } + + /** + * Builder模式构造器 + */ + public static class Builder { + private HarmonicData data = new HarmonicData(); + + public Builder calculationMode(CalculationMode mode) { + data.calculationMode = mode; + return this; + } + + public Builder harmonicCount(int count) { + data.harmonicCount = count; + return this; + } + + public Builder powerCount(int count) { + data.powerCount = count; + return this; + } + + public Builder powerNodeCount(int count) { + data.powerNodeCount = count; + return this; + } + + public Builder windowSize(int size) { + data.windowSize = size; + return this; + } + + public Builder harmonicThreshold(float threshold) { + data.harmonicThreshold = threshold; + return this; + } + + public Builder harmonicData(float[] data) { + this.data.harmonicData = data; + return this; + } + + public Builder powerData(float[][] data) { + this.data.powerData = data; + return this; + } + + public HarmonicData build() { + // 验证数据 + validateData(); + // 初始化数组 + initializeArrays(); + return data; + } + + private void validateData() { + if (data.harmonicCount <= 0 || data.harmonicCount > HarmonicConstants.MAX_HARM_NUM) { + throw new IllegalArgumentException("Invalid harmonic count: " + data.harmonicCount); + } + if (data.powerCount <= 0 || data.powerCount > HarmonicConstants.MAX_P_NUM) { + throw new IllegalArgumentException("Invalid power count: " + data.powerCount); + } + if (data.powerNodeCount <= 0 || data.powerNodeCount > HarmonicConstants.MAX_P_NODE) { + throw new IllegalArgumentException("Invalid power node count: " + data.powerNodeCount); + } + if (data.windowSize < HarmonicConstants.MIN_WIN_LEN || + data.windowSize > HarmonicConstants.MAX_WIN_LEN) { + throw new IllegalArgumentException("Invalid window size: " + data.windowSize); + } + + // 验证数据对齐 + if (data.calculationMode == CalculationMode.FULL_CALCULATION) { + int ratio = data.harmonicCount / data.powerCount; + if (ratio * data.powerCount != data.harmonicCount || ratio < 1) { + throw new IllegalArgumentException("Harmonic data count must be integer multiple of power data count"); + } + } + } + + private void initializeArrays() { + if (data.harmonicData == null) { + data.harmonicData = new float[data.harmonicCount]; + } + if (data.powerData == null) { + data.powerData = new float[data.powerCount][data.powerNodeCount]; + } + + int resultCount = data.powerCount - data.windowSize; + if (resultCount > 0) { + data.correlationData = new float[resultCount][data.powerNodeCount]; + data.fkData = new float[resultCount][data.powerNodeCount]; + data.hkData = new float[resultCount][data.powerNodeCount + 1]; + data.canonicalCorrelation = new float[resultCount]; + data.backgroundCanonicalCorr = new float[resultCount]; + } + + data.sumFKData = new float[data.powerNodeCount]; + data.sumHKData = new float[data.powerNodeCount + 1]; + } + } + + // Getters and Setters + public CalculationMode getCalculationMode() { + return calculationMode; + } + + public void setCalculationMode(CalculationMode calculationMode) { + this.calculationMode = calculationMode; + } + + public int getHarmonicCount() { + return harmonicCount; + } + + public void setHarmonicCount(int harmonicCount) { + this.harmonicCount = harmonicCount; + } + + public int getPowerCount() { + return powerCount; + } + + public void setPowerCount(int powerCount) { + this.powerCount = powerCount; + } + + public int getPowerNodeCount() { + return powerNodeCount; + } + + public void setPowerNodeCount(int powerNodeCount) { + this.powerNodeCount = powerNodeCount; + } + + public int getWindowSize() { + return windowSize; + } + + public void setWindowSize(int windowSize) { + this.windowSize = windowSize; + } + + public int getResponsibilityDataCount() { + return responsibilityDataCount; + } + + public void setResponsibilityDataCount(int responsibilityDataCount) { + this.responsibilityDataCount = responsibilityDataCount; + } + + public float getHarmonicThreshold() { + return harmonicThreshold; + } + + public void setHarmonicThreshold(float harmonicThreshold) { + this.harmonicThreshold = harmonicThreshold; + } + + public float[] getHarmonicData() { + return harmonicData; + } + + public void setHarmonicData(float[] harmonicData) { + this.harmonicData = harmonicData; + } + + public float[][] getPowerData() { + return powerData; + } + + public void setPowerData(float[][] powerData) { + this.powerData = powerData; + } + + public float[][] getCorrelationData() { + return correlationData; + } + + public void setCorrelationData(float[][] correlationData) { + this.correlationData = correlationData; + } + + public float[][] getFkData() { + return fkData; + } + + public void setFkData(float[][] fkData) { + this.fkData = fkData; + } + + public float[][] getHkData() { + return hkData; + } + + public void setHkData(float[][] hkData) { + this.hkData = hkData; + } + + public float[] getCanonicalCorrelation() { + return canonicalCorrelation; + } + + public void setCanonicalCorrelation(float[] canonicalCorrelation) { + this.canonicalCorrelation = canonicalCorrelation; + } + + public float[] getBackgroundCanonicalCorr() { + return backgroundCanonicalCorr; + } + + public void setBackgroundCanonicalCorr(float[] backgroundCanonicalCorr) { + this.backgroundCanonicalCorr = backgroundCanonicalCorr; + } + + public CalculationStatus getCalculationStatus() { + return calculationStatus; + } + + public void setCalculationStatus(CalculationStatus calculationStatus) { + this.calculationStatus = calculationStatus; + } + + public float[] getSumFKData() { + return sumFKData; + } + + public void setSumFKData(float[] sumFKData) { + this.sumFKData = sumFKData; + } + + public float[] getSumHKData() { + return sumHKData; + } + + public void setSumHKData(float[] sumHKData) { + this.sumHKData = sumHKData; + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/PDataStruct.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/PDataStruct.java new file mode 100644 index 0000000..d9c9555 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/PDataStruct.java @@ -0,0 +1,55 @@ +package com.njcn.product.advance.responsility.model; + +import com.sun.jna.Structure; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + + +public class PDataStruct extends Structure implements Serializable { + public float p[] = new float[QvvrStruct.MAX_P_NODE]; + + public PDataStruct() { + } + + @Override + protected List getFieldOrder() { +// return null; + return Collections.singletonList("p"); + } + + public PDataStruct(double[] p) { + for (int i = 0; i < p.length; i++) { + this.p[i] = (float) p[i]; + } + } + + public static class ByReference extends PDataStruct implements Structure.ByReference { + public ByReference(double[] p) { + super(p); + } + } + + public static class ByValue extends PDataStruct implements Structure.ByValue { + public ByValue(double[] p) { + super(p); + } + } + + public float[] getP() { + return p; + } + + public void setP(float[] p) { + this.p = p; + } + + @Override + public String toString() { + return "PDataStruct{" + + "p=" + Arrays.toString(p) + + '}'; + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/QvvrDataEntity.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/QvvrDataEntity.java new file mode 100644 index 0000000..ce1ba8d --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/QvvrDataEntity.java @@ -0,0 +1,54 @@ +package com.njcn.product.advance.responsility.model; + + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class QvvrDataEntity implements Serializable { + + public static final int MAX_P_NODE= 200; //功率节点个数限制,按200个限制 + public static final int MAX_P_NUM= 96 * 100; //功率数据按15分钟间隔,100天处理 + public static final int MAX_HARM_NUM= 1440 * 100; //谐波数据按一分钟间隔,100天处理 + public static final int MAX_WIN_LEN=96 * 10; //按15分钟算10天 + public static final int MIN_WIN_LEN = 4; //按15分钟算1小时 + + + //输入参数 + public int calFlag; //计算标志,0默认用电压和功率数据计算相关系数和责任,1用代入的动态相关系数计算责任 + public int harmNum; //谐波数据个数 + public int pNum; //功率数据个数 + public int pNode; //功率负荷节点数 + public int win; //数据窗大小 + public int resNum; //代入的责任数据个数 + public float harmMk; //谐波电压门槛 + public float harmData[]; //谐波数据序列 + public float [][] pData; //功率数据序列 + public float [][] simData; //动态相关系数数据序列,可作为输入或者输出 + public float [][] fKData; //不包含背景动态谐波责任数据序列,可作为输入或者输出 + public float [][] hKData; //包含背景动态谐波责任数据序列,可作为输入或者输出 + public float [] core; //典则相关系数 + public float [] bjCore; //包含背景典则相关系数 + + //输出结果 + public int calOk; //是否计算正确标志,置位0表示未计算,置位1表示计算完成 + public float [] sumFKdata;//不包含背景谐波责任 + public float [] sumHKdata;//包含背景谐波责任 + + public QvvrDataEntity() { + calFlag = 0; + harmData = new float[MAX_HARM_NUM]; + pData = new float[MAX_P_NUM][MAX_P_NODE]; + simData = new float[MAX_P_NUM][MAX_P_NODE]; + fKData = new float[MAX_P_NUM][MAX_P_NODE]; + hKData = new float[MAX_P_NUM][MAX_P_NODE+1]; + core = new float[MAX_P_NUM]; + bjCore = new float[MAX_P_NUM]; + sumFKdata = new float[MAX_P_NODE]; + sumHKdata = new float[MAX_P_NODE + 1]; + } + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/QvvrStruct.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/QvvrStruct.java new file mode 100644 index 0000000..7426426 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/model/QvvrStruct.java @@ -0,0 +1,205 @@ +package com.njcn.product.advance.responsility.model; + +import com.sun.jna.Structure; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; + +public class QvvrStruct extends HIKSDKStructure implements Serializable { + public static final int MAX_P_NODE = 200; //功率节点个数限制,按200个限制 + public static final int MAX_P_NUM = 96 * 100; //功率数据按15分钟间隔,100天处理 + public static final int MAX_HARM_NUM = 1440 * 100; //谐波数据按一分钟间隔,100天处理 + public static final int MAX_WIN_LEN = 96 * 10; //按15分钟算10天 + public static final int MIN_WIN_LEN = 4; //按15分钟算1小时 + + + //输入参数 + public int cal_flag; //计算标志,0默认用电压和功率数据计算相关系数和责任,1用代入的动态相关系数计算责任 + public int harm_num; //谐波数据个数 + public int p_num; //功率数据个数 + public int p_node; //功率负荷节点数 + public int win; //数据窗大小 + public int res_num; //代入的责任数据个数 + public float harm_mk; //谐波电压门槛 + public float harm_data[]; //谐波数据序列 + public PDataStruct p_data[]; //功率数据序列 + public PDataStruct sim_data[]; //动态相关系数数据序列,可作为输入或者输出 + public PDataStruct FKdata[]; //不包含背景动态谐波责任数据序列,可作为输入或者输出 + public HKDataStruct HKdata[]; //包含背景动态谐波责任数据序列,可作为输入或者输出 + public float Core[]; //典则相关系数 + public float BjCore[]; //包含背景典则相关系数 + + //输出结果 + public int cal_ok; //是否计算正确标志,置位0表示未计算,置位1表示计算完成 + public float sumFKdata[];//不包含背景谐波责任 + public float sumHKdata[];//包含背景谐波责任 + + public QvvrStruct() { + cal_flag = 0; + harm_data = new float[MAX_HARM_NUM]; + p_data = new PDataStruct[MAX_P_NUM]; + sim_data = new PDataStruct[MAX_P_NUM]; + FKdata = new PDataStruct[MAX_P_NUM]; + HKdata = new HKDataStruct[MAX_P_NUM]; + Core = new float[MAX_P_NUM]; + BjCore = new float[MAX_P_NUM]; + sumFKdata = new float[MAX_P_NODE]; + sumHKdata = new float[MAX_P_NODE + 1]; + } + + public static class ByReference extends QvvrStruct implements Structure.ByReference { + + } + + public static class ByValue extends QvvrStruct implements Structure.ByValue { + + } + + + public PDataStruct[] getFKdata() { + return FKdata; + } + + public void setFKdata(PDataStruct[] FKdata) { + this.FKdata = FKdata; + } + + public HKDataStruct[] getHKdata() { + return HKdata; + } + + public void setHKdata(HKDataStruct[] HKdata) { + this.HKdata = HKdata; + } + + public float[] getSumFKdata() { + return sumFKdata; + } + + public void setSumFKdata(float[] sumFKdata) { + this.sumFKdata = sumFKdata; + } + + public float[] getSumHKdata() { + return sumHKdata; + } + + public void setSumHKdata(float[] sumHKdata) { + this.sumHKdata = sumHKdata; + } + + public int getCal_flag() { + return cal_flag; + } + + public void setCal_flag(int cal_flag) { + this.cal_flag = cal_flag; + } + + public int getHarm_num() { + return harm_num; + } + + public void setHarm_num(int harm_num) { + this.harm_num = harm_num; + } + + public float getHarm_mk() { + return harm_mk; + } + + public void setHarm_mk(float harm_mk) { + this.harm_mk = harm_mk; + } + + public float[] getHarm_data() { + return harm_data; + } + + public void setHarm_data(float[] harm_data) { + this.harm_data = harm_data; + } + + public float[] getCore() { + return Core; + } + + public void setCore(float[] core) { + Core = core; + } + + public float[] getBjCore() { + return BjCore; + } + + public void setBjCore(float[] bjCore) { + BjCore = bjCore; + } + + public int getCal_ok() { + return cal_ok; + } + + public void setCal_ok(int cal_ok) { + this.cal_ok = cal_ok; + } + + public int getP_num() { + return p_num; + } + + public void setP_num(int p_num) { + this.p_num = p_num; + } + + public int getP_node() { + return p_node; + } + + public void setP_node(int p_node) { + this.p_node = p_node; + } + + public int getWin() { + return win; + } + + public void setWin(int win) { + this.win = win; + } + + public int getRes_num() { + return res_num; + } + + public void setRes_num(int res_num) { + this.res_num = res_num; + } + + public PDataStruct[] getP_data() { + return p_data; + } + + public void setP_data(PDataStruct[] p_data) { + this.p_data = p_data; + } + + public PDataStruct[] getSim_data() { + return sim_data; + } + + public void setSim_data(PDataStruct[] sim_data) { + this.sim_data = sim_data; + } + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "cal_flag", "harm_num", "p_num", "p_node", "win", + "res_num", "harm_mk", "harm_data", "p_data", "sim_data", + "FKdata", "HKdata", "Core", "BjCore", "cal_ok", + "sumFKdata", + "sumHKdata"); + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/DealDataResult.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/DealDataResult.java new file mode 100644 index 0000000..dff5a23 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/DealDataResult.java @@ -0,0 +1,37 @@ +package com.njcn.product.advance.responsility.pojo.bo; + + +import lombok.Data; + +import java.io.Serializable; +import java.util.*; + +/** + * 处理用采原始数据得到的一个结果 + * + * @author hongawen + * @Date: 2019/4/26 15:57 + */ +@Data +public class DealDataResult implements Serializable { + + /*** + * String 户号@监测点号@户名 + * String yyyy-MM-dd + * Date 数据的详细日期 + * UserDataExcel 数据详细信息 + * 先以测量局号分组,再以该测量局号下每个日期分组 + */ + private Map>> totalData = new HashMap<>(); + + private List dates = new ArrayList<>(); + + /*** + * String 户号@监测点号@户名 + * String yyyy-MM-dd + * UserDataExcel 数据详细信息 + * 先以测量局号分组,再以该测量局号下每个日期分组 + */ + private Map>> totalListData = new HashMap<>(); + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/DealUserDataResult.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/DealUserDataResult.java new file mode 100644 index 0000000..2376341 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/DealUserDataResult.java @@ -0,0 +1,31 @@ +package com.njcn.product.advance.responsility.pojo.bo; + + +import com.njcn.product.advance.responsility.pojo.po.RespUserDataIntegrity; +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 针对天处理用采数据的结果实体 + * @author hongawen + * @Date: 2019/4/19 14:38 + */ +@Data +public class DealUserDataResult implements Serializable { + + //处理好的数据 + private List completed = new ArrayList<>(); + + //因当日完整性不足90,没有处理直接返回 + private List lack = new ArrayList<>(); + + //完整性不足时,用户信息描述 + private String detail; + + //完整性不足的具体信息 + private RespUserDataIntegrity respUserDataIntegrity; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/RespCommon.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/RespCommon.java new file mode 100644 index 0000000..302de45 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/RespCommon.java @@ -0,0 +1,25 @@ +package com.njcn.product.advance.responsility.pojo.bo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月28日 11:32 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RespCommon implements Serializable { + + private int pNum; + + private int userIntervalTime; + + private int lineInterval; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/RespHarmData.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/RespHarmData.java new file mode 100644 index 0000000..ae00208 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/RespHarmData.java @@ -0,0 +1,25 @@ +package com.njcn.product.advance.responsility.pojo.bo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月28日 11:38 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RespHarmData implements Serializable { + + private float[] harmData; + + private List harmTime; + + private float overLimit; +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/UserDataExcel.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/UserDataExcel.java new file mode 100644 index 0000000..2720573 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/bo/UserDataExcel.java @@ -0,0 +1,46 @@ +package com.njcn.product.advance.responsility.pojo.bo; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 提取用采数据或者将用采数据写进excel的实体类 + * + * @author hongawen + * @date 2019/4/11 10:43 + */ +@Data +public class UserDataExcel implements Serializable, Comparable { + + @Excel(name = "时间") + private String time; + + @Excel(name = "瞬时功率") + private BigDecimal work; + + @Excel(name = "户号") + private String userId; + + @Excel(name = "测量点局号") + private String line; + + @Excel(name = "户名") + private String userName; + + + @Override + public int compareTo(UserDataExcel o) { + + if (DateUtil.parse(this.time, DatePattern.NORM_DATETIME_PATTERN).getTime() > DateUtil.parse(o.getTime(), DatePattern.NORM_DATETIME_PATTERN).getTime()) { + return 1; + } else if (DateUtil.parse(this.time, DatePattern.NORM_DATETIME_PATTERN).getTime() == DateUtil.parse(o.getTime(), DatePattern.NORM_DATETIME_PATTERN).getTime()) { + return 0; + } + return -1; + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/CalculationMode.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/CalculationMode.java new file mode 100644 index 0000000..6cf45f8 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/CalculationMode.java @@ -0,0 +1,46 @@ +package com.njcn.product.advance.responsility.pojo.constant; + +/** + * 计算模式枚举 + * + * @author hongawen + * @version 1.0 + */ +public enum CalculationMode { + /** + * 完整计算模式 + * 使用电压和功率数据计算相关系数和责任 + */ + FULL_CALCULATION(0, "完整计算模式"), + + /** + * 部分重算模式 + * 使用已有的动态相关系数计算责任 + */ + PARTIAL_RECALCULATION(1, "部分重算模式"); + + private final int code; + private final String description; + + CalculationMode(int code, String description) { + this.code = code; + this.description = description; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public static CalculationMode fromCode(int code) { + for (CalculationMode mode : values()) { + if (mode.code == code) { + return mode; + } + } + throw new IllegalArgumentException("Invalid calculation mode code: " + code); + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/CalculationStatus.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/CalculationStatus.java new file mode 100644 index 0000000..359f757 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/CalculationStatus.java @@ -0,0 +1,49 @@ +package com.njcn.product.advance.responsility.pojo.constant; + +/** + * 计算状态枚举 + * + * @author hongawen + * @version 1.0 + */ +public enum CalculationStatus { + /** + * 未计算 + */ + NOT_CALCULATED(0, "未计算"), + + /** + * 计算完成 + */ + CALCULATED(1, "计算完成"), + + /** + * 计算失败 + */ + FAILED(-1, "计算失败"); + + private final int code; + private final String description; + + CalculationStatus(int code, String description) { + this.code = code; + this.description = description; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public static CalculationStatus fromCode(int code) { + for (CalculationStatus status : values()) { + if (status.code == code) { + return status; + } + } + return NOT_CALCULATED; + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/HarmonicConstants.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/HarmonicConstants.java new file mode 100644 index 0000000..75e439a --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/constant/HarmonicConstants.java @@ -0,0 +1,60 @@ +package com.njcn.product.advance.responsility.pojo.constant; + +/** + * 谐波责任量化系统常量定义 + * + * @author hongawen + * @version 1.0 + */ +public final class HarmonicConstants { + + private HarmonicConstants() { + // 防止实例化 + } + + /** + * 最大谐波数据个数 (1440*100) + * 按一分钟间隔,100天处理 + */ + public static final int MAX_HARM_NUM = 144000; + + /** + * 最大功率数据个数 (96*100) + * 按15分钟间隔,100天处理 + */ + public static final int MAX_P_NUM = 9600; + + /** + * 最大功率节点个数 + * 按200个限制 + */ + public static final int MAX_P_NODE = 200; + + /** + * 最大数据窗长度 (96*10) + * 按15分钟算10天 + */ + public static final int MAX_WIN_LEN = 960; + + /** + * 最小数据窗长度 + * 按15分钟算一小时 + */ + public static final int MIN_WIN_LEN = 4; + + /** + * 默认数据窗大小 + * 一天的数据量(15分钟间隔) + */ + public static final int DEFAULT_WINDOW_SIZE = 96; + + /** + * 数值计算精度阈值 + */ + public static final double EPSILON = 1e-10; + + /** + * 协方差计算最小值(避免除零) + */ + public static final double MIN_COVARIANCE = 1e-5; +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/CustomerData.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/CustomerData.java new file mode 100644 index 0000000..c615675 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/CustomerData.java @@ -0,0 +1,27 @@ +package com.njcn.product.advance.responsility.pojo.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author hongawen + * @Date: 2019/4/3 13:34 + */ +@Data +public class CustomerData implements Serializable { + + /*** + * 用户名称 + */ + private String customerName; + + + /*** + * 每时刻的数据 + */ + private List valueDatas=new ArrayList<>(); + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/CustomerResponsibility.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/CustomerResponsibility.java new file mode 100644 index 0000000..ac8f2a2 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/CustomerResponsibility.java @@ -0,0 +1,34 @@ +package com.njcn.product.advance.responsility.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author hongawen + * @Date: 2019/4/3 13:35 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CustomerResponsibility implements Serializable { + + /*** + * 用户名 + */ + private String customerName; + + /*** + * 责任值 + */ + private float responsibilityData; + + + /*** + * 监测点id + */ + private String monitorId; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/RespDataDTO.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/RespDataDTO.java new file mode 100644 index 0000000..ec223b1 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/RespDataDTO.java @@ -0,0 +1,29 @@ +package com.njcn.product.advance.responsility.pojo.dto; + +import com.njcn.product.advance.responsility.pojo.po.RespData; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月24日 17:49 + */ +@Data +public class RespDataDTO extends RespData implements Serializable { + + private String userDataName; + + private String gdName; + + private String subName; + + private String devName; + + private String ip; + + private String lineName; + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/ResponsibilityResult.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/ResponsibilityResult.java new file mode 100644 index 0000000..e6d37e4 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/dto/ResponsibilityResult.java @@ -0,0 +1,53 @@ +package com.njcn.product.advance.responsility.pojo.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 谐波责任量化最终结果,包括动态数据和责任量化结果 + * + * @author hongawen + * @Date: 2019/4/3 15:00 + */ +@Data +public class ResponsibilityResult implements Serializable { + + /*** + * 限值 + */ + private String limitValue; + + /*** + * 指定起始时间 + */ + private String limitSTime; + + /*** + * 指定结束时间 + */ + private String limitETime; + + /*** + * 责任划分结果存库数据 + */ + private String responsibilityDataIndex; + + /*** + * 每个用户的详细时刻的责任数据 + */ + private List datas; + + /*** + * 时间轴 + */ + private List timeDatas=new ArrayList<>(); + + /*** + * 用户责任的表格数据 + */ + private List responsibilities; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/HistoryHarmParam.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/HistoryHarmParam.java new file mode 100644 index 0000000..68bb27f --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/HistoryHarmParam.java @@ -0,0 +1,56 @@ +package com.njcn.product.advance.responsility.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.product.advance.eventSource.pojo.constant.HarmonicValidMessage; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import java.io.Serializable; +import java.util.List; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月19日 09:23 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class HistoryHarmParam implements Serializable { + + + @ApiModelProperty("开始时间") + @NotBlank(message = HarmonicValidMessage.DATA_NOT_BLANK) + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String searchBeginTime; + + @ApiModelProperty("结束时间") + @NotBlank(message = HarmonicValidMessage.DATA_NOT_BLANK) + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String searchEndTime; + + @NotBlank(message = HarmonicValidMessage.DATA_NOT_BLANK) + @ApiModelProperty("监测点索引") + private String lineId; + + + + @Max(1) + @Min(0) + @ApiModelProperty("0-电流 1-电压") + private int type; + + @Max(50) + @Min(2) + @ApiModelProperty("谐波次数") + private Integer time; + + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/PHistoryHarmParam.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/PHistoryHarmParam.java new file mode 100644 index 0000000..7e215e2 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/PHistoryHarmParam.java @@ -0,0 +1,34 @@ +package com.njcn.product.advance.responsility.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.product.advance.eventSource.pojo.constant.HarmonicValidMessage; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-09-18 + * @Description: + */ +@Data +public class PHistoryHarmParam { + + + @ApiModelProperty("开始时间") + @NotBlank(message = HarmonicValidMessage.DATA_NOT_BLANK) + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String searchBeginTime; + + @ApiModelProperty("结束时间") + @NotBlank(message = HarmonicValidMessage.DATA_NOT_BLANK) + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String searchEndTime; + + @NotBlank(message = HarmonicValidMessage.DATA_NOT_BLANK) + @ApiModelProperty("监测点索引") + private List lineIds; +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/RespBaseParam.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/RespBaseParam.java new file mode 100644 index 0000000..032aaf6 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/RespBaseParam.java @@ -0,0 +1,18 @@ +package com.njcn.product.advance.responsility.pojo.param; + +import com.njcn.web.pojo.param.BaseParam; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: cdf + * @CreateTime: 2025-09-09 + * @Description: + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RespBaseParam extends BaseParam { + + private String deptId; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/ResponsibilityCalculateParam.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/ResponsibilityCalculateParam.java new file mode 100644 index 0000000..00aa456 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/ResponsibilityCalculateParam.java @@ -0,0 +1,57 @@ +package com.njcn.product.advance.responsility.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import java.io.Serializable; +import java.util.List; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月21日 10:20 + */ +@Data +public class ResponsibilityCalculateParam implements Serializable { + + + @ApiModelProperty("开始时间") + @NotBlank(message = "参数不能为空") + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String searchBeginTime; + + @ApiModelProperty("结束时间") + @NotBlank(message = "参数不能为空") + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String searchEndTime; + + @NotBlank(message = "参数不能为空") + @ApiModelProperty("监测点索引") + private String lineId; + + @NotBlank(message = "参数不能为空") + @ApiModelProperty("用采数据索引") + private String userDataId; + + @Min(0) + @Max(1) + @ApiModelProperty("0-电流 1-电压") + private int type; + + @Min(2) + @Max(50) + @ApiModelProperty("谐波次数") + private Integer time; + + @ApiModelProperty("背景用户的下级") + private List userList; + + @ApiModelProperty("0或者null:配网环境,1.系统环境") + private Integer systemType; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/ResponsibilitySecondCalParam.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/ResponsibilitySecondCalParam.java new file mode 100644 index 0000000..dad8cbb --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/ResponsibilitySecondCalParam.java @@ -0,0 +1,44 @@ +package com.njcn.product.advance.responsility.pojo.param; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月24日 15:47 + */ +@Data +public class ResponsibilitySecondCalParam implements Serializable { + + @NotBlank(message = "参数不能为空") + @ApiModelProperty("责任数据索引") + private String resDataId; + + @Min(2) + @Max(50) + @ApiModelProperty("谐波次数") + private Integer time; + + @Min(0) + @Max(1) + @ApiModelProperty("0-电流 1-电压") + private int type; + + @ApiModelProperty("限值") + private float limitValue; + + @ApiModelProperty("开始时间(yyyy-MM-dd HH:mm:ss)") + @NotBlank(message = "参数不能为空") + private String limitStartTime; + + @ApiModelProperty("结束时间(yyyy-MM-dd HH:mm:ss)") + @NotBlank(message = "参数不能为空") + private String limitEndTime; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/UserDataIntegrityParam.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/UserDataIntegrityParam.java new file mode 100644 index 0000000..0b86cbf --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/param/UserDataIntegrityParam.java @@ -0,0 +1,19 @@ +package com.njcn.product.advance.responsility.pojo.param; + +import com.njcn.web.pojo.param.BaseParam; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年07月25日 14:14 + */ +@Data +public class UserDataIntegrityParam extends BaseParam implements Serializable { + + + private String userDataId; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespData.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespData.java new file mode 100644 index 0000000..0a567b2 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespData.java @@ -0,0 +1,55 @@ +package com.njcn.product.advance.responsility.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Getter; +import lombok.Setter; + +/** + * + * @author hongawen + * @since 2023-07-21 + */ +@Getter +@Setter +@TableName("pqs_resp_data") +public class RespData extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 责任量化数据结果 + */ + private String id; + + /** + * 监测点索引 + */ + private String lineId; + + /** + * 用采数据索引 + */ + private String userDataId; + + /** + * 谐波类型(谐波电压、谐波电流) + */ + private String dataType; + + /** + * 谐波次数 + */ + private String dataTimes; + + /** + * 计算的时间窗口 + */ + private String timeWindow; + + /** + * 状态(0 删除 1正常) + */ + private Integer state; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespDataResult.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespDataResult.java new file mode 100644 index 0000000..6950d6b --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespDataResult.java @@ -0,0 +1,80 @@ +package com.njcn.product.advance.responsility.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Getter; +import lombok.Setter; + +import java.util.Date; + +/** + *

+ * + *

+ * + * @author hongawen + * @since 2023-07-24 + */ +@Getter +@Setter +@TableName("pqs_resp_data_result") +public class RespDataResult extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 责任划分结果数据文件保存记录表 + */ + private String id; + + /** + * 责任划分结果表id + */ + private String resDataId; + + /** + * 限值 + */ + private Float limitValue; + + /*** + * 起始时间 + */ + private Date startTime; + + /*** + * 结束时间 + */ + private Date endTime; + + /** + * 谐波次数 + */ + private Integer time; + + /** + * 用户责任数据地址 + */ + private String userDetailData; + + /** + * 用户责任时间数据地址 + */ + private String timeData; + + /** + * 前10用户的每刻对应的责任数据地址 + */ + private String userResponsibility; + + /** + * 调用高级算法后的数据结果地址,提供二次计算 + */ + private String qvvrData; + + /** + * 状态(0 删除 1正常) + */ + private Integer state; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespUserData.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespUserData.java new file mode 100644 index 0000000..e45180a --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespUserData.java @@ -0,0 +1,57 @@ +package com.njcn.product.advance.responsility.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; + +/** + * + * @author hongawen + * @since 2023-07-13 + */ +@Getter +@Setter +@TableName("pqs_resp_user_data") +public class RespUserData extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 用采数据表id + */ + private String id; + + /** + * 用采数据名称 + */ + private String name; + + /** + * 起始日期 + */ + private LocalDate startTime; + + /** + * 截止日期 + */ + private LocalDate endTime; + + /** + * 0 存在数据不完整的;1 存在数据完整 + */ + private Integer integrity = 1; + + /** + * 用采数据存放地址 + */ + private String dataPath; + + /** + * 状态(0 删除 1正常) + */ + private Integer state; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespUserDataIntegrity.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespUserDataIntegrity.java new file mode 100644 index 0000000..cf69d08 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/pojo/po/RespUserDataIntegrity.java @@ -0,0 +1,63 @@ +package com.njcn.product.advance.responsility.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * + * @author hongawen + * @since 2023-07-13 + */ +@Getter +@Setter +@TableName("pqs_resp_user_data_integrity") +public class RespUserDataIntegrity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 用采数据完整不足表Id + */ + private String id; + + /** + * 用采数据表id + */ + private String userDataId; + + /** + * 用户名称 + */ + private String userName; + + /** + * 用户户号 + */ + private String userNo; + + /** + * 测量点局号 + */ + private String lineNo; + + /** + * 数据不完整的日期 + */ + private LocalDate lackDate; + + /** + * 完整率(低于90%会记录) + */ + private BigDecimal integrity; + + /** + * 状态(0 删除 1正常) + */ + private Integer state; + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IHarmonicResponsibilityService.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IHarmonicResponsibilityService.java new file mode 100644 index 0000000..3230b20 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IHarmonicResponsibilityService.java @@ -0,0 +1,62 @@ +package com.njcn.product.advance.responsility.service; + + +import com.njcn.product.advance.responsility.model.HarmonicData; + +/** + * 谐波责任计算服务接口 + * + * @author hongawen + * @version 1.0 + */ +public interface IHarmonicResponsibilityService { + + /** + * 执行谐波责任计算 + * + * @param data 输入的谐波数据 + * @return 计算是否成功 + */ + boolean calculate(HarmonicData data); + + /** + * 执行完整计算 + * + * @param harmonicData 谐波数据数组 + * @param powerData 功率数据矩阵 + * @param harmonicCount 谐波数据个数 + * @param powerCount 功率数据个数 + * @param nodeCount 节点数量 + * @param windowSize 窗口大小 + * @param threshold 谐波门槛 + * @return 计算结果 + */ + HarmonicData fullCalculation(float[] harmonicData, float[][] powerData, + int harmonicCount, int powerCount, int nodeCount, + int windowSize, float threshold); + + /** + * 执行部分重算 + * + * @param harmonicData 谐波数据数组 + * @param fkData FK数据矩阵 + * @param hkData HK数据矩阵 + * @param harmonicCount 谐波数据个数 + * @param nodeCount 节点数量 + * @param windowSize 窗口大小 + * @param responsibilityCount 责任数据个数 + * @param threshold 谐波门槛 + * @return 计算结果 + */ + HarmonicData partialCalculation(float[] harmonicData, float[][] fkData, float[][] hkData, + int harmonicCount, int nodeCount, int windowSize, + int responsibilityCount, float threshold); + + /** + * 验证输入数据的有效性 + * + * @param data 待验证的数据 + * @return 验证结果消息,null表示验证通过 + */ + String validateData(HarmonicData data); +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespDataResultService.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespDataResultService.java new file mode 100644 index 0000000..463a4ac --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespDataResultService.java @@ -0,0 +1,20 @@ +package com.njcn.product.advance.responsility.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.advance.responsility.pojo.dto.ResponsibilityResult; +import com.njcn.product.advance.responsility.pojo.po.RespDataResult; + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2023-07-24 + */ +public interface IRespDataResultService extends IService { + + List displayHistoryData(String id, Integer time); +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespDataService.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespDataService.java new file mode 100644 index 0000000..a17db8c --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespDataService.java @@ -0,0 +1,38 @@ +package com.njcn.product.advance.responsility.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; + +import com.njcn.product.advance.responsility.pojo.dto.RespDataDTO; +import com.njcn.product.advance.responsility.pojo.dto.ResponsibilityResult; +import com.njcn.product.advance.responsility.pojo.param.RespBaseParam; +import com.njcn.product.advance.responsility.pojo.param.ResponsibilityCalculateParam; +import com.njcn.product.advance.responsility.pojo.param.ResponsibilitySecondCalParam; +import com.njcn.product.advance.responsility.pojo.po.RespData; +import com.njcn.web.pojo.param.BaseParam; + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2023-07-21 + */ +public interface IRespDataService extends IService { + + ResponsibilityResult getDynamicDataOld(ResponsibilityCalculateParam responsibilityCalculateParam); + + ResponsibilityResult getDynamicData(ResponsibilityCalculateParam responsibilityCalculateParam); + + ResponsibilityResult getResponsibilityDataOld(ResponsibilitySecondCalParam responsibilitySecondCalParam); + + ResponsibilityResult getResponsibilityData(ResponsibilitySecondCalParam responsibilitySecondCalParam); + + Page responsibilityList(RespBaseParam queryParam); + + void deleteByIds(List ids); + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespUserDataIntegrityService.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespUserDataIntegrityService.java new file mode 100644 index 0000000..938b87c --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespUserDataIntegrityService.java @@ -0,0 +1,20 @@ +package com.njcn.product.advance.responsility.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.advance.responsility.pojo.param.UserDataIntegrityParam; +import com.njcn.product.advance.responsility.pojo.po.RespUserDataIntegrity; + + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2023-07-13 + */ +public interface IRespUserDataIntegrityService extends IService { + + Page userDataIntegrityList(UserDataIntegrityParam userDataIntegrityParam); +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespUserDataService.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespUserDataService.java new file mode 100644 index 0000000..9f718e6 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/IRespUserDataService.java @@ -0,0 +1,40 @@ +package com.njcn.product.advance.responsility.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; + +import com.njcn.common.pojo.dto.SelectOption; +import com.njcn.product.advance.responsility.pojo.bo.UserDataExcel; +import com.njcn.product.advance.responsility.pojo.po.RespUserData; +import com.njcn.web.pojo.param.BaseParam; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2023-07-13 + */ +public interface IRespUserDataService extends IService { + + /** + * 解析用采数据并保存 + * @author hongawen + * @date 2023/7/13 19:48 + * @param file 用采数据 + */ + void uploadUserData(MultipartFile file, HttpServletResponse response); + + Page userDataList(BaseParam queryParam); + + List userDataSelect(); + + void deleteUserDataByIds(List ids); + + List getUserDataExcelList(String userDataId); +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/HarmonicResponsibilityServiceImpl.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/HarmonicResponsibilityServiceImpl.java new file mode 100644 index 0000000..96b4241 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/HarmonicResponsibilityServiceImpl.java @@ -0,0 +1,163 @@ +package com.njcn.product.advance.responsility.service.impl; + + +import com.njcn.product.advance.responsility.calculator.HarmonicCalculationEngine; +import com.njcn.product.advance.responsility.model.HarmonicData; +import com.njcn.product.advance.responsility.pojo.constant.CalculationMode; +import com.njcn.product.advance.responsility.pojo.constant.HarmonicConstants; +import com.njcn.product.advance.responsility.service.IHarmonicResponsibilityService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +/** + * 谐波责任计算服务实现类 + * + * @author hongawen + * @version 1.0 + */ +@Service +public class HarmonicResponsibilityServiceImpl implements IHarmonicResponsibilityService { + + private static final Logger logger = LoggerFactory.getLogger(HarmonicResponsibilityServiceImpl.class); + + private final HarmonicCalculationEngine engine; + + public HarmonicResponsibilityServiceImpl() { + this.engine = new HarmonicCalculationEngine(); + } + + @Override + public boolean calculate(HarmonicData data) { + if (data == null) { + logger.error("Input data is null"); + return false; + } + + String validationError = validateData(data); + if (validationError != null) { + logger.error("Data validation failed: {}", validationError); + return false; + } + + long startTime = System.currentTimeMillis(); + boolean result = engine.calculate(data); + long endTime = System.currentTimeMillis(); + + logger.info("Calculation completed in {} ms, result: {}", (endTime - startTime), result); + + return result; + } + + @Override + public HarmonicData fullCalculation(float[] harmonicData, float[][] powerData, + int harmonicCount, int powerCount, int nodeCount, + int windowSize, float threshold) { + logger.info("Starting full calculation with harmonicCount={}, powerCount={}, nodeCount={}, windowSize={}", + harmonicCount, powerCount, nodeCount, windowSize); + + HarmonicData data = new HarmonicData.Builder() + .calculationMode(CalculationMode.FULL_CALCULATION) + .harmonicCount(harmonicCount) + .powerCount(powerCount) + .powerNodeCount(nodeCount) + .windowSize(windowSize) + .harmonicThreshold(threshold) + .harmonicData(harmonicData) + .powerData(powerData) + .build(); + + calculate(data); + + return data; + } + + @Override + public HarmonicData partialCalculation(float[] harmonicData, float[][] fkData, float[][] hkData, + int harmonicCount, int nodeCount, int windowSize, + int responsibilityCount, float threshold) { + logger.info("Starting partial calculation with harmonicCount={}, nodeCount={}, windowSize={}, responsibilityCount={}", + harmonicCount, nodeCount, windowSize, responsibilityCount); + + HarmonicData data = new HarmonicData(); + data.setCalculationMode(CalculationMode.PARTIAL_RECALCULATION); + data.setHarmonicCount(harmonicCount); + data.setPowerNodeCount(nodeCount); + data.setWindowSize(windowSize); + data.setResponsibilityDataCount(responsibilityCount); + data.setHarmonicThreshold(threshold); + data.setHarmonicData(harmonicData); + data.setFkData(fkData); + data.setHkData(hkData); + + // 初始化输出数组 + data.setSumFKData(new float[nodeCount]); + data.setSumHKData(new float[nodeCount + 1]); + + calculate(data); + + return data; + } + + @Override + public String validateData(HarmonicData data) { + if (data == null) { + return "Data object is null"; + } + + // 验证基本参数 + if (data.getHarmonicCount() <= 0 || data.getHarmonicCount() > HarmonicConstants.MAX_HARM_NUM) { + return String.format("Invalid harmonic count: %d (should be 1-%d)", + data.getHarmonicCount(), HarmonicConstants.MAX_HARM_NUM); + } + + if (data.getCalculationMode() == CalculationMode.FULL_CALCULATION) { + // 完整计算模式验证 + if (data.getPowerCount() <= 0 || data.getPowerCount() > HarmonicConstants.MAX_P_NUM) { + return String.format("Invalid power count: %d (should be 1-%d)", + data.getPowerCount(), HarmonicConstants.MAX_P_NUM); + } + + if (data.getPowerNodeCount() <= 0 || data.getPowerNodeCount() > HarmonicConstants.MAX_P_NODE) { + return String.format("Invalid power node count: %d (should be 1-%d)", + data.getPowerNodeCount(), HarmonicConstants.MAX_P_NODE); + } + + // 验证数据对齐 + int ratio = data.getHarmonicCount() / data.getPowerCount(); + if (ratio * data.getPowerCount() != data.getHarmonicCount()) { + return String.format("Harmonic count %d is not aligned with power count %d", + data.getHarmonicCount(), data.getPowerCount()); + } + + // 验证数据数组 + if (data.getHarmonicData() == null || data.getHarmonicData().length < data.getHarmonicCount()) { + return "Harmonic data array is null or insufficient"; + } + + if (data.getPowerData() == null || data.getPowerData().length < data.getPowerCount()) { + return "Power data array is null or insufficient"; + } + + } else if (data.getCalculationMode() == CalculationMode.PARTIAL_RECALCULATION) { + // 部分计算模式验证 + if (data.getResponsibilityDataCount() + data.getWindowSize() != data.getHarmonicCount()) { + return String.format("Data length mismatch: resNum(%d) + winSize(%d) != harmCount(%d)", + data.getResponsibilityDataCount(), data.getWindowSize(), data.getHarmonicCount()); + } + + if (data.getFkData() == null || data.getHkData() == null) { + return "FK or HK data is null for partial calculation"; + } + } + + // 验证窗口大小 + if (data.getWindowSize() < HarmonicConstants.MIN_WIN_LEN || + data.getWindowSize() > HarmonicConstants.MAX_WIN_LEN) { + return String.format("Invalid window size: %d (should be %d-%d)", + data.getWindowSize(), HarmonicConstants.MIN_WIN_LEN, HarmonicConstants.MAX_WIN_LEN); + } + + return null; // 验证通过 + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespDataResultServiceImpl.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespDataResultServiceImpl.java new file mode 100644 index 0000000..a7ce6ba --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespDataResultServiceImpl.java @@ -0,0 +1,88 @@ +package com.njcn.product.advance.responsility.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.StrPool; +import com.alibaba.fastjson.JSONArray; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.oss.utils.FileStorageUtil; +import com.njcn.product.advance.responsility.mapper.RespDataResultMapper; +import com.njcn.product.advance.responsility.pojo.dto.CustomerData; +import com.njcn.product.advance.responsility.pojo.dto.CustomerResponsibility; +import com.njcn.product.advance.responsility.pojo.dto.ResponsibilityResult; +import com.njcn.product.advance.responsility.pojo.po.RespData; +import com.njcn.product.advance.responsility.pojo.po.RespDataResult; +import com.njcn.product.advance.responsility.service.IRespDataResultService; +import com.njcn.product.advance.responsility.service.IRespDataService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2023-07-24 + */ +@Service +public class RespDataResultServiceImpl extends ServiceImpl implements IRespDataResultService { + + @Resource + private FileStorageUtil fileStorageUtil; + + @Lazy + @Resource + private IRespDataService respDataService; + + @Override + public List displayHistoryData(String id, Integer time) { + List responsibilityResults = new ArrayList<>(); + if (Objects.isNull(time)) { + RespData respData = respDataService.getById(id); + String[] split = respData.getDataTimes().split(StrPool.COMMA); + time = Integer.parseInt(split[0]); + } + LambdaQueryWrapper respDataResultLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respDataResultLambdaQueryWrapper.eq(RespDataResult::getResDataId, id) + .eq(RespDataResult::getTime, time); + List respDataResults = this.baseMapper.selectList(respDataResultLambdaQueryWrapper); + if (CollectionUtil.isNotEmpty(respDataResults)) { + ResponsibilityResult responsibilityResult; + for (RespDataResult respDataResult : respDataResults) { + responsibilityResult = new ResponsibilityResult(); + responsibilityResult.setLimitValue(String.valueOf(respDataResult.getLimitValue())); + responsibilityResult.setLimitSTime(DateUtil.format(respDataResult.getStartTime(), DatePattern.NORM_DATETIME_PATTERN)); + responsibilityResult.setLimitETime(DateUtil.format(respDataResult.getEndTime(), DatePattern.NORM_DATETIME_PATTERN)); + responsibilityResult.setResponsibilityDataIndex(respDataResult.getResDataId()); + //处理时间轴数据 + InputStream timeDataStream = fileStorageUtil.getFileStream(respDataResult.getTimeData()); + String timeDataStr = IoUtil.readUtf8(timeDataStream); + List timeData = JSONArray.parseArray(timeDataStr, Long.class); + responsibilityResult.setTimeDatas(timeData); + //处理用户详细数据 + InputStream userDetailStream = fileStorageUtil.getFileStream(respDataResult.getUserDetailData()); + String userDetailStr = IoUtil.readUtf8(userDetailStream); + List customerData = JSONArray.parseArray(userDetailStr, CustomerData.class); + responsibilityResult.setDatas(customerData); + //处理排名前10数据 + InputStream respStream = fileStorageUtil.getFileStream(respDataResult.getUserResponsibility()); + String respStr = IoUtil.readUtf8(respStream); + List respData = JSONArray.parseArray(respStr, CustomerResponsibility.class); + responsibilityResult.setResponsibilities(respData); + responsibilityResults.add(responsibilityResult); + } + } + return responsibilityResults; + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespDataServiceImpl.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespDataServiceImpl.java new file mode 100644 index 0000000..32e6ae8 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespDataServiceImpl.java @@ -0,0 +1,1756 @@ +package com.njcn.product.advance.responsility.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.StrPool; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.constant.ServerInfo; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.utils.FileUtil; +import com.njcn.common.utils.PubUtils; + +import com.njcn.db.mybatisplus.constant.DbConstant; +import com.njcn.influx.pojo.constant.InfluxDBTableConstant; +import com.njcn.influx.pojo.dto.HarmData; +import com.njcn.influx.pojo.dto.HarmHistoryDataDTO; +import com.njcn.influx.pojo.po.DataHarmPowerP; +import com.njcn.oss.constant.OssPath; +import com.njcn.oss.utils.FileStorageUtil; +import com.njcn.product.advance.eventSource.pojo.enums.AdvanceResponseEnum; +import com.njcn.product.advance.eventSource.service.HistoryHarmonicService; +import com.njcn.product.advance.responsility.imapper.DataHarmP; +import com.njcn.product.advance.responsility.mapper.RespDataMapper; +import com.njcn.product.advance.responsility.model.CacheQvvrData; +import com.njcn.product.advance.responsility.model.HarmonicData; +import com.njcn.product.advance.responsility.model.QvvrDataEntity; +import com.njcn.product.advance.responsility.pojo.bo.DealDataResult; +import com.njcn.product.advance.responsility.pojo.bo.RespCommon; +import com.njcn.product.advance.responsility.pojo.bo.RespHarmData; +import com.njcn.product.advance.responsility.pojo.bo.UserDataExcel; +import com.njcn.product.advance.responsility.pojo.constant.CalculationStatus; +import com.njcn.product.advance.responsility.pojo.dto.CustomerData; +import com.njcn.product.advance.responsility.pojo.dto.CustomerResponsibility; +import com.njcn.product.advance.responsility.pojo.dto.RespDataDTO; +import com.njcn.product.advance.responsility.pojo.dto.ResponsibilityResult; +import com.njcn.product.advance.responsility.pojo.param.*; +import com.njcn.product.advance.responsility.pojo.po.RespData; +import com.njcn.product.advance.responsility.pojo.po.RespDataResult; +import com.njcn.product.advance.responsility.service.IHarmonicResponsibilityService; +import com.njcn.product.advance.responsility.service.IRespDataResultService; +import com.njcn.product.advance.responsility.service.IRespDataService; +import com.njcn.product.advance.responsility.service.IRespUserDataService; +import com.njcn.product.advance.responsility.utils.ResponsibilityAlgorithm; +import com.njcn.product.terminal.mysqlTerminal.mapper.LineMapper; +import com.njcn.product.terminal.mysqlTerminal.mapper.OverlimitMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.DeptGetChildrenMoreDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LineDevGetDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.DeptGetLineParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Overlimit; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.LineDetailDataVO; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.LineDetailVO; +import com.njcn.product.terminal.mysqlTerminal.service.CommTerminalService; +import com.njcn.product.terminal.mysqlTerminal.service.LineService; +import com.njcn.product.terminal.mysqlTerminal.service.impl.CommTerminalServiceImpl; +import com.njcn.web.factory.PageFactory; +import com.njcn.web.utils.RequestUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2023-07-21 + */ +@Service +@RequiredArgsConstructor +public class RespDataServiceImpl extends ServiceImpl implements IRespDataService { + + + private final FileStorageUtil fileStorageUtil; + + + private final IRespDataResultService respDataResultService; + + private final IRespUserDataService respUserDataService; + + private final CommTerminalService commTerminalService; + + private final OverlimitMapper overlimitMapper; + + private final LineService lineService; + + private final HistoryHarmonicService historyHarmonicService; + + private final LineMapper lineMapper; + + private final IHarmonicResponsibilityService harmonicResponsibilityService; + + private static DateTimeFormatter format = DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN); + + public final static int SORT_10 = 10; + public final static int INTERVAL_TIME_1 = 1; + public final static int INTERVAL_TIME_3 = 3; + public final static int INTERVAL_TIME_5 = 5; + public final static int INTERVAL_TIME_15 = 15; + public final static int INTERVAL_TIME_30 = 30; + public final static int WINDOW_96 = 96; + public final static int WINDOW_48 = 48; + public final static int WINDOW_4 = 4; + + public final static int MINUS_2 = 2; + public final static int MINUS_3 = 3; + public final static int MINUS_4 = 4; + public final static int MINUS_5 = 5; + private final CommTerminalServiceImpl commTerminalServiceImpl; + + + @Override + public Page responsibilityList(RespBaseParam queryParam) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + //获取当前用户部门,根据部门获取监测点 + DeptGetLineParam deptGetLineParam = new DeptGetLineParam(); + deptGetLineParam.setDeptId(queryParam.getDeptId()); + deptGetLineParam.setServerName(ServerInfo.ADVANCE_BOOT); + List list = commTerminalService.deptGetLine(deptGetLineParam); + DeptGetChildrenMoreDTO dto = list.stream().collect(Collectors.toMap(DeptGetChildrenMoreDTO::getUnitId, Function.identity())).get(queryParam.getDeptId()); + if (CollUtil.isNotEmpty(dto.getLineBaseList())) { + List line = dto.getLineBaseList().stream().map(LineDevGetDTO::getPointId).collect(Collectors.toList()); + queryWrapper.in("pqs_resp_data.line_id", line); + } + if (ObjectUtil.isNotNull(queryParam)) { + //查询参数不为空,进行条件填充 + if (StrUtil.isNotBlank(queryParam.getSearchValue())) { + //仅提供用采名称 + queryWrapper.and(param -> param.like("pqs_resp_user_data.name", queryParam.getSearchValue())); + } + //排序 + if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) { + queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy())); + } else { + //没有排序参数,默认根据sort字段排序,没有排序字段的,根据updateTime更新时间排序 + queryWrapper.orderBy(true, false, "pqs_resp_data.create_time"); + } + queryWrapper.between("pqs_resp_data.create_time", DateUtil.beginOfDay(DateUtil.parse(queryParam.getSearchBeginTime())), DateUtil.endOfDay(DateUtil.parse(queryParam.getSearchEndTime()))); + } + queryWrapper.eq("pqs_resp_data.state", DataStateEnum.ENABLE.getCode()); + Page page = this.baseMapper.page(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), queryWrapper); + List records = page.getRecords(); + if (CollectionUtil.isNotEmpty(records)) { + //获取该监测点的详细信息 + for (RespDataDTO respDataDTO : records) { + LineDetailVO lineSubGdDetail = lineMapper.getLineSubGdDetail(respDataDTO.getLineId()); + BeanUtil.copyProperties(lineSubGdDetail, respDataDTO); + } + } + return page.setRecords(records); + } + + + /*** + * 批量逻辑删除责任划分数据 + * @author hongawen + * @date 2023/7/24 19:16 + */ + @Override + public void deleteByIds(List ids) { + this.baseMapper.deleteByIds(ids); + } + + + @Override + @Deprecated + public ResponsibilityResult getDynamicDataOld(ResponsibilityCalculateParam responsibilityCalculateParam) { + ResponsibilityResult result = new ResponsibilityResult(); + //调用c++依赖需要待初始化的参数 + int pNode, pNum, win, harmNum; + float harmMk; + List userDataExcels = respUserDataService.getUserDataExcelList(responsibilityCalculateParam.getUserDataId()); + //开始处理,根据接口参数需求,需要节点数(用户数,用户名+监测点号为一个用户),时间范围内功率数据 + DealDataResult dealDataResult = RespUserDataServiceImpl.getStanderData(userDataExcels, 1); + List dateStr = PubUtils.getTimes(DateUtil.parse(responsibilityCalculateParam.getSearchBeginTime(), DatePattern.NORM_DATE_PATTERN), DateUtil.parse(responsibilityCalculateParam.getSearchEndTime(), DatePattern.NORM_DATE_PATTERN)); + Map>> finalData = getFinalUserData(dealDataResult, dateStr); + //至此,finalData便是我们最终获得的用于计算责任数据,第一个参数节点数值pNode获取到 + //第一个参数pNode + pNode = finalData.size(); + if (pNode < 1) { + //没有合理的用采数据直接返回 + throw new BusinessException(AdvanceResponseEnum.USER_DATA_P_NODE_PARAMETER_ERROR); + } + //第二个参数pNum,根据起始时间和截止时间以及监测点测量间隔计算数量 + RespCommon pNumAndInterval = getPNumAndInterval(finalData, responsibilityCalculateParam.getLineId(), dateStr); + pNum = pNumAndInterval.getPNum(); + int userIntervalTime = pNumAndInterval.getUserIntervalTime(); + int lineInterval = pNumAndInterval.getLineInterval(); + //第三个参数win,根据起始时间和截止时间的间隔 + if (dateStr.size() > 1) { + if (userIntervalTime == INTERVAL_TIME_15) { + win = WINDOW_96; + } else { + win = WINDOW_48; + } + } else { + win = WINDOW_4; + } + //第四个参数harmMk,默认为0f + harmMk = 0f; + //第五个参数harmNum,与功率数据保持一致 + harmNum = pNum; + //至此基础数据组装完毕,开始组装功率数据和谐波数据 + //先做谐波数据,理论上到这步的时候,谐波数据是满足完整性并已经补充完整性到100%,此处需要将谐波数据与功率数据长度匹配上 + RespHarmData respHarmData = getRespHarmData(responsibilityCalculateParam, lineInterval); + //harmData填充完毕后,开始组装功率数据 + //首先获取当前时间内的各个用户的数据 + Map> originalPData = new HashMap<>(16); + List names = new ArrayList<>(); + Set userNamesFinal = finalData.keySet(); + for (String userName : userNamesFinal) { + List tempData = new ArrayList<>(); + //根据日期将日期数据全部获取出来z + Map> tempResult = finalData.get(userName); + for (String date : dateStr) { + tempData.addAll(tempResult.get(date)); + } + //按日期排序 + Collections.sort(tempData); + originalPData.put(userName, tempData); + names.add(userName); + } + //然后开始组装数据 + float[][] pData = new float[QvvrDataEntity.MAX_P_NUM][QvvrDataEntity.MAX_P_NODE]; + for (int i = 0; i < names.size(); i++) { + //当前某用户测量节点的所有数据 + List userDataExcelBodies1 = originalPData.get(names.get(i)); + for (int k = 0; k < userDataExcelBodies1.size(); k++) { + float[] pDataStruct = pData[k]; + if (pDataStruct == null) { + pDataStruct = new float[QvvrDataEntity.MAX_P_NODE]; + } + float[] p = pDataStruct; + p[i] = userDataExcelBodies1.get(k).getWork().floatValue(); + pData[k] = pDataStruct; + } + } + //至此功率数据也组装完毕,调用友谊提供的接口 + QvvrDataEntity qvvrDataEntity = new QvvrDataEntity(); + qvvrDataEntity.calFlag = 0; + qvvrDataEntity.pNode = pNode; + qvvrDataEntity.pNum = pNum; + qvvrDataEntity.win = win; + qvvrDataEntity.harmNum = harmNum; + qvvrDataEntity.harmMk = harmMk; + qvvrDataEntity.pData = pData; + qvvrDataEntity.harmData = respHarmData.getHarmData(); + ResponsibilityAlgorithm responsibilityAlgorithm = new ResponsibilityAlgorithm(); + qvvrDataEntity = responsibilityAlgorithm.getResponsibilityResult(qvvrDataEntity); + //至此接口调用结束,开始组装动态责任数据和用户责任量化结果 + //首先判断cal_ok的标识位是否为1,为0表示程序没有计算出结果 + if (qvvrDataEntity.calOk == 0) { + throw new BusinessException(AdvanceResponseEnum.RESPONSIBILITY_PARAMETER_ERROR); + } + //没问题后,先玩动态责任数据 + CustomerData[] customerDatas = new CustomerData[qvvrDataEntity.pNode]; + float[][] fKdata/*无背景的动态责任数据*/ = qvvrDataEntity.getFKData(); + //第一个时间节点是起始时间+win窗口得到的时间 + Date sTime = DateUtil.parse(dateStr.get(0).concat(" 00:00:00"), DatePattern.NORM_DATETIME_PATTERN); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(sTime); + calendar.add(Calendar.MINUTE, (win - 1) * userIntervalTime); + List timeDatas = new ArrayList<>(); + for (int i = 0; i < qvvrDataEntity.pNum - qvvrDataEntity.win; i++) { + calendar.add(Calendar.MINUTE, userIntervalTime); + //一个时间点所有的用户数据 + float[] fKdatum = fKdata[i]; + for (int k = 0; k < qvvrDataEntity.pNode; k++) { + CustomerData customerData = customerDatas[k]; + if (null == customerData) { + customerData = new CustomerData(); + customerData.setCustomerName(names.get(k)); + } + List valueDatas = customerData.getValueDatas(); + Float valueTemp = fKdatum[k]; + if (valueTemp.isNaN()) { + valueTemp = 0.0f; + } + valueDatas.add(valueTemp); + customerData.setValueDatas(valueDatas); + customerDatas[k] = customerData; + } + timeDatas.add(calendar.getTimeInMillis()); + } + //OK拿到所有测量点的数据了,现在就是看如何将相同户号的动态数据进行算术和求值,之前的用户name为:户号@测量点号@用户名 + Map> customerDataTemp = new HashMap<>(16); + for (CustomerData data : customerDatas) { + String customerName = data.getCustomerName(); + String[] customerInfo = customerName.split("@"); + String name = customerInfo[2] + "(" + customerInfo[0] + ")"; + List customerData = customerDataTemp.get(name); + CustomerData temp = data; + temp.setCustomerName(name); + if (CollectionUtils.isEmpty(customerData)) { + customerData = new ArrayList<>(); + } + customerData.add(temp); + customerDataTemp.put(name, customerData); + } + //动态数据组装完成后,开始组装责任数据 + List customerResponsibilities = getCustomerResponsibilityData(names, qvvrDataEntity.sumFKdata, qvvrDataEntity.pNode); + //根据前十的用户数据,获取这些用户的动态责任数据 + List customerData = new ArrayList<>(); + for (CustomerResponsibility customerResponsibility : customerResponsibilities) { + String cusName = customerResponsibility.getCustomerName(); + List customerData1 = customerDataTemp.get(cusName); + if (CollectionUtils.isEmpty(customerData1)) { + continue; + } + if (customerData1.size() == 1) { + //表示用户唯一的 + customerData.add(customerData1.get(0)); + } else { + // 表示用户可能包含多个监测点号,需要进行数据累加 + CustomerData customerDataT = new CustomerData(); + customerDataT.setCustomerName(cusName); + //进行数值累加 + List valueDatas = new ArrayList<>(); + for (int i = 0; i < customerData1.get(0).getValueDatas().size(); i++) { + float original = 0.0f; + for (CustomerData data : customerData1) { + original = original + data.getValueDatas().get(i); + } + valueDatas.add(original); + } + customerDataT.setValueDatas(valueDatas); + customerData.add(customerDataT); + } + } + result.setDatas(customerData); + result.setTimeDatas(timeDatas); + result.setResponsibilities(customerResponsibilities); + //此次的操作进行入库操作responsibilityData表数据 + //根据监测点名称+谐波框选的时间来查询,是否做过责任量化 + String timeWin = responsibilityCalculateParam.getSearchBeginTime().replaceAll(StrPool.DASHED, "").concat(StrPool.DASHED).concat(responsibilityCalculateParam.getSearchEndTime().replaceAll(StrPool.DASHED, "")); + String type = responsibilityCalculateParam.getType() == 0 ? "谐波电流" : "谐波电压"; + //为了避免有监测点名称重复的,最终还是选择使用监测点索引来判断唯一性 + LambdaQueryWrapper respDataLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respDataLambdaQueryWrapper.eq(RespData::getLineId, responsibilityCalculateParam.getLineId()) + .eq(RespData::getUserDataId, responsibilityCalculateParam.getUserDataId()) + .eq(RespData::getTimeWindow, timeWin) + .eq(RespData::getDataType, type) + .eq(RespData::getState, DataStateEnum.ENABLE.getCode()); + List responsibilityDataTemp = this.baseMapper.selectList(respDataLambdaQueryWrapper); + RespData responsibilityData; + if (CollectionUtils.isEmpty(responsibilityDataTemp)) { + responsibilityData = new RespData(); + //库中没有记录则可以新建数据进行插入 + responsibilityData.setLineId(responsibilityCalculateParam.getLineId()); + responsibilityData.setUserDataId(responsibilityCalculateParam.getUserDataId()); + responsibilityData.setDataType(type); + responsibilityData.setDataTimes(responsibilityCalculateParam.getTime().toString()); + responsibilityData.setTimeWindow(timeWin); + responsibilityData.setState(DataStateEnum.ENABLE.getCode()); + //进行插入操作 + this.baseMapper.insert(responsibilityData); + } else { + //库中存在记录只需要判断次数进行数据更新 + responsibilityData = responsibilityDataTemp.get(0); + String times = responsibilityData.getDataTimes(); + List timesList = Stream.of(times.split(StrPool.COMMA)).collect(Collectors.toList()); + Integer time = responsibilityCalculateParam.getTime(); + if (!timesList.contains(time.toString())) { + timesList.add(time.toString()); + timesList = timesList.stream().sorted().collect(Collectors.toList()); + responsibilityData.setDataTimes(String.join(StrPool.COMMA, timesList)); + } + //执行更新操作 + this.baseMapper.updateById(responsibilityData); + } + //入库完毕之后,需要将必要数据进行序列化存储,方便后期的重复利用 + /* + * 需要序列化三种数据结构 1 cal_flag置为1时需要的一些列参数的CacheQvvrData 2 cal_flag为0时的,动态结果。3 用户责任量化结果 + * 其中1/2都只需要一个文件即可 + * 3因为用户限值的变化调整,可能存在很多个文件,具体根据用户的选择而定 + * + * 路径的结构为,temPath+userData+excelName+type+timeWin+lineIndex+time+文件名 + * 用户责任量化结果,需要再细化到限值 + */ + //首先判断有没有存储记录,没有则存储,有就略过 指定测点、时间窗口、谐波类型、谐波次数判断唯一性 + LambdaQueryWrapper respDataResultLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respDataResultLambdaQueryWrapper.eq(RespDataResult::getResDataId, responsibilityData.getId()) + .eq(RespDataResult::getTime, responsibilityCalculateParam.getTime()) + .eq(RespDataResult::getStartTime, DateUtil.parse(responsibilityCalculateParam.getSearchBeginTime() + " 00:00:00", DatePattern.NORM_DATETIME_PATTERN)) + .eq(RespDataResult::getEndTime, DateUtil.parse(responsibilityCalculateParam.getSearchEndTime() + " 23:59:59", DatePattern.NORM_DATETIME_PATTERN)) + .eq(RespDataResult::getLimitValue, respHarmData.getOverLimit()); + RespDataResult respDataResult = respDataResultService.getOne(respDataResultLambdaQueryWrapper); + if (Objects.isNull(respDataResult)) { + respDataResult = new RespDataResult(); + respDataResult.setResDataId(responsibilityData.getId()); + respDataResult.setTime(responsibilityCalculateParam.getTime()); + respDataResult.setStartTime(DateUtil.parse(responsibilityCalculateParam.getSearchBeginTime() + " 00:00:00", DatePattern.NORM_DATETIME_PATTERN)); + respDataResult.setEndTime(DateUtil.parse(responsibilityCalculateParam.getSearchEndTime() + " 23:59:59", DatePattern.NORM_DATETIME_PATTERN)); + respDataResult.setLimitValue(respHarmData.getOverLimit()); + //时间横轴数据 timeDatas + JSONArray timeDataJson = JSONArray.parseArray(JSON.toJSONString(timeDatas)); + InputStream timeDataStream = IoUtil.toUtf8Stream(timeDataJson.toString()); + String timeDataPath = fileStorageUtil.uploadStream(timeDataStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setTimeData(timeDataPath); + //用户每时刻对应的责任数据 + JSONArray customerDataJson = JSONArray.parseArray(JSON.toJSONString(customerData)); + InputStream customerStream = IoUtil.toUtf8Stream(customerDataJson.toString()); + String customerPath = fileStorageUtil.uploadStream(customerStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setUserDetailData(customerPath); + //调用qvvr生成的中间数据 + CacheQvvrData cacheQvvrData = new CacheQvvrData(qvvrDataEntity.getPNode(), qvvrDataEntity.getHarmNum(), qvvrDataEntity.getHarmData(), qvvrDataEntity.fKData, qvvrDataEntity.hKData, names, userIntervalTime, qvvrDataEntity.win, userIntervalTime, respHarmData.getHarmTime()); + String cacheJson = PubUtils.obj2json(cacheQvvrData); + InputStream cacheQvvrDataStream = IoUtil.toUtf8Stream(cacheJson); + String cacheQvvrDataPath = fileStorageUtil.uploadStream(cacheQvvrDataStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setQvvrData(cacheQvvrDataPath); + //用户前10数据存储 + JSONArray customerResJson = JSONArray.parseArray(JSON.toJSONString(customerResponsibilities)); + InputStream customerResStream = IoUtil.toUtf8Stream(customerResJson.toString()); + String customerResPath = fileStorageUtil.uploadStream(customerResStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setUserResponsibility(customerResPath); + respDataResultService.save(respDataResult); + } + //防止过程中创建了大量的对象,主动调用下GC处理 + System.gc(); + result.setResponsibilityDataIndex(responsibilityData.getId()); + return result; + } + + @Override + public ResponsibilityResult getDynamicData(ResponsibilityCalculateParam responsibilityCalculateParam) { + ResponsibilityResult result = new ResponsibilityResult(); + //调用c++依赖需要待初始化的参数 + int pNode, pNum, win, harmNum; + float harmMk; + List userDataExcels = new ArrayList<>(); + if(Objects.nonNull(responsibilityCalculateParam.getSystemType()) && responsibilityCalculateParam.getSystemType()==1){ + userDataExcels = test(responsibilityCalculateParam.getUserList(),responsibilityCalculateParam.getSearchBeginTime(),responsibilityCalculateParam.getSearchEndTime()); + }else { + userDataExcels = respUserDataService.getUserDataExcelList(responsibilityCalculateParam.getUserDataId()); + + } + //开始处理,根据接口参数需求,需要节点数(用户数,用户名+监测点号为一个用户),时间范围内功率数据 + DealDataResult dealDataResult = RespUserDataServiceImpl.getStanderData(userDataExcels, 1); + List dateStr = PubUtils.getTimes(DateUtil.parse(responsibilityCalculateParam.getSearchBeginTime(), DatePattern.NORM_DATE_PATTERN), DateUtil.parse(responsibilityCalculateParam.getSearchEndTime(), DatePattern.NORM_DATE_PATTERN)); + Map>> finalData = getFinalUserData(dealDataResult, dateStr); + //至此,finalData便是我们最终获得的用于计算责任数据,第一个参数节点数值pNode获取到 + //第一个参数pNode + pNode = finalData.size(); + if (pNode < 1) { + //没有合理的用采数据直接返回 + throw new BusinessException(AdvanceResponseEnum.USER_DATA_P_NODE_PARAMETER_ERROR); + } + //第二个参数pNum,根据起始时间和截止时间以及监测点测量间隔计算数量 + RespCommon pNumAndInterval = getPNumAndInterval(finalData, responsibilityCalculateParam.getLineId(), dateStr); + pNum = pNumAndInterval.getPNum(); + int userIntervalTime = pNumAndInterval.getUserIntervalTime(); + int lineInterval = pNumAndInterval.getLineInterval(); + //第三个参数win,根据起始时间和截止时间的间隔 + if (dateStr.size() > 1) { + if (userIntervalTime == INTERVAL_TIME_15) { + win = WINDOW_96; + } else { + win = WINDOW_48; + } + } else { + win = WINDOW_4; + } + //第四个参数harmMk,默认为0f + harmMk = 0f; + //第五个参数harmNum,与功率数据保持一致 + harmNum = pNum; + //至此基础数据组装完毕,开始组装功率数据和谐波数据 + //先做谐波数据,理论上到这步的时候,谐波数据是满足完整性并已经补充完整性到100%,此处需要将谐波数据与功率数据长度匹配上 + RespHarmData respHarmData = getRespHarmData(responsibilityCalculateParam, lineInterval); + //harmData填充完毕后,开始组装功率数据 + //首先获取当前时间内的各个用户的数据 + Map> originalPData = new HashMap<>(16); + List names = new ArrayList<>(); + Set userNamesFinal = finalData.keySet(); + for (String userName : userNamesFinal) { + List tempData = new ArrayList<>(); + //根据日期将日期数据全部获取出来z + Map> tempResult = finalData.get(userName); + for (String date : dateStr) { + tempData.addAll(tempResult.get(date)); + } + //按日期排序 + Collections.sort(tempData); + originalPData.put(userName, tempData); + names.add(userName); + } + //然后开始组装数据 + float[][] pData = new float[QvvrDataEntity.MAX_P_NUM][QvvrDataEntity.MAX_P_NODE]; + for (int i = 0; i < names.size(); i++) { + //当前某用户测量节点的所有数据 + List userDataExcelBodies1 = originalPData.get(names.get(i)); + for (int k = 0; k < userDataExcelBodies1.size(); k++) { + float[] pDataStruct = pData[k]; + if (pDataStruct == null) { + pDataStruct = new float[QvvrDataEntity.MAX_P_NODE]; + } + float[] p = pDataStruct; + p[i] = userDataExcelBodies1.get(k).getWork().floatValue(); + pData[k] = pDataStruct; + } + } + //至此功率数据也组装完毕,调用友谊提供的接口 +// QvvrDataEntity qvvrDataEntity = new QvvrDataEntity(); +// qvvrDataEntity.calFlag = 0; +// qvvrDataEntity.pNode = pNode; +// qvvrDataEntity.pNum = pNum; +// qvvrDataEntity.win = win; +// qvvrDataEntity.harmNum = harmNum; +// qvvrDataEntity.harmMk = harmMk; +// qvvrDataEntity.pData = pData; +// qvvrDataEntity.harmData = respHarmData.getHarmData(); +// ResponsibilityAlgorithm responsibilityAlgorithm = new ResponsibilityAlgorithm(); +// qvvrDataEntity = responsibilityAlgorithm.getResponsibilityResult(qvvrDataEntity); + + HarmonicData harmonicData = harmonicResponsibilityService.fullCalculation(respHarmData.getHarmData(), pData, harmNum, pNum, pNode, win, harmMk); + //至此接口调用结束,开始组装动态责任数据和用户责任量化结果 + //首先判断cal_ok的标识位是否为1,为0表示程序没有计算出结果 + if (harmonicData.getCalculationStatus() == CalculationStatus.FAILED) { + throw new BusinessException(AdvanceResponseEnum.RESPONSIBILITY_PARAMETER_ERROR); + } + //没问题后,先玩动态责任数据 + CustomerData[] customerDatas = new CustomerData[harmonicData.getPowerNodeCount()]; + float[][] fKdata/*无背景的动态责任数据*/ = harmonicData.getFkData(); + //第一个时间节点是起始时间+win窗口得到的时间 + Date sTime = DateUtil.parse(dateStr.get(0).concat(" 00:00:00"), DatePattern.NORM_DATETIME_PATTERN); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(sTime); + calendar.add(Calendar.MINUTE, (win - 1) * userIntervalTime); + List timeDatas = new ArrayList<>(); + for (int i = 0; i < harmonicData.getPowerCount() - harmonicData.getWindowSize(); i++) { + calendar.add(Calendar.MINUTE, userIntervalTime); + //一个时间点所有的用户数据 + float[] fKdatum = fKdata[i]; + for (int k = 0; k < harmonicData.getPowerNodeCount(); k++) { + CustomerData customerData = customerDatas[k]; + if (null == customerData) { + customerData = new CustomerData(); + customerData.setCustomerName(names.get(k)); + } + List valueDatas = customerData.getValueDatas(); + Float valueTemp = fKdatum[k]; + if (valueTemp.isNaN()) { + valueTemp = 0.0f; + } + valueDatas.add(valueTemp); + customerData.setValueDatas(valueDatas); + customerDatas[k] = customerData; + } + timeDatas.add(calendar.getTimeInMillis()); + } + //OK拿到所有测量点的数据了,现在就是看如何将相同户号的动态数据进行算术和求值,之前的用户name为:户号@测量点号@用户名 + Map> customerDataTemp = new HashMap<>(16); + for (CustomerData data : customerDatas) { + String customerName = data.getCustomerName(); + String[] customerInfo = customerName.split("@"); + String name = customerInfo[2] + "(" + customerInfo[0] + ")"; + List customerData = customerDataTemp.get(name); + CustomerData temp = data; + temp.setCustomerName(name); + if (CollectionUtils.isEmpty(customerData)) { + customerData = new ArrayList<>(); + } + customerData.add(temp); + customerDataTemp.put(name, customerData); + } + //动态数据组装完成后,开始组装责任数据 + List customerResponsibilities = getCustomerResponsibilityData(names, harmonicData.getSumFKData(), harmonicData.getPowerNodeCount()); + //根据前十的用户数据,获取这些用户的动态责任数据 + List customerData = new ArrayList<>(); + for (CustomerResponsibility customerResponsibility : customerResponsibilities) { + String cusName = customerResponsibility.getCustomerName(); + List customerData1 = customerDataTemp.get(cusName); + if (CollectionUtils.isEmpty(customerData1)) { + continue; + } + if (customerData1.size() == 1) { + //表示用户唯一的 + customerData.add(customerData1.get(0)); + } else { + // 表示用户可能包含多个监测点号,需要进行数据累加 + CustomerData customerDataT = new CustomerData(); + customerDataT.setCustomerName(cusName); + //进行数值累加 + List valueDatas = new ArrayList<>(); + for (int i = 0; i < customerData1.get(0).getValueDatas().size(); i++) { + float original = 0.0f; + for (CustomerData data : customerData1) { + original = original + data.getValueDatas().get(i); + } + valueDatas.add(original); + } + customerDataT.setValueDatas(valueDatas); + customerData.add(customerDataT); + } + } + result.setDatas(customerData); + result.setTimeDatas(timeDatas); + result.setResponsibilities(customerResponsibilities); + //此次的操作进行入库操作responsibilityData表数据 + //根据监测点名称+谐波框选的时间来查询,是否做过责任量化 + String timeWin = responsibilityCalculateParam.getSearchBeginTime().replaceAll(StrPool.DASHED, "").concat(StrPool.DASHED).concat(responsibilityCalculateParam.getSearchEndTime().replaceAll(StrPool.DASHED, "")); + String type = responsibilityCalculateParam.getType() == 0 ? "谐波电流" : "谐波电压"; + //为了避免有监测点名称重复的,最终还是选择使用监测点索引来判断唯一性 + LambdaQueryWrapper respDataLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respDataLambdaQueryWrapper.eq(RespData::getLineId, responsibilityCalculateParam.getLineId()) + .eq(RespData::getUserDataId, responsibilityCalculateParam.getUserDataId()) + .eq(RespData::getTimeWindow, timeWin) + .eq(RespData::getDataType, type) + .eq(RespData::getState, DataStateEnum.ENABLE.getCode()); + List responsibilityDataTemp = this.baseMapper.selectList(respDataLambdaQueryWrapper); + RespData responsibilityData; + if (CollectionUtils.isEmpty(responsibilityDataTemp)) { + responsibilityData = new RespData(); + //库中没有记录则可以新建数据进行插入 + responsibilityData.setLineId(responsibilityCalculateParam.getLineId()); + responsibilityData.setUserDataId(responsibilityCalculateParam.getUserDataId()); + responsibilityData.setDataType(type); + responsibilityData.setDataTimes(responsibilityCalculateParam.getTime().toString()); + responsibilityData.setTimeWindow(timeWin); + responsibilityData.setState(DataStateEnum.ENABLE.getCode()); + //进行插入操作 + this.baseMapper.insert(responsibilityData); + } else { + //库中存在记录只需要判断次数进行数据更新 + responsibilityData = responsibilityDataTemp.get(0); + String times = responsibilityData.getDataTimes(); + List timesList = Stream.of(times.split(StrPool.COMMA)).collect(Collectors.toList()); + Integer time = responsibilityCalculateParam.getTime(); + if (!timesList.contains(time.toString())) { + timesList.add(time.toString()); + timesList = timesList.stream().sorted().collect(Collectors.toList()); + responsibilityData.setDataTimes(String.join(StrPool.COMMA, timesList)); + } + //执行更新操作 + this.baseMapper.updateById(responsibilityData); + } + //入库完毕之后,需要将必要数据进行序列化存储,方便后期的重复利用 + /* + * 需要序列化三种数据结构 1 cal_flag置为1时需要的一些列参数的CacheQvvrData 2 cal_flag为0时的,动态结果。3 用户责任量化结果 + * 其中1/2都只需要一个文件即可 + * 3因为用户限值的变化调整,可能存在很多个文件,具体根据用户的选择而定 + * + * 路径的结构为,temPath+userData+excelName+type+timeWin+lineIndex+time+文件名 + * 用户责任量化结果,需要再细化到限值 + */ + //首先判断有没有存储记录,没有则存储,有就略过 指定测点、时间窗口、谐波类型、谐波次数判断唯一性 + LambdaQueryWrapper respDataResultLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respDataResultLambdaQueryWrapper.eq(RespDataResult::getResDataId, responsibilityData.getId()) + .eq(RespDataResult::getTime, responsibilityCalculateParam.getTime()) + .eq(RespDataResult::getStartTime, DateUtil.parse(responsibilityCalculateParam.getSearchBeginTime() + " 00:00:00", DatePattern.NORM_DATETIME_PATTERN)) + .eq(RespDataResult::getEndTime, DateUtil.parse(responsibilityCalculateParam.getSearchEndTime() + " 23:59:59", DatePattern.NORM_DATETIME_PATTERN)) + .eq(RespDataResult::getLimitValue, respHarmData.getOverLimit()); + RespDataResult respDataResult = respDataResultService.getOne(respDataResultLambdaQueryWrapper); + if (Objects.isNull(respDataResult)) { + respDataResult = new RespDataResult(); + respDataResult.setResDataId(responsibilityData.getId()); + respDataResult.setTime(responsibilityCalculateParam.getTime()); + respDataResult.setStartTime(DateUtil.parse(responsibilityCalculateParam.getSearchBeginTime() + " 00:00:00", DatePattern.NORM_DATETIME_PATTERN)); + respDataResult.setEndTime(DateUtil.parse(responsibilityCalculateParam.getSearchEndTime() + " 23:59:59", DatePattern.NORM_DATETIME_PATTERN)); + respDataResult.setLimitValue(respHarmData.getOverLimit()); + //时间横轴数据 timeDatas + JSONArray timeDataJson = JSONArray.parseArray(JSON.toJSONString(timeDatas)); + InputStream timeDataStream = IoUtil.toUtf8Stream(timeDataJson.toString()); + String timeDataPath = fileStorageUtil.uploadStream(timeDataStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setTimeData(timeDataPath); + //用户每时刻对应的责任数据 + JSONArray customerDataJson = JSONArray.parseArray(JSON.toJSONString(customerData)); + InputStream customerStream = IoUtil.toUtf8Stream(customerDataJson.toString()); + String customerPath = fileStorageUtil.uploadStream(customerStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setUserDetailData(customerPath); + //调用qvvr生成的中间数据 + CacheQvvrData cacheQvvrData = new CacheQvvrData(harmonicData.getPowerNodeCount(), harmonicData.getHarmonicCount(), harmonicData.getHarmonicData(), harmonicData.getFkData(), harmonicData.getHkData(), names, userIntervalTime, harmonicData.getWindowSize(), userIntervalTime, respHarmData.getHarmTime()); + String cacheJson = PubUtils.obj2json(cacheQvvrData); + InputStream cacheQvvrDataStream = IoUtil.toUtf8Stream(cacheJson); + String cacheQvvrDataPath = fileStorageUtil.uploadStream(cacheQvvrDataStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setQvvrData(cacheQvvrDataPath); + //用户前10数据存储 + JSONArray customerResJson = JSONArray.parseArray(JSON.toJSONString(customerResponsibilities)); + InputStream customerResStream = IoUtil.toUtf8Stream(customerResJson.toString()); + String customerResPath = fileStorageUtil.uploadStream(customerResStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setUserResponsibility(customerResPath); + respDataResultService.save(respDataResult); + } + //防止过程中创建了大量的对象,主动调用下GC处理 + System.gc(); + result.setResponsibilityDataIndex(responsibilityData.getId()); + return result; + } + + @Override + @Deprecated + public ResponsibilityResult getResponsibilityDataOld(ResponsibilitySecondCalParam responsibilitySecondCalParam) { + ResponsibilityResult result = new ResponsibilityResult(); + //根据时间天数,获取理论上多少次用采数据 + RespData responsibilityData = this.baseMapper.selectById(responsibilitySecondCalParam.getResDataId()); + if (Objects.isNull(responsibilityData)) { + throw new BusinessException(AdvanceResponseEnum.RESP_DATA_NOT_FOUND); + } + Overlimit overlimit = overlimitMapper.selectById(responsibilityData.getLineId()); + //获取总数据 + LambdaQueryWrapper respDataResultLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respDataResultLambdaQueryWrapper.eq(RespDataResult::getResDataId, responsibilityData.getId()) + .eq(RespDataResult::getTime, responsibilitySecondCalParam.getTime()); + if (responsibilitySecondCalParam.getType() == 0) { + respDataResultLambdaQueryWrapper.eq(RespDataResult::getLimitValue, PubUtils.getValueByMethod(overlimit, "getIharm", responsibilitySecondCalParam.getTime())); + } else { + respDataResultLambdaQueryWrapper.eq(RespDataResult::getLimitValue, PubUtils.getValueByMethod(overlimit, "getUharm", responsibilitySecondCalParam.getTime())); + } + RespDataResult respDataResultTemp = respDataResultService.getOne(respDataResultLambdaQueryWrapper); + if (Objects.isNull(respDataResultTemp)) { + throw new BusinessException(AdvanceResponseEnum.RESP_DATA_NOT_FOUND); + } + CacheQvvrData cacheQvvrData; + try { + InputStream fileStream = fileStorageUtil.getFileStream(respDataResultTemp.getQvvrData()); + String qvvrDataStr = IoUtil.readUtf8(fileStream); + cacheQvvrData = PubUtils.json2obj(qvvrDataStr, CacheQvvrData.class); + + } catch (Exception exception) { + throw new BusinessException(AdvanceResponseEnum.RESP_RESULT_DATA_NOT_FOUND); + } + //获取成功后,延长该缓存的生命周期为初始生命时长 + int win = cacheQvvrData.getWin(); + //不管窗口为4或者96,都需要考虑最小公倍数 + //最小公倍数根据监测点测量间隔来获取,可以考虑也由第一步操作缓存起来 + int minMultiple = cacheQvvrData.getMinMultiple(); + //谐波横轴所有的时间 + List times = cacheQvvrData.getTimes(); + //首先根据窗口判断限值时间范围是否满足最小窗口 + Long limitSL = DateUtil.parse(responsibilitySecondCalParam.getLimitStartTime(), DatePattern.NORM_DATETIME_PATTERN).getTime(); + Long limitEL = DateUtil.parse(responsibilitySecondCalParam.getLimitEndTime(), DatePattern.NORM_DATETIME_PATTERN).getTime(); + List temp = getTimes(times, limitSL, limitEL); + //在动态责任数据中,时间的起始索引位置和截止索引位置 + Integer timeStartIndex = temp.get(0); + Integer timeEndIndex = temp.get(1); + //间隔中的时间长度 + int minus = timeEndIndex - timeStartIndex + 1; + //组装参数 + QvvrDataEntity qvvrDataEntity = new QvvrDataEntity(); + qvvrDataEntity.calFlag = 1; + qvvrDataEntity.pNode = cacheQvvrData.getPNode(); + qvvrDataEntity.harmMk = responsibilitySecondCalParam.getLimitValue(); + qvvrDataEntity.win = win; + int resNum; + float[][] FKdata = new float[9600][QvvrDataEntity.MAX_P_NODE]; + float[][] HKdata = new float[9600][QvvrDataEntity.MAX_P_NODE + 1]; + float[] harmData = new float[1440 * 100]; + float[][] fKdataOriginal = cacheQvvrData.getFKData(); + float[][] hKdataOriginal = cacheQvvrData.getHKData(); + float[] harmDataOriginal = cacheQvvrData.getHarmData(); + //如果起始索引与截止索引的差值等于时间轴的长度,则说明用户没有选择限值时间,直接带入全部的原始数据,参与计算即可 + if (minus == times.size()) { + qvvrDataEntity.harmNum = cacheQvvrData.getHarmNum(); + qvvrDataEntity.resNum = cacheQvvrData.getHarmNum() - cacheQvvrData.getWin(); + qvvrDataEntity.setFKData(cacheQvvrData.getFKData()); + qvvrDataEntity.setHKData(cacheQvvrData.getHKData()); + qvvrDataEntity.harmData = cacheQvvrData.getHarmData(); + } else { + if (win == WINDOW_4) { + //当窗口为4时,两个时间限制范围在最小公倍数为15时,最起码有5个有效时间点,在最小公倍数为30时,最起码有3个有效时间点 + if (minMultiple == INTERVAL_TIME_15) { + if (minus < MINUS_5) { + throw new BusinessException(AdvanceResponseEnum.WIN_TIME_ERROR); + } + resNum = minus - MINUS_4; + + } else if (minMultiple == INTERVAL_TIME_30) { + if (minus < MINUS_3) { + throw new BusinessException(AdvanceResponseEnum.WIN_TIME_ERROR); + } + resNum = minus - MINUS_2; + } else { + throw new BusinessException(AdvanceResponseEnum.CALCULATE_INTERVAL_ERROR); + } + } else if (win == WINDOW_96) { + //当窗口为96时,两个时间限值范围在最小公倍数为15时,最起码有97个有效时间点,在最小公倍数为30时,最起码有49个有效时间点 + if (minMultiple == INTERVAL_TIME_15) { + if (minus <= WINDOW_96) { + throw new BusinessException(AdvanceResponseEnum.WIN_TIME_ERROR); + } + resNum = minus - WINDOW_96; + } else if (minMultiple == INTERVAL_TIME_30) { + if (minus <= WINDOW_48) { + throw new BusinessException(AdvanceResponseEnum.WIN_TIME_ERROR); + } + resNum = minus - WINDOW_48; + } else { + throw new BusinessException(AdvanceResponseEnum.CALCULATE_INTERVAL_ERROR); + } + } else { + throw new BusinessException(AdvanceResponseEnum.CALCULATE_INTERVAL_ERROR); + } + qvvrDataEntity.resNum = resNum; + qvvrDataEntity.harmNum = minus; + //因为限值时间实际是含头含尾的,所以harmNum需要索引差值+1 + for (int i = timeStartIndex; i <= timeEndIndex; i++) { + harmData[i - timeStartIndex] = harmDataOriginal[i]; + } + qvvrDataEntity.harmData = harmData; + //FKData与HKData的值则等于resNum + for (int i = timeStartIndex; i < timeStartIndex + resNum; i++) { + FKdata[i - timeStartIndex] = fKdataOriginal[i]; + HKdata[i - timeStartIndex] = hKdataOriginal[i]; + } + + qvvrDataEntity.setFKData(FKdata); + qvvrDataEntity.setHKData(HKdata); + } + ResponsibilityAlgorithm responsibilityAlgorithm = new ResponsibilityAlgorithm(); + qvvrDataEntity = responsibilityAlgorithm.getResponsibilityResult(qvvrDataEntity); + if (qvvrDataEntity.calOk == 0) { + throw new BusinessException(AdvanceResponseEnum.RESPONSIBILITY_PARAMETER_ERROR); + } + + //没问题后,先玩动态责任数据 + List names = cacheQvvrData.getNames(); + CustomerData[] customerDatas = new CustomerData[qvvrDataEntity.pNode]; + float[][] fKdata/*无背景的动态责任数据*/ = qvvrDataEntity.getFKData(); + //第一个时间节点是起始时间+win窗口得到的时间 + Date sTime = new Date(); + sTime.setTime(times.get(timeStartIndex)); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(sTime); + calendar.add(Calendar.MINUTE, (win - 1) * minMultiple); + List timeDatas = new ArrayList<>(); + for (int i = 0; i < qvvrDataEntity.harmNum - qvvrDataEntity.win; i++) { + calendar.add(Calendar.MINUTE, minMultiple); + //一个时间点所有的用户数据 + float[] fKdatum = fKdata[i]; + for (int k = 0; k < qvvrDataEntity.pNode; k++) { + CustomerData customerData = customerDatas[k]; + if (null == customerData) { + customerData = new CustomerData(); + customerData.setCustomerName(names.get(k)); + } + List valueDatas = customerData.getValueDatas(); + Float valueTemp = fKdatum[k]; + if (valueTemp.isNaN()) { + valueTemp = 0.0f; + } + valueDatas.add(valueTemp); + customerData.setValueDatas(valueDatas); + customerDatas[k] = customerData; + } + timeDatas.add(calendar.getTimeInMillis()); + } + //OK拿到所有测量点的数据了,现在就是看如何将相同户号的动态数据进行算术和求值,之前的用户name为:户号@测量点号@用户名 + Map> customerDataTemp = new HashMap<>(32); + for (CustomerData data : customerDatas) { + String customerName = data.getCustomerName(); + String[] customerInfo = customerName.split("@"); + String name = customerInfo[2] + "(" + customerInfo[0] + ")"; + List customerData = customerDataTemp.get(name); + CustomerData customerTemp = data; + customerTemp.setCustomerName(name); + if (CollectionUtils.isEmpty(customerData)) { + customerData = new ArrayList<>(); + } + customerData.add(customerTemp); + customerDataTemp.put(name, customerData); + } + //调用程序接口后,首先组装责任量化结果 + float[] sumFKdata = qvvrDataEntity.sumFKdata; + List customerResponsibilities = getCustomerResponsibilityData(names, sumFKdata, qvvrDataEntity.pNode); + //根据前十的用户数据,获取这些用户的动态责任数据 + List customerData = new ArrayList<>(); + + for (CustomerResponsibility customerResponsibility : customerResponsibilities) { + String cusName = customerResponsibility.getCustomerName(); + List customerData1 = customerDataTemp.get(cusName); + if (CollectionUtils.isEmpty(customerData1)) { + continue; + } + if (customerData1.size() == 1) { + //表示用户唯一的 + customerData.add(customerData1.get(0)); + } else { + // 表示用户可能包含多个监测点号,需要进行数据累加 + CustomerData customerDataT = new CustomerData(); + customerDataT.setCustomerName(cusName); + //进行数值累加 + List valueDatas = new ArrayList<>(); + for (int i = 0; i < customerData1.get(0).getValueDatas().size(); i++) { + float original = 0.0f; + for (CustomerData data : customerData1) { + original = original + data.getValueDatas().get(i); + } + valueDatas.add(original); + } + customerDataT.setValueDatas(valueDatas); + customerData.add(customerDataT); + } + } + //接着组装动态数据结果 + result.setResponsibilities(customerResponsibilities); + result.setDatas(customerData); + result.setTimeDatas(timeDatas); + + //首先判断有没有存储记录,没有则存储,有就略过 指定测点、时间窗口、谐波类型、谐波次数判断唯一性 + LambdaQueryWrapper respDataResultLambdaQueryWrapper1 = new LambdaQueryWrapper<>(); + respDataResultLambdaQueryWrapper1.eq(RespDataResult::getResDataId, responsibilityData.getId()) + .eq(RespDataResult::getTime, responsibilitySecondCalParam.getTime()) + .eq(RespDataResult::getStartTime, DateUtil.parse(responsibilitySecondCalParam.getLimitStartTime(), DatePattern.NORM_DATETIME_PATTERN)) + .eq(RespDataResult::getEndTime, DateUtil.parse(responsibilitySecondCalParam.getLimitEndTime(), DatePattern.NORM_DATETIME_PATTERN)) + .eq(RespDataResult::getLimitValue, responsibilitySecondCalParam.getLimitValue()); + RespDataResult respDataResult = respDataResultService.getOne(respDataResultLambdaQueryWrapper1); + if (Objects.isNull(respDataResult)) { + respDataResult = new RespDataResult(); + respDataResult.setResDataId(responsibilityData.getId()); + respDataResult.setTime(responsibilitySecondCalParam.getTime()); + respDataResult.setStartTime(DateUtil.parse(responsibilitySecondCalParam.getLimitStartTime(), DatePattern.NORM_DATETIME_PATTERN)); + respDataResult.setEndTime(DateUtil.parse(responsibilitySecondCalParam.getLimitEndTime(), DatePattern.NORM_DATETIME_PATTERN)); + respDataResult.setLimitValue(responsibilitySecondCalParam.getLimitValue()); + //时间横轴数据 timeDatas + JSONArray timeDataJson = JSONArray.parseArray(JSON.toJSONString(timeDatas)); + InputStream timeDataStream = IoUtil.toUtf8Stream(timeDataJson.toString()); + String timeDataPath = fileStorageUtil.uploadStream(timeDataStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setTimeData(timeDataPath); + //用户每时刻对应的责任数据 + JSONArray customerDataJson = JSONArray.parseArray(JSON.toJSONString(customerData)); + InputStream customerStream = IoUtil.toUtf8Stream(customerDataJson.toString()); + String customerPath = fileStorageUtil.uploadStream(customerStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setUserDetailData(customerPath); + //用户前10数据存储 + JSONArray customerResJson = JSONArray.parseArray(JSON.toJSONString(customerResponsibilities)); + InputStream customerResStream = IoUtil.toUtf8Stream(customerResJson.toString()); + String customerResPath = fileStorageUtil.uploadStream(customerResStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setUserResponsibility(customerResPath); + respDataResultService.save(respDataResult); + } + //防止过程中创建了大量的对象,主动调用下GC处理 + System.gc(); + result.setResponsibilityDataIndex(responsibilityData.getId()); + return result; + } + + @Override + public ResponsibilityResult getResponsibilityData(ResponsibilitySecondCalParam responsibilitySecondCalParam) { + ResponsibilityResult result = new ResponsibilityResult(); + //根据时间天数,获取理论上多少次用采数据 + RespData responsibilityData = this.baseMapper.selectById(responsibilitySecondCalParam.getResDataId()); + if (Objects.isNull(responsibilityData)) { + throw new BusinessException(AdvanceResponseEnum.RESP_DATA_NOT_FOUND); + } + Overlimit overlimit = overlimitMapper.selectById(responsibilityData.getLineId()); + //获取总数据 + LambdaQueryWrapper respDataResultLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respDataResultLambdaQueryWrapper.eq(RespDataResult::getResDataId, responsibilityData.getId()) + .eq(RespDataResult::getTime, responsibilitySecondCalParam.getTime()); + if (responsibilitySecondCalParam.getType() == 0) { + respDataResultLambdaQueryWrapper.eq(RespDataResult::getLimitValue, PubUtils.getValueByMethod(overlimit, "getIharm", responsibilitySecondCalParam.getTime())); + } else { + respDataResultLambdaQueryWrapper.eq(RespDataResult::getLimitValue, PubUtils.getValueByMethod(overlimit, "getUharm", responsibilitySecondCalParam.getTime())); + } + RespDataResult respDataResultTemp = respDataResultService.getOne(respDataResultLambdaQueryWrapper); + if (Objects.isNull(respDataResultTemp)) { + throw new BusinessException(AdvanceResponseEnum.RESP_DATA_NOT_FOUND); + } + CacheQvvrData cacheQvvrData; + try { + InputStream fileStream = fileStorageUtil.getFileStream(respDataResultTemp.getQvvrData()); + String qvvrDataStr = IoUtil.readUtf8(fileStream); + cacheQvvrData = PubUtils.json2obj(qvvrDataStr, CacheQvvrData.class); + + } catch (Exception exception) { + throw new BusinessException(AdvanceResponseEnum.RESP_RESULT_DATA_NOT_FOUND); + } + //获取成功后,延长该缓存的生命周期为初始生命时长 + int win = cacheQvvrData.getWin(); + //不管窗口为4或者96,都需要考虑最小公倍数 + //最小公倍数根据监测点测量间隔来获取,可以考虑也由第一步操作缓存起来 + int minMultiple = cacheQvvrData.getMinMultiple(); + //谐波横轴所有的时间 + List times = cacheQvvrData.getTimes(); + //首先根据窗口判断限值时间范围是否满足最小窗口 + Long limitSL = DateUtil.parse(responsibilitySecondCalParam.getLimitStartTime(), DatePattern.NORM_DATETIME_PATTERN).getTime(); + Long limitEL = DateUtil.parse(responsibilitySecondCalParam.getLimitEndTime(), DatePattern.NORM_DATETIME_PATTERN).getTime(); + List temp = getTimes(times, limitSL, limitEL); + //在动态责任数据中,时间的起始索引位置和截止索引位置 + Integer timeStartIndex = temp.get(0); + Integer timeEndIndex = temp.get(1); + //间隔中的时间长度 + int minus = timeEndIndex - timeStartIndex + 1; + //组装参数 + QvvrDataEntity qvvrDataEntity = new QvvrDataEntity(); + qvvrDataEntity.calFlag = 1; + qvvrDataEntity.pNode = cacheQvvrData.getPNode(); + qvvrDataEntity.harmMk = responsibilitySecondCalParam.getLimitValue(); + qvvrDataEntity.win = win; + int resNum; + float[][] FKdata = new float[9600][QvvrDataEntity.MAX_P_NODE]; + float[][] HKdata = new float[9600][QvvrDataEntity.MAX_P_NODE + 1]; + float[] harmData = new float[1440 * 100]; + float[][] fKdataOriginal = cacheQvvrData.getFKData(); + float[][] hKdataOriginal = cacheQvvrData.getHKData(); + float[] harmDataOriginal = cacheQvvrData.getHarmData(); + //如果起始索引与截止索引的差值等于时间轴的长度,则说明用户没有选择限值时间,直接带入全部的原始数据,参与计算即可 + if (minus == times.size()) { + qvvrDataEntity.harmNum = cacheQvvrData.getHarmNum(); + qvvrDataEntity.resNum = cacheQvvrData.getHarmNum() - cacheQvvrData.getWin(); + qvvrDataEntity.setFKData(cacheQvvrData.getFKData()); + qvvrDataEntity.setHKData(cacheQvvrData.getHKData()); + qvvrDataEntity.harmData = cacheQvvrData.getHarmData(); + } else { + if (win == WINDOW_4) { + //当窗口为4时,两个时间限制范围在最小公倍数为15时,最起码有5个有效时间点,在最小公倍数为30时,最起码有3个有效时间点 + if (minMultiple == INTERVAL_TIME_15) { + if (minus < MINUS_5) { + throw new BusinessException(AdvanceResponseEnum.WIN_TIME_ERROR); + } + resNum = minus - MINUS_4; + + } else if (minMultiple == INTERVAL_TIME_30) { + if (minus < MINUS_3) { + throw new BusinessException(AdvanceResponseEnum.WIN_TIME_ERROR); + } + resNum = minus - MINUS_2; + } else { + throw new BusinessException(AdvanceResponseEnum.CALCULATE_INTERVAL_ERROR); + } + } else if (win == WINDOW_96) { + //当窗口为96时,两个时间限值范围在最小公倍数为15时,最起码有97个有效时间点,在最小公倍数为30时,最起码有49个有效时间点 + if (minMultiple == INTERVAL_TIME_15) { + if (minus <= WINDOW_96) { + throw new BusinessException(AdvanceResponseEnum.WIN_TIME_ERROR); + } + resNum = minus - WINDOW_96; + } else if (minMultiple == INTERVAL_TIME_30) { + if (minus <= WINDOW_48) { + throw new BusinessException(AdvanceResponseEnum.WIN_TIME_ERROR); + } + resNum = minus - WINDOW_48; + } else { + throw new BusinessException(AdvanceResponseEnum.CALCULATE_INTERVAL_ERROR); + } + } else { + throw new BusinessException(AdvanceResponseEnum.CALCULATE_INTERVAL_ERROR); + } + qvvrDataEntity.resNum = resNum; + qvvrDataEntity.harmNum = minus; + //因为限值时间实际是含头含尾的,所以harmNum需要索引差值+1 + for (int i = timeStartIndex; i <= timeEndIndex; i++) { + harmData[i - timeStartIndex] = harmDataOriginal[i]; + } + qvvrDataEntity.harmData = harmData; + //FKData与HKData的值则等于resNum + for (int i = timeStartIndex; i < timeStartIndex + resNum; i++) { + FKdata[i - timeStartIndex] = fKdataOriginal[i]; + HKdata[i - timeStartIndex] = hKdataOriginal[i]; + } + + qvvrDataEntity.setFKData(FKdata); + qvvrDataEntity.setHKData(HKdata); + } +// ResponsibilityAlgorithm responsibilityAlgorithm = new ResponsibilityAlgorithm(); +// qvvrDataEntity = responsibilityAlgorithm.getResponsibilityResult(qvvrDataEntity); +// +// if (qvvrDataEntity.calOk == 0) { +// throw new BusinessException(AdvanceResponseEnum.RESPONSIBILITY_PARAMETER_ERROR); +// } + HarmonicData harmonicData = harmonicResponsibilityService.partialCalculation( + qvvrDataEntity.getHarmData(), qvvrDataEntity.getFKData(), qvvrDataEntity.getHKData(), qvvrDataEntity.getHarmNum(), qvvrDataEntity.getPNode(), qvvrDataEntity.getWin(), qvvrDataEntity.getResNum(), qvvrDataEntity.getHarmMk()); + if (harmonicData.getCalculationStatus() == CalculationStatus.FAILED) { + throw new BusinessException(AdvanceResponseEnum.RESPONSIBILITY_PARAMETER_ERROR); + } + + //没问题后,先玩动态责任数据 + List names = cacheQvvrData.getNames(); + CustomerData[] customerDatas = new CustomerData[harmonicData.getPowerNodeCount()]; + float[][] fKdata/*无背景的动态责任数据*/ = harmonicData.getFkData(); + //第一个时间节点是起始时间+win窗口得到的时间 + Date sTime = new Date(); + sTime.setTime(times.get(timeStartIndex)); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(sTime); + calendar.add(Calendar.MINUTE, (win - 1) * minMultiple); + List timeDatas = new ArrayList<>(); + for (int i = 0; i < harmonicData.getHarmonicCount() - harmonicData.getWindowSize(); i++) { + calendar.add(Calendar.MINUTE, minMultiple); + //一个时间点所有的用户数据 + float[] fKdatum = fKdata[i]; + for (int k = 0; k < harmonicData.getPowerNodeCount(); k++) { + CustomerData customerData = customerDatas[k]; + if (null == customerData) { + customerData = new CustomerData(); + customerData.setCustomerName(names.get(k)); + } + List valueDatas = customerData.getValueDatas(); + Float valueTemp = fKdatum[k]; + if (valueTemp.isNaN()) { + valueTemp = 0.0f; + } + valueDatas.add(valueTemp); + customerData.setValueDatas(valueDatas); + customerDatas[k] = customerData; + } + timeDatas.add(calendar.getTimeInMillis()); + } + //OK拿到所有测量点的数据了,现在就是看如何将相同户号的动态数据进行算术和求值,之前的用户name为:户号@测量点号@用户名 + Map> customerDataTemp = new HashMap<>(32); + for (CustomerData data : customerDatas) { + String customerName = data.getCustomerName(); + String[] customerInfo = customerName.split("@"); + String name = customerInfo[2] + "(" + customerInfo[0] + ")"; + List customerData = customerDataTemp.get(name); + CustomerData customerTemp = data; + customerTemp.setCustomerName(name); + if (CollectionUtils.isEmpty(customerData)) { + customerData = new ArrayList<>(); + } + customerData.add(customerTemp); + customerDataTemp.put(name, customerData); + } + //调用程序接口后,首先组装责任量化结果 + float[] sumFKdata = harmonicData.getSumFKData(); + List customerResponsibilities = getCustomerResponsibilityData(names, sumFKdata, harmonicData.getPowerNodeCount()); + //根据前十的用户数据,获取这些用户的动态责任数据 + List customerData = new ArrayList<>(); + + for (CustomerResponsibility customerResponsibility : customerResponsibilities) { + String cusName = customerResponsibility.getCustomerName(); + List customerData1 = customerDataTemp.get(cusName); + if (CollectionUtils.isEmpty(customerData1)) { + continue; + } + if (customerData1.size() == 1) { + //表示用户唯一的 + customerData.add(customerData1.get(0)); + } else { + // 表示用户可能包含多个监测点号,需要进行数据累加 + CustomerData customerDataT = new CustomerData(); + customerDataT.setCustomerName(cusName); + //进行数值累加 + List valueDatas = new ArrayList<>(); + for (int i = 0; i < customerData1.get(0).getValueDatas().size(); i++) { + float original = 0.0f; + for (CustomerData data : customerData1) { + original = original + data.getValueDatas().get(i); + } + valueDatas.add(original); + } + customerDataT.setValueDatas(valueDatas); + customerData.add(customerDataT); + } + } + //接着组装动态数据结果 + result.setResponsibilities(customerResponsibilities); + result.setDatas(customerData); + result.setTimeDatas(timeDatas); + + //首先判断有没有存储记录,没有则存储,有就略过 指定测点、时间窗口、谐波类型、谐波次数判断唯一性 + LambdaQueryWrapper respDataResultLambdaQueryWrapper1 = new LambdaQueryWrapper<>(); + respDataResultLambdaQueryWrapper1.eq(RespDataResult::getResDataId, responsibilityData.getId()) + .eq(RespDataResult::getTime, responsibilitySecondCalParam.getTime()) + .eq(RespDataResult::getStartTime, DateUtil.parse(responsibilitySecondCalParam.getLimitStartTime(), DatePattern.NORM_DATETIME_PATTERN)) + .eq(RespDataResult::getEndTime, DateUtil.parse(responsibilitySecondCalParam.getLimitEndTime(), DatePattern.NORM_DATETIME_PATTERN)) + .eq(RespDataResult::getLimitValue, responsibilitySecondCalParam.getLimitValue()); + RespDataResult respDataResult = respDataResultService.getOne(respDataResultLambdaQueryWrapper1); + if (Objects.isNull(respDataResult)) { + respDataResult = new RespDataResult(); + respDataResult.setResDataId(responsibilityData.getId()); + respDataResult.setTime(responsibilitySecondCalParam.getTime()); + respDataResult.setStartTime(DateUtil.parse(responsibilitySecondCalParam.getLimitStartTime(), DatePattern.NORM_DATETIME_PATTERN)); + respDataResult.setEndTime(DateUtil.parse(responsibilitySecondCalParam.getLimitEndTime(), DatePattern.NORM_DATETIME_PATTERN)); + respDataResult.setLimitValue(responsibilitySecondCalParam.getLimitValue()); + //时间横轴数据 timeDatas + JSONArray timeDataJson = JSONArray.parseArray(JSON.toJSONString(timeDatas)); + InputStream timeDataStream = IoUtil.toUtf8Stream(timeDataJson.toString()); + String timeDataPath = fileStorageUtil.uploadStream(timeDataStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setTimeData(timeDataPath); + //用户每时刻对应的责任数据 + JSONArray customerDataJson = JSONArray.parseArray(JSON.toJSONString(customerData)); + InputStream customerStream = IoUtil.toUtf8Stream(customerDataJson.toString()); + String customerPath = fileStorageUtil.uploadStream(customerStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setUserDetailData(customerPath); + //用户前10数据存储 + JSONArray customerResJson = JSONArray.parseArray(JSON.toJSONString(customerResponsibilities)); + InputStream customerResStream = IoUtil.toUtf8Stream(customerResJson.toString()); + String customerResPath = fileStorageUtil.uploadStream(customerResStream, OssPath.RESPONSIBILITY_USER_RESULT_DATA, FileUtil.generateFileName("json")); + respDataResult.setUserResponsibility(customerResPath); + respDataResultService.save(respDataResult); + } + //防止过程中创建了大量的对象,主动调用下GC处理 + System.gc(); + result.setResponsibilityDataIndex(responsibilityData.getId()); + return result; + } + + + /** + * 监测点测量间隔获取最后用于计算的功率数据 + * + * @param finalData 参数计算的功率数据 + */ + private Map>> dealFinalDataByLineInterval(Map>> finalData) { + DecimalFormat decimalFormat = new DecimalFormat("0.0000"); + Map>> result; + //当监测点测量间隔为10分钟时,功率数据需要调整为30分钟数据 + result = new HashMap<>(32); + Set userNames = finalData.keySet(); + for (String userName : userNames) { + Map> temp = new HashMap<>(32); + Map> original = finalData.get(userName); + Set dates = original.keySet(); + for (String date : dates) { + //某当天的数据 + List single = original.get(date); + //先根据事时间排序 + Collections.sort(single); + //此时根据当天所有的数据,重新计算出所有时间点的数据,担心这个过程会消耗过长时间 + List tempDatas = new ArrayList<>(); + for (int i = 0; i < WINDOW_96; i = i + 2) { + //30分钟内的2个15分钟功率数据相加作平均计算30分钟内的功率数据,最终的数据序列时间间隔30分钟。by 友谊文档 + UserDataExcel tempData = new UserDataExcel(); + tempData.setUserName(single.get(i).getUserName()); + tempData.setUserId(single.get(i).getUserId()); + tempData.setLine(single.get(i).getLine()); + tempData.setTime(single.get(i).getTime()); + //功率为 2个15分钟功率数据相加作平均 + double work = single.get(i).getWork().doubleValue() + single.get(i + 1).getWork().doubleValue(); + tempData.setWork(new BigDecimal(decimalFormat.format(work / 2.0))); + tempDatas.add(tempData); + } + temp.put(date, tempDatas); + } + result.put(userName, temp); + } + return result; + } + + /** + * 通过监测点测量间隔计算对齐后的谐波数据 + * 暂且认为最小公倍数就15、30两种可能 + * + * @param historyData 原始的谐波数据 + * @param lineInterval 测量间隔 + */ + private List getDataWithLineInterval(List historyData, int lineInterval) { + List result; + switch (lineInterval) { + case 1: + result = getHarmResultByTimes(historyData, 15); + break; + case 3: + result = getHarmResultByTimes(historyData, 5); + break; + // 间隔为5、10时,直接返回即可 + default: + result = getHarmResultByTimes(historyData, 3); + break; + } + return result.stream().sorted(Comparator.comparing(HarmData::getTime)).collect(Collectors.toList()); + } + + /** + * 通过监测点测量间隔计算对齐后的谐波数据 + * + * @param historyData 原始的谐波数据 + */ + private List getHarmResultByTimes(List historyData, int times) { + List result = new ArrayList<>(); + DecimalFormat decimalFormat = new DecimalFormat("0.0000"); + for (int i = 0; i < historyData.size(); i = i + times) { + float temp = 0.0f; + for (int j = 0; j < times; j++) { + int index = i + j; + temp = temp + historyData.get(index).getValue(); + } + //求平均值 + temp = Float.parseFloat(decimalFormat.format(temp / (float) times)); + HarmData resTemp = new HarmData(); + resTemp.setTime(historyData.get(i).getTime()); + resTemp.setValue(temp); + result.add(resTemp); + } + return result; + } + + /** + * 根据接口返回值组装需要显示的责任量化数据 + */ + private List getCustomerResponsibilityData(List names, float[] sumFKdata, int pNode) { + Map customerResponsibilityMap = new HashMap<>(16); + for (int i = 0; i < pNode; i++) { + /*用户ID 测量点ID 用户名*/ + String[] customerInfo = names.get(i).split("@"); + String name = customerInfo[2] + "(" + customerInfo[0] + ")"; + CustomerResponsibility customerResponsibility; + if (customerResponsibilityMap.containsKey(name)) { + customerResponsibility = customerResponsibilityMap.get(name); + customerResponsibility.setResponsibilityData(customerResponsibility.getResponsibilityData() + sumFKdata[i]); + } else { + customerResponsibility = new CustomerResponsibility(); + customerResponsibility.setCustomerName(name); + customerResponsibility.setResponsibilityData(sumFKdata[i]); + } + customerResponsibilityMap.put(name, customerResponsibility); + } + //map转为list + List customerResponsibilities = new ArrayList<>(); + Set cusNames = customerResponsibilityMap.keySet(); + for (String cusName : cusNames) { + customerResponsibilities.add(customerResponsibilityMap.get(cusName)); + } + //取出前十的用户责任数据 + customerResponsibilities = customerResponsibilities.stream().sorted(Comparator.comparing(CustomerResponsibility::getResponsibilityData).reversed()).collect(Collectors.toList()); + if (customerResponsibilities.size() > SORT_10) { + //当用户超出10,将前十用户保留,然后剩余归类为其他用户 + float tenTotal = 0.0f; + for (int i = 0; i < SORT_10; i++) { + float temp = PubUtils.floatRound(3, customerResponsibilities.get(i).getResponsibilityData()); + tenTotal = tenTotal + temp; + } + int size = customerResponsibilities.size() - 10; + customerResponsibilities = customerResponsibilities.subList(0, 10); + CustomerResponsibility others = new CustomerResponsibility(); + others.setCustomerName("其他用户(" + size + ")"); + others.setResponsibilityData(PubUtils.floatRound(3, 100.0f - tenTotal)); + customerResponsibilities.add(others); + } + return customerResponsibilities; + } + + /** + * 根据起始时间获取在集合中最接近的起始和截止值 + * + * @param times 时间集合 + * @param limitSL 起始值 + * @param limitEL 截止值 + */ + private List getTimes(List times, Long limitSL, Long limitEL) { + List result = new ArrayList<>(); + Integer temps = null; + Integer tempe = null; + //因为可以知道times是为4的倍数,所以长度肯定是偶数,不会出现索引越界的异常 + if (limitSL < times.get(0)) { + temps = 0; + } + if (limitEL > times.get(times.size() - 1)) { + tempe = times.size() - 1; + } + for (int i = 0; i < times.size() - 1; i++) { + if (temps != null & tempe != null) { + //判断都已经赋值后,跳出循环 + break; + } + //锁定前值 + if (times.get(i).equals(limitSL)) { + //相等则给起始时间赋值 + temps = i; + } else if (times.get(i + 1).equals(limitSL)) { + temps = i + 1; + } else if (times.get(i) < limitSL & times.get(i + 1) > limitSL) { + //当起始时间处于中间时,将后值赋值给temps + temps = i + 1; + } + //锁定后值 + if (times.get(i).equals(limitEL)) { + //相等则给起始时间赋值 + tempe = i; + } else if (times.get(i + 1).equals(limitEL)) { + tempe = i + 1; + } else if (times.get(i) < limitEL & times.get(i + 1) > limitEL) { + //当起始时间处于中间时,将前值赋值给temps + tempe = i; + } + } + if (temps == null) { + temps = 0; + } + if (tempe == null) { + tempe = times.size() - 1; + } + result.add(temps); + result.add(tempe); + return result; + } + + + /*** + * 用采数据,根据用户选择的时间窗口过滤出稍后用于计算的用采数据 + */ + private Map>> getFinalUserData(DealDataResult dealDataResult, List dateStr) { + Map>> totalData = dealDataResult.getTotalListData(); + Map>> finalData = new HashMap<>(16); + /*第一个参数pNode 如果时间范围内完整性不足90%的节点,不参与责任量化统计,因为之前处理过用采数据,此时只需要判断是否满足100%就可以判断*/ + int dueCounts = dateStr.size() * 96; + Set userNames = totalData.keySet(); + for (String userName : userNames) { + int realCounts = 0; + Map> temp = totalData.get(userName); + for (String date : dateStr) { + if (CollectionUtil.isNotEmpty(temp.get(date))) { + realCounts = realCounts + temp.get(date).size(); + } + } + if (realCounts == dueCounts) { + //只有期望和实际数量一致的时候才作为计算用户 + finalData.put(userName, temp); + } + } + return finalData; + } + + /*** + * 处理获取pNum参数 + */ + private RespCommon getPNumAndInterval(Map>> finalData, String lineId, List dateStr) { + int pNum; + LineDetailDataVO lineDetailData = lineService.getLineDetailData(lineId); + int lineInterval = lineDetailData.getTimeInterval(); + int userIntervalTime; + if (lineInterval == INTERVAL_TIME_1 || lineInterval == INTERVAL_TIME_3 || lineInterval == INTERVAL_TIME_5) { + userIntervalTime = INTERVAL_TIME_15; + pNum = dateStr.size() * WINDOW_96; + } else { + userIntervalTime = INTERVAL_TIME_30; + pNum = dateStr.size() * WINDOW_48; + finalData = dealFinalDataByLineInterval(finalData); + } + return new RespCommon(pNum, userIntervalTime, lineInterval); + } + + + /*** + * 获取责任需要的谐波数据 + */ + private RespHarmData getRespHarmData(ResponsibilityCalculateParam responsibilityCalculateParam, int lineInterval) { + HarmHistoryDataDTO data = historyHarmonicService.getHistoryHarmData(new HistoryHarmParam(responsibilityCalculateParam.getSearchBeginTime(), responsibilityCalculateParam.getSearchEndTime(), responsibilityCalculateParam.getLineId(), responsibilityCalculateParam.getType(), responsibilityCalculateParam.getTime())); + List historyData = data.getHistoryData(); + historyData = getDataWithLineInterval(historyData, lineInterval); + //理论上此处的historyData的长度等于pNum,开始填充harm_data + float[] harmData = new float[144000]; + //谐波波形的横轴时间集合 + List harmTime = new ArrayList<>(); + for (int i = 0; i < historyData.size(); i++) { + Float value = historyData.get(i).getValue(); +// if (value != null) { +// value = value * 1000; +// } + harmData[i] = value; + harmTime.add(PubUtils.instantToDate(historyData.get(i).getTime()).getTime()); + } + return new RespHarmData(harmData, harmTime, data.getOverLimit()); + } + + + /** + * 获取背景用户的下级用户及其数据 + */ + + public List test(List userList, String startTime, String endTime){ + List userDataExcelList = new ArrayList<>(); + PHistoryHarmParam param = new PHistoryHarmParam(); + param.setSearchBeginTime(startTime); + param.setSearchEndTime(endTime); + param.setLineIds(userList); + List dataHarmPList = historyHarmonicService.getHarmonicPData(param); + Map> collect = dataHarmPList.stream().collect(Collectors.groupingBy(DataHarmPowerP::getLineId)); + List result = new ArrayList<>(); + collect.forEach((k,v)->{ + LineDetailDataVO lineDetailData = lineService.getLineDetailData(k); + + //数据补全 + List dataHarmPowerPList = linearInterpolate(startTime.concat(InfluxDBTableConstant.START_TIME), endTime.concat(InfluxDBTableConstant.END_TIME), lineDetailData.getTimeInterval()*60, v); + //数据间隔转换成15分钟 + dataHarmPowerPList =convertTo15MinuteInterval(dataHarmPowerPList,lineDetailData.getTimeInterval()); + List userDataExcels = dataHarmPowerPList.stream().map(temp -> { + UserDataExcel userDataExcel = new UserDataExcel(); + userDataExcel.setTime(format.format(temp.getTime().atZone(ZoneId.systemDefault()))); + userDataExcel.setWork(BigDecimal.valueOf(temp.getP())); + userDataExcel.setUserId(k); + userDataExcel.setUserName(lineDetailData.getObjName()); + + return userDataExcel; + }).collect(Collectors.toList()); + + userDataExcelList.addAll(userDataExcels); + }); + + System.out.println(result.size()); + + return userDataExcelList; + } + + private static List convertTo15MinuteInterval(List dataHarmPowerPList, int interval) { + List result = new ArrayList<>(); + Instant startTime = dataHarmPowerPList.get(0).getTime(); + Instant endTime = dataHarmPowerPList.get(dataHarmPowerPList.size()-1).getTime(); + //向内寻找15分钟的约数 + while (startTime.atZone(ZoneId.systemDefault()).getMinute()%15!=0 ){ + startTime = startTime.plusSeconds(60); + } + while (endTime.atZone(ZoneId.systemDefault()).getMinute()%15!=0 ){ + endTime = endTime.minusSeconds(60); + } + + Instant currentTime = startTime; + List timePoints15Min = new ArrayList<>(); + + while (!currentTime.isAfter(endTime)) { + timePoints15Min.add(currentTime); + currentTime = currentTime.plusSeconds(15*60); + } + + if(interval==1||interval==3||interval==5){ + result = dataHarmPowerPList.stream().filter(temp->timePoints15Min.contains(temp.getTime())).collect(Collectors.toList()); + } + else if(interval==10){ + // 对每个15分钟时间点处理数据 + for (Instant targetTime : timePoints15Min) { + // 查找目标时间点前后最近的两个10分钟数据点 + DataHarmPowerP before = null; + DataHarmPowerP after = null; + + for (DataHarmPowerP data : dataHarmPowerPList) { + if (!data.getTime().isAfter(targetTime)) { + before = data; + } else { + after = data; + break; + } + } + + Double value; + + // 如果正好有10分钟数据点在这个15分钟时间点上 + if (before != null && before.getTime().equals(targetTime)) { + value = before.getP(); + } + // 如果目标时间点正好在两个10分钟数据点中间 + else if (before != null && after != null) { + // 计算平均值 + value = (before.getP() + after.getP()) / 2.0; + } + // 如果只有前一个点(边界情况) + else if (before != null) { + value = before.getP(); + } + // 如果只有后一个点(边界情况) + else if (after != null) { + value = after.getP(); + } + // 理论上不会发生 + else { + continue; + } + DataHarmPowerP dataHarmPowerP = new DataHarmPowerP(); + dataHarmPowerP.setP(value); + dataHarmPowerP.setTime(targetTime); + dataHarmPowerP.setLineId(dataHarmPowerPList.get(0).getLineId()); + result.add(dataHarmPowerP); + } + }else { + throw new BusinessException(AdvanceResponseEnum.INTERVAL_ERROR); + } + return result; + } + + + /** + * 将字符串时间转换为Instant + * @param timeStr 时间字符串,格式为"yyyy-MM-dd HH:mm:ss" + * @return Instant对象 + */ + private static Instant parseTime(String timeStr) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + LocalDateTime localDateTime = LocalDateTime.parse(timeStr, formatter); + return localDateTime.atZone(ZoneId.systemDefault()).toInstant(); + } + + /** + * 线性补全时间序列数据 + * @param startTimeStr 开始时间字符串 + * @param endTimeStr 结束时间字符串 + * @param interval 数据时间间隔(秒) + * @param originalData 原始数据列表 + * @return 补全后的数据列表 + */ + public static List linearInterpolate(String startTimeStr, String endTimeStr, int interval, List originalData) { + List result = new ArrayList<>(); + + // 如果原始数据为空,直接返回空列表 + if (originalData == null || originalData.isEmpty()) { + return result; + } + + // 解析时间字符串 + Instant startTime = parseTime(startTimeStr); + Instant endTime = parseTime(endTimeStr); + + + + + // 按时间排序 + originalData.sort(Comparator.comparing(DataHarmPowerP::getTime)); + + // 创建时间点列表 + List timePoints = new ArrayList<>(); + Instant currentTime = startTime; + while (!currentTime.isAfter(endTime)) { + timePoints.add(currentTime); + currentTime = currentTime.plusSeconds(interval); + } + + // 使用双指针进行线性插值 + int dataIndex = 0; + for (Instant currentPoint : timePoints) { + + // 如果当前时间点早于所有数据点,使用第一个数据点的值 + if (currentPoint.isBefore(originalData.get(0).getTime())) { + DataHarmPowerP dataHarmPowerP = new DataHarmPowerP(); + + dataHarmPowerP.setP(originalData.get(0).getP()); + dataHarmPowerP.setTime(currentPoint); + dataHarmPowerP.setLineId(originalData.get(0).getLineId()); + result.add(dataHarmPowerP); + continue; + } + + // 如果当前时间点晚于所有数据点,使用最后一个数据点的值 + if (currentPoint.isAfter(originalData.get(originalData.size() - 1).getTime())) { + DataHarmPowerP dataHarmPowerP = new DataHarmPowerP(); + + dataHarmPowerP.setP(originalData.get(originalData.size() - 1).getP()); + dataHarmPowerP.setTime(currentPoint); + dataHarmPowerP.setLineId(originalData.get(0).getLineId()); + result.add(dataHarmPowerP); + + continue; + } + + // 找到当前时间点所在的数据区间 + while (dataIndex < originalData.size() - 1 && + originalData.get(dataIndex + 1).getTime().isBefore(currentPoint)) { + dataIndex++; + } + + // 如果正好有数据点在这个时间点上 + if (originalData.get(dataIndex).getTime().equals(currentPoint)) { + DataHarmPowerP dataHarmPowerP = new DataHarmPowerP(); + + dataHarmPowerP.setP( originalData.get(dataIndex).getP()); + dataHarmPowerP.setTime(currentPoint); + dataHarmPowerP.setLineId(originalData.get(0).getLineId()); + result.add(dataHarmPowerP); + + continue; + } + + // 如果当前时间点在前一个数据点和后一个数据点之间,进行线性插值 + DataHarmPowerP prev = originalData.get(dataIndex); + DataHarmPowerP next = originalData.get(dataIndex + 1); + + long timeDiff = next.getTime().getEpochSecond() - prev.getTime().getEpochSecond(); + long currentDiff = currentPoint.getEpochSecond() - prev.getTime().getEpochSecond(); + + double slope = (next.getP() - prev.getP()) / timeDiff; + double interpolatedValue = prev.getP() + slope * currentDiff; + DataHarmPowerP dataHarmPowerP = new DataHarmPowerP(); + + dataHarmPowerP.setP( interpolatedValue); + dataHarmPowerP.setTime(currentPoint); + dataHarmPowerP.setLineId(originalData.get(0).getLineId()); + result.add(dataHarmPowerP); + } + + + return result; + } + + public static void main(String[] args) { + // 创建测试数据 - 10分钟间隔 + List testData = new ArrayList<>(); + Instant baseTime = Instant.parse("2025-01-01T00:10:00Z"); + + + // 创建10分钟间隔的数据点 (0, 10, 20, 30, 40, 50分钟) + for (int i = 0; i < 13; i++) { + DataHarmPowerP dataHarmPowerP = new DataHarmPowerP(); + dataHarmPowerP.setP(10.0 + i * 2.0); + + dataHarmPowerP.setTime(baseTime.plusSeconds(i * 600)); + dataHarmPowerP.setLineId("123"); + testData.add(dataHarmPowerP); + } + + + + System.out.println("原始数据 (10分钟间隔):"); + for (DataHarmPowerP data : testData) { + ZonedDateTime zdt = data.getTime().atZone(ZoneId.systemDefault()); + System.out.println(data.getLineId() + " - " + + zdt.getHour() + ":" + + String.format("%02d", zdt.getMinute()) + + " - " + data.getP()); + } + + // 转换为15分钟间隔 + List convertedData = convertTo15MinuteInterval(testData,10); + + System.out.println("\n转换后的数据 (15分钟间隔):"); + for (DataHarmPowerP data : convertedData) { + ZonedDateTime zdt = data.getTime().atZone(ZoneId.systemDefault()); + System.out.println(data.getLineId() + " - " + + zdt.getHour() + ":" + + String.format("%02d", zdt.getMinute()) + + " - " + data.getP()); + } + + // 按lineId分组显示结果 + Map> groupedResults = new HashMap<>(); + for (DataHarmPowerP data : convertedData) { + groupedResults.putIfAbsent(data.getLineId(), new ArrayList<>()); + groupedResults.get(data.getLineId()).add(data); + } + + System.out.println("\n按lineId分组的结果:"); + for (String lineId : groupedResults.keySet()) { + System.out.println("Line: " + lineId); + for (DataHarmPowerP data : groupedResults.get(lineId)) { + ZonedDateTime zdt = data.getTime().atZone(ZoneId.systemDefault()); + System.out.println(" " + zdt.getHour() + ":" + + String.format("%02d", zdt.getMinute()) + + ": " + data.getP()); + } + } + } + + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespUserDataIntegrityServiceImpl.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespUserDataIntegrityServiceImpl.java new file mode 100644 index 0000000..cc128a2 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespUserDataIntegrityServiceImpl.java @@ -0,0 +1,33 @@ +package com.njcn.product.advance.responsility.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.product.advance.responsility.mapper.RespUserDataIntegrityMapper; +import com.njcn.product.advance.responsility.pojo.param.UserDataIntegrityParam; +import com.njcn.product.advance.responsility.pojo.po.RespUserDataIntegrity; +import com.njcn.product.advance.responsility.service.IRespUserDataIntegrityService; +import com.njcn.web.factory.PageFactory; +import org.springframework.stereotype.Service; + + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2023-07-13 + */ +@Service +public class RespUserDataIntegrityServiceImpl extends ServiceImpl implements IRespUserDataIntegrityService { + + @Override + public Page userDataIntegrityList(UserDataIntegrityParam userDataIntegrityParam) { + QueryWrapper lambdaQueryWrapper = new QueryWrapper<>(); + lambdaQueryWrapper.eq("pqs_resp_user_data_integrity.user_data_id", userDataIntegrityParam.getUserDataId()) + .orderByDesc("pqs_resp_user_data_integrity.create_time").like("pqs_resp_user_data_integrity.user_name",userDataIntegrityParam.getSearchValue()); + return this.baseMapper.page(new Page<>(PageFactory.getPageNum(userDataIntegrityParam), PageFactory.getPageSize(userDataIntegrityParam)), lambdaQueryWrapper); + } +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespUserDataServiceImpl.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespUserDataServiceImpl.java new file mode 100644 index 0000000..13b5850 --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/service/impl/RespUserDataServiceImpl.java @@ -0,0 +1,486 @@ +package com.njcn.product.advance.responsility.service.impl; + +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.afterturn.easypoi.handler.inter.IReadHandler; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.StrPool; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.common.pojo.dto.SelectOption; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.utils.FileUtil; +import com.njcn.common.utils.PubUtils; +import com.njcn.db.mybatisplus.constant.DbConstant; +import com.njcn.oss.constant.OssPath; +import com.njcn.oss.utils.FileStorageUtil; +import com.njcn.product.advance.responsility.mapper.RespUserDataMapper; +import com.njcn.product.advance.responsility.pojo.bo.DealDataResult; +import com.njcn.product.advance.responsility.pojo.bo.DealUserDataResult; +import com.njcn.product.advance.responsility.pojo.bo.UserDataExcel; +import com.njcn.product.advance.eventSource.pojo.enums.AdvanceResponseEnum; +import com.njcn.product.advance.responsility.pojo.po.RespUserData; +import com.njcn.product.advance.responsility.pojo.po.RespUserDataIntegrity; +import com.njcn.product.advance.responsility.service.IRespUserDataIntegrityService; +import com.njcn.product.advance.responsility.service.IRespUserDataService; +import com.njcn.web.factory.PageFactory; +import com.njcn.web.pojo.param.BaseParam; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2023-07-13 + */ +@Service +@RequiredArgsConstructor +public class RespUserDataServiceImpl extends ServiceImpl implements IRespUserDataService { + + private final FileStorageUtil fileStorageUtil; + + private final IRespUserDataIntegrityService respUserDataIntegrityService; + + @Override + public void uploadUserData(MultipartFile file, HttpServletResponse response) { + ImportParams params = new ImportParams(); + List userDataExcels = new ArrayList<>(); + try { + // 显式设置SAX解析器(如果添加依赖后仍有问题) + System.setProperty("org.xml.sax.driver", "org.apache.xerces.parsers.SAXParser"); + + ExcelImportUtil.importExcelBySax(file.getInputStream(), UserDataExcel.class, params, new IReadHandler() { + @Override + public void handler(UserDataExcel o) { + userDataExcels.add(o); + } + + @Override + public void doAfterAll() { + + } + }); + //处理用户上传的用采数据内容 + analysisUserData(userDataExcels, file.getOriginalFilename()); + } catch (Exception e) { + throw new BusinessException(AdvanceResponseEnum.ANALYSIS_USER_DATA_ERROR); + } + } + + @Override + public Page userDataList(BaseParam queryParam) { + QueryWrapper respUserDataQueryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(queryParam)) { + //查询参数不为空,进行条件填充 + if (StrUtil.isNotBlank(queryParam.getSearchValue())) { + //仅提供用采名称 + respUserDataQueryWrapper.and(param -> param.like("pqs_resp_user_data.name", queryParam.getSearchValue())); + } + //排序 + if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) { + respUserDataQueryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy())); + } else { + //没有排序参数,默认根据sort字段排序,没有排序字段的,根据updateTime更新时间排序 + respUserDataQueryWrapper.orderBy(true, false, "pqs_resp_user_data.update_time"); + } + } else { + respUserDataQueryWrapper.orderBy(true, false, "pqs_resp_user_data.update_time"); + } + respUserDataQueryWrapper.eq("pqs_resp_user_data.state", DataStateEnum.ENABLE.getCode()); + return this.baseMapper.page(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), respUserDataQueryWrapper); + } + + @Override + public List userDataSelect() { + List selectOptions = new ArrayList<>(); + LambdaQueryWrapper respUserDataLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respUserDataLambdaQueryWrapper.eq(RespUserData::getState, DataStateEnum.ENABLE.getCode()) + .orderByDesc(RespUserData::getUpdateTime); + List respUserData = this.baseMapper.selectList(respUserDataLambdaQueryWrapper); + if (CollectionUtil.isNotEmpty(respUserData)) { + selectOptions = respUserData.stream().map(temp -> new SelectOption(temp.getName(), temp.getId())).collect(Collectors.toList()); + } + return selectOptions; + } + + @Override + public void deleteUserDataByIds(List ids) { + this.baseMapper.deleteUserDataByIds(ids); + } + + @Override + public List getUserDataExcelList(String userDataId) { + LambdaQueryWrapper userDataLambdaQueryWrapper = new LambdaQueryWrapper<>(); + userDataLambdaQueryWrapper.eq(RespUserData::getId, userDataId).eq(RespUserData::getState, DataStateEnum.ENABLE.getCode()); + RespUserData respUserData = this.getOne(userDataLambdaQueryWrapper); + if (Objects.isNull(respUserData)) { + throw new BusinessException(AdvanceResponseEnum.USER_DATA_NOT_FOUND); + } + InputStream fileStream = fileStorageUtil.getFileStream(respUserData.getDataPath()); + String excelDataStr = IoUtil.read(fileStream, CharsetUtil.UTF_8); + //将文件流转为list集合 + List userDataExcels = JSONArray.parseArray(excelDataStr, UserDataExcel.class); + if (CollectionUtils.isEmpty(userDataExcels)) { + throw new BusinessException(AdvanceResponseEnum.USER_DATA_NOT_FOUND); + } + return userDataExcels; + } + + + /** + * 根据流获取出用采有功功率数据 + */ + private void analysisUserData(List userDataExcelList, String fileName) { + List exportExcelList = new ArrayList<>(); + RespUserData respUserData; + //判断数据提取情况 + if (CollectionUtils.isEmpty(userDataExcelList)) { + throw new BusinessException(AdvanceResponseEnum.USER_DATA_EMPTY); + } + DealDataResult dealDataResult = getStanderData(userDataExcelList, 0); + Map>> totalData = dealDataResult.getTotalData(); + //收集所有的日期,以便获取起始日期和截止日期 + List dates = dealDataResult.getDates(); + //将前面获取出来的日期进行排序,提供入库 + List resultDates = getSortDate(dates); + LocalDate endTime = resultDates.get(resultDates.size() - 1); + LocalDate startTime = resultDates.get(0); + //针对每个用户的数据进行完整度的判断 todo 暂且认为所有用户的时间跨度是一样的,比如都是15天或者都是30天,不存在有的用户5天数据,有的用户10天数据 + Map> tempResult = new HashMap<>(); + List respUserDataIntegrities = new ArrayList<>(); + Set userNames = totalData.keySet(); + for (String name : userNames) { + Map> userDataTemp = totalData.get(name); + //现在数据拿到了,但是因为是hashkey,所以日期顺序是乱的-->怎么变成有序的呢 + Set times = userDataTemp.keySet(); + //循环日期处理数据 + for (String time : times) { + DealUserDataResult dealtData = dealUserData(name, userDataTemp.get(time), time, 15); + List UserDataExcelTemp = dealtData.getCompleted(); + List UserDataExcel; + if (CollectionUtils.isEmpty(UserDataExcelTemp) && Objects.nonNull(dealtData.getRespUserDataIntegrity())) { + //为空,说明补齐操作没有进行,选择填充缺失数据即可 + respUserDataIntegrities.add(dealtData.getRespUserDataIntegrity()); + UserDataExcel = dealtData.getLack(); + } else { + //填充补齐完整性后的数据 + UserDataExcel = UserDataExcelTemp; + } + List userDatas = tempResult.get(name); + if (CollectionUtil.isNotEmpty(UserDataExcel)) { + if (CollectionUtils.isEmpty(userDatas)) { + userDatas = new ArrayList<>(UserDataExcel); + } else { + userDatas.addAll(UserDataExcel); + } + } + tempResult.put(name, userDatas); + } + } + //完成后,开始将数据按公司排序,然后输出到指定表格中,方便下次使用 + for (String name : userNames) { + List tempUserData = tempResult.get(name); + //按时间排序 + Collections.sort(tempUserData); + exportExcelList.addAll(tempUserData); + } + //输出到报表中 + String fileNameWithOutSuffix = fileName.substring(0, fileName.indexOf('.')); + fileNameWithOutSuffix = fileNameWithOutSuffix.concat(LocalDateTimeUtil.format(startTime, DatePattern.PURE_DATE_PATTERN)).concat(StrPool.DASHED).concat(LocalDateTimeUtil.format(endTime, DatePattern.PURE_DATE_PATTERN)); + //处理完后的用采数据,生成json文件流到oss服务器 + JSONArray finalUserData = JSONArray.parseArray(JSON.toJSONString(exportExcelList)); + InputStream reportStream = IoUtil.toStream(finalUserData.toString(), CharsetUtil.UTF_8); + String ossPath = fileStorageUtil.uploadStream(reportStream, OssPath.RESPONSIBILITY_USER_DATA, FileUtil.generateFileName("json")); + //入库前进行查询操作,存在则更新,不存在则插入 + LambdaQueryWrapper respUserDataLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respUserDataLambdaQueryWrapper.eq(RespUserData::getName, fileNameWithOutSuffix) + .eq(RespUserData::getStartTime, startTime) + .eq(RespUserData::getEndTime, endTime) + .eq(RespUserData::getState, DataStateEnum.ENABLE.getCode()); + respUserData = this.baseMapper.selectOne(respUserDataLambdaQueryWrapper); + //不存在则插入 + if (Objects.isNull(respUserData)) { + respUserData = new RespUserData(); + respUserData.setEndTime(endTime); + respUserData.setStartTime(startTime); + respUserData.setName(fileNameWithOutSuffix); + respUserData.setDataPath(ossPath); + respUserData.setState(DataStateEnum.ENABLE.getCode()); + this.baseMapper.insert(respUserData); + if (CollectionUtil.isNotEmpty(respUserDataIntegrities)) { + //关联插入数据 户号,监测点号,户名,时间,完整性 + for (RespUserDataIntegrity respUserDataIntegrity : respUserDataIntegrities) { + respUserDataIntegrity.setUserDataId(respUserData.getId()); + } + //插入操作 + respUserDataIntegrityService.saveBatch(respUserDataIntegrities); + respUserData.setIntegrity(1); + } else { + respUserData.setIntegrity(0); + } + this.baseMapper.updateById(respUserData); + } else { + //存在则更新,需要删除之前的oss文件 + fileStorageUtil.deleteFile(respUserData.getDataPath()); + if (CollectionUtil.isNotEmpty(respUserDataIntegrities)) { + LambdaQueryWrapper respUserDataIntegrityLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respUserDataIntegrityLambdaQueryWrapper.eq(RespUserDataIntegrity::getUserDataId, respUserData.getId()); + respUserDataIntegrityService.remove(respUserDataIntegrityLambdaQueryWrapper); + for (RespUserDataIntegrity respUserDataIntegrity : respUserDataIntegrities) { + respUserDataIntegrity.setUserDataId(respUserData.getId()); + } + //插入操作 + respUserDataIntegrityService.saveBatch(respUserDataIntegrities); + respUserData.setIntegrity(1); + } else { + respUserData.setIntegrity(0); + } + respUserData.setDataPath(ossPath); + this.baseMapper.updateById(respUserData); + } + + } + + /** + * 解析用采数据为一个标准格式 + */ + public static DealDataResult getStanderData(List userDataExcelBodies, int flag) { + DealDataResult result = new DealDataResult(); + //收集所有的日期,以便获取起始日期和截止日期 + List dates = new ArrayList<>(); + Map>> totalData = new HashMap<>(); + Map>> totalListData = new HashMap<>(); + for (UserDataExcel UserDataExcel : userDataExcelBodies) { + //第一个key + String key = UserDataExcel.getUserId() + "@" + UserDataExcel.getLine() + "@" + UserDataExcel.getUserName(); + String time = UserDataExcel.getTime().substring(0, 10); + if (!dates.contains(time)) { + dates.add(time); + } + if (!totalData.containsKey(key)) { + if (flag == 0) { + //Map形式,避免后面补齐数据嵌套循环 + Map userDatas = new HashMap<>(); + userDatas.put(PubUtils.getSecondsAsZero(DateUtil.parse(UserDataExcel.getTime(), DatePattern.NORM_DATETIME_PATTERN)), UserDataExcel); + Map> dataToUserDatas = new HashMap<>(); + dataToUserDatas.put(time, userDatas); + totalData.put(key, dataToUserDatas); + } else if (flag == 1) { + //List形式,避免后面责任数据提取嵌套循环 + List userListDatas = new ArrayList<>(); + userListDatas.add(UserDataExcel); + Map> dataToUserListDatas = new HashMap<>(); + dataToUserListDatas.put(time, userListDatas); + totalData.put(key, new HashMap<>()); + totalListData.put(key, dataToUserListDatas); + } + } else { + if (flag == 0) { + //Map形式,避免后面补齐数据嵌套循环 + Map> dataToUserDatas = totalData.get(key); + Map userDatas = dataToUserDatas.get(time); + //某日凌晨,还没存放该日的数据 + if (CollectionUtils.isEmpty(userDatas)) { + userDatas = new HashMap<>(); + userDatas.put(PubUtils.getSecondsAsZero(DateUtil.parse(UserDataExcel.getTime(), DatePattern.NORM_DATETIME_PATTERN)), UserDataExcel); + dataToUserDatas.put(time, userDatas); + } else { + //累加该日的数据 + userDatas.put(PubUtils.getSecondsAsZero(DateUtil.parse(UserDataExcel.getTime(), DatePattern.NORM_DATETIME_PATTERN)), UserDataExcel); + dataToUserDatas.put(time, userDatas); + } + totalData.put(key, dataToUserDatas); + } else if (flag == 1) { + //List形式,避免后面责任数据提取嵌套循环 + Map> dataToUserListDatas = totalListData.get(key); + List userListDatas = dataToUserListDatas.get(time); + if (CollectionUtils.isEmpty(userListDatas)) { + userListDatas = new ArrayList<>(); + userListDatas.add(UserDataExcel); + dataToUserListDatas.put(time, userListDatas); + } else { + userListDatas.add(UserDataExcel); + dataToUserListDatas.put(time, userListDatas); + } + totalListData.put(key, dataToUserListDatas); + } + } + + } + result.setDates(dates); + result.setTotalData(totalData); + result.setTotalListData(totalListData); + return result; + } + + /** + * 将日期排序后返回 + */ + private List getSortDate(List dates) { + List result = new ArrayList<>(); + for (String date : dates) { + LocalDate temp = LocalDateTimeUtil.parseDate(date, DatePattern.NORM_DATE_PATTERN); + result.add(temp); + } + if (!CollectionUtils.isEmpty(result)) { + Collections.sort(result); + } + return result; + } + + + /** + * 处理用户每日数据 + * + * @param name 用户名 + * @param beforeDeal 处理前的用户数据 + */ + private DealUserDataResult dealUserData(String name, Map beforeDeal, String time, int step) { + DealUserDataResult result = new DealUserDataResult(); + String[] userFlag = name.split("@"); + //每天的最开是的数据是从00:00:00开始的,所以起始时间为time + 00:00:00 + List completed = new ArrayList<>(); + List lack = new ArrayList<>(); + if (CollectionUtils.isEmpty(beforeDeal)) { + return result; + } else { + String timeTemp = time + " 00:00:00"; + Date date = DateUtil.parse(timeTemp, DatePattern.NORM_DATETIME_PATTERN); + int count = 24 * 60 / 15; + if ((float) beforeDeal.size() / (float) count < 0.9) { + Set dates = beforeDeal.keySet(); + for (Date tempDate : dates) { + UserDataExcel UserDataExcel = beforeDeal.get(tempDate); + if (UserDataExcel.getWork() != null) { + lack.add(UserDataExcel); + } + } + RespUserDataIntegrity respUserDataIntegrity = new RespUserDataIntegrity(); + respUserDataIntegrity.setIntegrity(BigDecimal.valueOf((double) lack.size() / 96.0)); + respUserDataIntegrity.setLackDate(LocalDateTimeUtil.parseDate(time, DatePattern.NORM_DATE_PATTERN)); + respUserDataIntegrity.setUserName(userFlag[2]); + respUserDataIntegrity.setLineNo(userFlag[1]); + respUserDataIntegrity.setUserNo(userFlag[0]); + result.setLack(lack); + result.setRespUserDataIntegrity(respUserDataIntegrity); + return result; + } else { + for (int i = 0; i < count; i++) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.MINUTE, step * i); + UserDataExcel UserDataExcel = beforeDeal.get(calendar.getTime()); + if (UserDataExcel != null && UserDataExcel.getWork() != null) { + completed.add(UserDataExcel); + } else { + //找到前一个时间点值 + Float perValue = getPreValue(date, calendar.getTime(), beforeDeal); + //找到后一个时间点值 + Float appendValue = getAppendValue(date, count, step, calendar.getTime(), beforeDeal); + UserDataExcel temp = new UserDataExcel(); + SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN); + temp.setTime(sdf.format(calendar.getTime())); + temp.setUserId(userFlag[0]); + temp.setLine(userFlag[1]); + temp.setUserName(userFlag[2]); + //还需要判断前值和后值为空的情况 + if (null == perValue && null == appendValue) { + temp.setWork(new BigDecimal("0.0")); + } else if (null == perValue) { + temp.setWork(new BigDecimal(appendValue)); + } else if (null == appendValue) { + temp.setWork(new BigDecimal(perValue)); + } else { + temp.setWork(BigDecimal.valueOf((perValue + appendValue) / 2)); + } + completed.add(temp); + } + } + } + } + result.setCompleted(completed); + return result; + } + + /** + * 递归找前值 + * + * @param date 起始时间 + * @param time 当前时间 + * @param beforeDeal 处理前的数据 + */ + private Float getPreValue(Date date, Date time, Map beforeDeal) { + Float result; + if (date.equals(time)) { + return null; + } else { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(time); + calendar.add(Calendar.MINUTE, -15); + UserDataExcel temp = beforeDeal.get(calendar.getTime()); + if (temp == null || temp.getWork() == null) { + result = getPreValue(date, calendar.getTime(), beforeDeal); + } else { + result = temp.getWork().floatValue(); + } + } + return result; + } + + /** + * 递归找后置 + * + * @param date 起始时间 + * @param count 一天时间的总计数 + * @param step 间隔分钟 + * @param time 截止时间 + */ + private Float getAppendValue(Date date, int count, int step, Date time, Map beforeDeal) { + Float result; + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.set(Calendar.MINUTE, (count - 1) * step); + if (time.equals(calendar.getTime())) { + return null; + } else { + Calendar calendar1 = Calendar.getInstance(); + calendar1.setTime(time); + calendar1.add(Calendar.MINUTE, 15); + UserDataExcel temp = beforeDeal.get(calendar1.getTime()); + if (temp == null || temp.getWork() == null) { + result = getAppendValue(date, count, step, calendar1.getTime(), beforeDeal); + } else { + result = temp.getWork().floatValue(); + } + } + return result; + } + +} diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/utils/MathUtils.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/utils/MathUtils.java new file mode 100644 index 0000000..9fce3cb --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/utils/MathUtils.java @@ -0,0 +1,314 @@ +package com.njcn.product.advance.responsility.utils; + +import com.njcn.product.advance.responsility.pojo.constant.HarmonicConstants; +import org.apache.commons.math3.linear.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 数学工具类 + * 提供基础数学计算功能 + * + * @author hongawen + * @version 1.0 + */ +public class MathUtils { + + private static final Logger logger = LoggerFactory.getLogger(MathUtils.class); + + /** + * 计算协方差 + * + * @param x 数据序列1 + * @param y 数据序列2 + * @param width 数据窗宽度 + * @return 协方差值 + */ + public static double covariance(double[] x, double[] y, int width) { + if (x == null || y == null || width <= 0) { + throw new IllegalArgumentException("Invalid input parameters for covariance calculation"); + } + + if (x.length < width || y.length < width) { + throw new IllegalArgumentException("Data length is less than window width"); + } + + double meanX = 0.0; + double meanY = 0.0; + + // 计算均值 + for (int i = 0; i < width; i++) { + meanX += x[i]; + meanY += y[i]; + } + meanX /= width; + meanY /= width; + + // 计算协方差 + double cov = 0.0; + for (int i = 0; i < width; i++) { + cov += (x[i] - meanX) * (y[i] - meanY); + } + + return cov / (width - 1); + } + + /** + * 计算协方差(float版本) + */ + public static float covariance(float[] x, float[] y, int width) { + double[] dx = new double[x.length]; + double[] dy = new double[y.length]; + for (int i = 0; i < x.length; i++) dx[i] = x[i]; + for (int i = 0; i < y.length; i++) dy[i] = y[i]; + return (float) covariance(dx, dy, width); + } + + /** + * 计算Pearson相关系数 + * + * @param x 数据序列1 + * @param y 数据序列2 + * @param count 数据长度 + * @return Pearson相关系数 + */ + public static double pearsonCorrelation(double[] x, double[] y, int count) { + if (x == null || y == null || count <= 0) { + throw new IllegalArgumentException("Invalid input parameters for Pearson correlation"); + } + + double meanX = 0.0; + double meanY = 0.0; + + // 计算均值 + for (int i = 0; i < count; i++) { + meanX += x[i]; + meanY += y[i]; + } + meanX /= count; + meanY /= count; + + // 计算相关系数的各个部分 + double numerator = 0.0; + double denomX = 0.0; + double denomY = 0.0; + + for (int i = 0; i < count; i++) { + double dx = x[i] - meanX; + double dy = y[i] - meanY; + numerator += dx * dy; + denomX += dx * dx; + denomY += dy * dy; + } + + double denominator = Math.sqrt(denomX * denomY); + + if (Math.abs(denominator) < HarmonicConstants.EPSILON) { + logger.warn("Denominator is too small in Pearson correlation calculation"); + return 0.0; + } + + return numerator / denominator; + } + + /** + * 计算Pearson相关系数(float版本) + */ + public static float pearsonCorrelation(float[] x, float[] y, int count) { + double[] dx = new double[count]; + double[] dy = new double[count]; + for (int i = 0; i < count; i++) { + dx[i] = x[i]; + dy[i] = y[i]; + } + return (float) pearsonCorrelation(dx, dy, count); + } + + /** + * 计算协方差矩阵(SXX) + * + * @param data 数据矩阵 [时间][节点] + * @param width 窗口宽度 + * @param nodeCount 节点数 + * @return 协方差矩阵 + */ + public static double[][] covarianceMatrix(double[][] data, int width, int nodeCount) { + double[][] covMatrix = new double[nodeCount][nodeCount]; + + for (int i = 0; i < nodeCount; i++) { + for (int j = 0; j < nodeCount; j++) { + double[] col1 = new double[width]; + double[] col2 = new double[width]; + + for (int k = 0; k < width; k++) { + col1[k] = data[k][i]; + col2[k] = data[k][j]; + } + + covMatrix[i][j] = covariance(col1, col2, width); + } + } + + return covMatrix; + } + + /** + * 计算协方差矩阵(float版本) + */ + public static float[][] covarianceMatrix(float[][] data, int width, int nodeCount) { + float[][] covMatrix = new float[nodeCount][nodeCount]; + + for (int i = 0; i < nodeCount; i++) { + for (int j = 0; j < nodeCount; j++) { + float[] col1 = new float[width]; + float[] col2 = new float[width]; + + for (int k = 0; k < width; k++) { + col1[k] = data[k][i]; + col2[k] = data[k][j]; + } + + covMatrix[i][j] = covariance(col1, col2, width); + } + } + + return covMatrix; + } + + /** + * 计算协方差向量(SXY) + * + * @param data 数据矩阵 [时间][节点] + * @param y 目标向量 + * @param width 窗口宽度 + * @param nodeCount 节点数 + * @return 协方差向量 + */ + public static double[] covarianceVector(double[][] data, double[] y, int width, int nodeCount) { + double[] covVector = new double[nodeCount]; + + for (int i = 0; i < nodeCount; i++) { + double[] col = new double[width]; + for (int k = 0; k < width; k++) { + col[k] = data[k][i]; + } + covVector[i] = covariance(col, y, width); + } + + return covVector; + } + + /** + * 计算协方差向量(float版本) + */ + public static float[] covarianceVector(float[][] data, float[] y, int width, int nodeCount) { + float[] covVector = new float[nodeCount]; + + for (int i = 0; i < nodeCount; i++) { + float[] col = new float[width]; + for (int k = 0; k < width; k++) { + col[k] = data[k][i]; + } + covVector[i] = covariance(col, y, width); + } + + return covVector; + } + + /** + * 矩阵求逆 + * 使用Apache Commons Math库 + * + * @param matrix 输入矩阵 + * @return 逆矩阵 + */ + public static double[][] matrixInverse(double[][] matrix) { + RealMatrix realMatrix = new Array2DRowRealMatrix(matrix); + + try { + // 使用LU分解求逆 + DecompositionSolver solver = new LUDecomposition(realMatrix).getSolver(); + RealMatrix inverseMatrix = solver.getInverse(); + return inverseMatrix.getData(); + } catch (SingularMatrixException e) { + logger.error("Matrix is singular, cannot compute inverse", e); + throw new RuntimeException("Matrix inversion failed: singular matrix"); + } + } + + /** + * 计算矩阵的特征值 + * + * @param matrix 输入矩阵 + * @return 特征值数组 + */ + public static double[] eigenvalues(double[][] matrix) { + RealMatrix realMatrix = new Array2DRowRealMatrix(matrix); + EigenDecomposition eigenDecomposition = new EigenDecomposition(realMatrix); + return eigenDecomposition.getRealEigenvalues(); + } + + /** + * 归一化处理 + * 将数据归一化到[0,1]区间 + * + * @param data 输入数据 + * @return 归一化后的数据 + */ + public static double[] normalize(double[] data) { + if (data == null || data.length == 0) { + return data; + } + + double min = Double.MAX_VALUE; + double max = Double.MIN_VALUE; + + // 找最大最小值 + for (double value : data) { + min = Math.min(min, value); + max = Math.max(max, value); + } + + double range = max - min; + if (Math.abs(range) < HarmonicConstants.EPSILON) { + return new double[data.length]; // 返回全0数组 + } + + double[] normalized = new double[data.length]; + for (int i = 0; i < data.length; i++) { + normalized[i] = (data[i] - min) / range; + } + + return normalized; + } + + /** + * 数据对齐处理 + * 将不同采样间隔的数据对齐到相同的时间间隔 + * + * @param data 原始数据 + * @param originalInterval 原始采样间隔 + * @param targetInterval 目标采样间隔 + * @return 对齐后的数据 + */ + public static float[] alignData(float[] data, int originalInterval, int targetInterval) { + if (targetInterval % originalInterval != 0) { + throw new IllegalArgumentException( + "Target interval must be multiple of original interval"); + } + + int ratio = targetInterval / originalInterval; + int newLength = data.length / ratio; + float[] alignedData = new float[newLength]; + + for (int i = 0; i < newLength; i++) { + float sum = 0; + for (int j = 0; j < ratio; j++) { + sum += data[i * ratio + j]; + } + alignedData[i] = sum / ratio; + } + + return alignedData; + } +} \ No newline at end of file diff --git a/cn-advance/src/main/java/com/njcn/product/advance/responsility/utils/ResponsibilityAlgorithm.java b/cn-advance/src/main/java/com/njcn/product/advance/responsility/utils/ResponsibilityAlgorithm.java new file mode 100644 index 0000000..395d16e --- /dev/null +++ b/cn-advance/src/main/java/com/njcn/product/advance/responsility/utils/ResponsibilityAlgorithm.java @@ -0,0 +1,766 @@ +package com.njcn.product.advance.responsility.utils; + +import cn.hutool.core.bean.BeanUtil; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.advance.eventSource.pojo.enums.AdvanceResponseEnum; +import com.njcn.product.advance.responsility.model.QvvrDataEntity; +import org.apache.commons.math3.linear.*; +import org.ejml.data.DMatrixRMaj; +import org.ejml.dense.row.CommonOps_DDRM; +import org.ejml.dense.row.factory.DecompositionFactory_DDRM; +import org.ejml.interfaces.decomposition.EigenDecomposition_F64; +import org.ejml.simple.SimpleMatrix; + +import java.util.Arrays; + +/** + * 责任量化的算法细节: + * 此代码用以计算刻度不等两序列之间的动态相关系数与动态谐波责任指标; + * 随机数生成器生成原始谐波U数据,一维数组长度为LL;生成原始负荷PE数据,二维数组,(行)长度为TL,(列)宽度为P(表示P个负荷公司); + * 只能识别 U数据长度 为PE数据长度的 JIANGE倍,也即无法自动判别时间间隔,需人为定义; + * Width用以控制动态相关系数的计算窗宽; + * 使用库中函数进行矩阵构造与计算; + * 最终的到结果为:向量Core为动态典则相关系数 + * 矩阵simCor为剥离背景后的动态相关系数,其中每列为每一用户负荷 + * 矩阵HKdata为使用simCor计算的动态谐波责任指标,其中每列为每一用户负荷 + * 向量sumHKdata为超限额的谐波时,不同用户的长时谐波责任指标; + * 函数说明:cov(a,b)计算协方差; + * SXX(a,width)计算(行)长度为width序列的方差矩阵; + * SXY(a,b,width)计算长度为width两序列的协方差矩阵; + * TransCancor(Ma,Vb,width)计算长度为width的矩阵a与向量b的典则相关系数; + * SlideCanCor(a,b,width)计算a与b的在 窗宽 width下 的动态典则相关系数; + * SlideCor(a,b,slidecancor,width)计算a,b,在窗宽 width 下 典则相关剥离背景后的动态相关系数。 + * + * 算法很多代码没有注释,这些代码翻译的友谊的C代码,不清楚实际逻辑 + */ +public class ResponsibilityAlgorithm { + + static int P = 21; + static int TL = 671; + static int LL = 3355; + static int JIANGE = 5; + static int wdith = 96; + static float XIANE = 0; + static int RES_NUM = 0; + + static QvvrDataEntity qvvrResult; + + + public QvvrDataEntity getResponsibilityResult(QvvrDataEntity qvvrParam) { + int i, j; + if (qvvrParam != null) { + // 计算责任 + harm_res(qvvrParam.calFlag, qvvrParam); + // 输出结果 + qvvrParam.calOk = qvvrResult.calOk; + if (qvvrParam.calOk == 1) { + // 长时越限谐波责任 + for (i = 0; i < qvvrParam.pNode; i++) + qvvrParam.sumFKdata[i] = qvvrResult.sumFKdata[i]; + for (i = 0; i < (qvvrParam.pNode + 1); i++) + qvvrParam.sumHKdata[i] = qvvrResult.sumHKdata[i]; + // 如果是原始功率数据和谐波数据代入计算,将动态相关系数和动态谐波责任输出 + if (qvvrParam.calFlag == 0) { + qvvrParam.resNum = qvvrResult.resNum; + for (i = 0; i < qvvrResult.resNum; i++) { + qvvrParam.core[i] = qvvrResult.core[i]; + qvvrParam.bjCore[i] = qvvrResult.bjCore[i]; + for (j = 0; j < qvvrParam.pNode; j++) { + qvvrParam.fKData[i][j] = qvvrResult.fKData[i][j]; + qvvrParam.simData[i][j] = qvvrResult.simData[i][j]; + } + for (j = 0; j < (qvvrParam.pNode + 1); j++) + qvvrParam.hKData[i][j] = qvvrResult.hKData[i][j]; + } + } + } + } + return qvvrParam; + } + + + public static int harm_res(int calFlag, QvvrDataEntity qvvrParam) { + if (calFlag == 0) { + harm_res_all(qvvrParam); + } else if (calFlag == 1) { + harm_res_part(qvvrParam); + } + return 0; + } + + static int harm_res_part(QvvrDataEntity qvvrParam) { + int ret = 0; + //缓冲大小初始化 + ret = data_init_part(qvvrParam); + if (ret != 0) { + qvvrResult.calOk = 0; +// System.out.printf("data init err,exit\r\n"); + throw new BusinessException(AdvanceResponseEnum.DATA_ERROR); + } + int colK = P + 1; + RealMatrix HKdata = MatrixUtils.createRealMatrix(RES_NUM, colK); + for (int i = 0; i < P + 1; i++) { + for (int j = 0; j < RES_NUM; j++) { + HKdata.setEntry(j, i, qvvrResult.hKData[j][i]); + } + } + + RealVector Udata = new ArrayRealVector(TL); + for (int j = 0; j < TL; j++) { + Udata.setEntry(j, qvvrResult.harmData[j]); + } + + float[] arrHKsum = SumHK(HKdata, Udata, wdith, colK, TL); + RealVector sumHKdata = new ArrayRealVector(colK); + for (int i = 0; i < colK; i++) { + sumHKdata.setEntry(i, 0); + } + + float sum_hk = 0; + for (int i = 0; i < colK; i++) { + sumHKdata.setEntry(i, arrHKsum[i]); + sum_hk += sumHKdata.getEntry(i); + qvvrResult.sumHKdata[i] = (float) sumHKdata.getEntry(i); + } + + RealMatrix FKdata = MatrixUtils.createRealMatrix(RES_NUM, P); + for (int i = 0; i < P; i++) { + for (int j = 0; j < RES_NUM; j++) { + FKdata.setEntry(j, i, qvvrResult.fKData[j][i]); + } + } + + colK = P; + arrHKsum = SumHK(FKdata, Udata, wdith, colK, TL); + RealVector sumFKdata = new ArrayRealVector(colK); + for (int i = 0; i < colK; i++) { + sumFKdata.setEntry(i, 0); + } + + float sum_fk = 0; + for (int i = 0; i < colK; i++) { + sumFKdata.setEntry(i, arrHKsum[i]); + sum_fk += sumFKdata.getEntry(i); + qvvrResult.sumFKdata[i] = (float) sumFKdata.getEntry(i); + } + + qvvrResult.calOk = 1; + return 0; + } + + static int data_init_part(QvvrDataEntity qvvrParam) { + qvvrResult = new QvvrDataEntity(); + //输入数据处理 + BeanUtil.copyProperties(qvvrParam, qvvrResult); +// if ((qvvrResult.resNum + qvvrResult.win) != qvvrResult.harmNum) { +// System.out.printf("数据未对齐...\r\n"); +// return -1; +// } + RES_NUM = qvvrResult.resNum; + P = qvvrResult.pNode; + TL = qvvrResult.win + qvvrResult.resNum; + wdith = qvvrResult.win; + XIANE = qvvrResult.harmMk; + + if ((wdith < QvvrDataEntity.MIN_WIN_LEN) || (wdith > QvvrDataEntity.MAX_WIN_LEN)) { + System.out.printf("窗宽超限...\r\n"); + throw new BusinessException(AdvanceResponseEnum.WIN_DATA_ERROR); + } + + if ((P > QvvrDataEntity.MAX_P_NODE) || (TL > QvvrDataEntity.MAX_P_NUM)) { + System.out.printf("数据长度超限...\r\n"); + throw new BusinessException(AdvanceResponseEnum.DATA_ERROR); + } + for (int i = 0; i < RES_NUM; i++) { + for (int j = 0; j < P; j++) { + qvvrResult.fKData[i][j] = qvvrParam.fKData[i][j]; + } + for (int j = 0; j < P + 1; j++) { + qvvrResult.hKData[i][j] = qvvrParam.hKData[i][j]; + } + } + + // 复制 qvvrParam + for (int i = 0; i < TL; i++) { + qvvrResult.harmData[i] = qvvrParam.harmData[i]; + } + return 0; + } + + static int harm_res_all(QvvrDataEntity qvvrParam) { + int ret = 0; + //缓冲大小初始化 + ret = data_init_all(qvvrParam); + if (ret != 0) { + qvvrResult.calOk = 0; + System.out.printf("data init err,exit\r\n"); + throw new BusinessException(AdvanceResponseEnum.INIT_DATA_ERROR); + } + //测试数据申请空间 + float[][] a = new float[TL][]; + for (int i = 0; i < TL; i++) { + a[i] = new float[P]; + } + + float[] b = new float[LL]; + float[] u = new float[TL]; + + for (int i = 0; i < TL; i++) { + for (int j = 0; j < P; j++) { + a[i][j] = qvvrResult.pData[i][j]; + } + } + for (int i = 0; i < LL; i++) { + b[i] = qvvrResult.harmData[i]; + } + + for (int i = 0; i < LL; i += JIANGE) { + float tempt = 0.0f; + for (int j = 0; j < JIANGE; j++) { + tempt += b[i + j]; + } + b[i] = tempt / JIANGE; + } + int width = wdith; + int slcorlength = TL - width; + + //剥离背景谐波后的动态相关系数计算 + //数据格式转换 + // 创建矩阵Pdata并复制数据 + double[][] PdataArray = new double[TL][P]; + for (int i = 0; i < TL; i++) { + for (int j = 0; j < P; j++) { + PdataArray[i][j] = a[i][j]; + } + } + RealMatrix Pdata = new Array2DRowRealMatrix(PdataArray); + // 创建向量Udata并复制数据 + double[] UdataArray = new double[TL]; + for (int i = 0; i < TL; i++) { + UdataArray[i] = b[i * JIANGE]; + } + RealVector Udata = new ArrayRealVector(UdataArray); + for (int i = 0; i < TL; i++) { + u[i] = (float) UdataArray[i]; + } + + //动态典则相关系数数组获得 并转化为向量 + + float[] cancorrelation = SlideCanCor(a, u, width, P, TL); + + RealVector Core = new ArrayRealVector(slcorlength); + RealVector bjCore = new ArrayRealVector(slcorlength); + for (int i = 0; i < slcorlength; i++) { + Core.setEntry(i, cancorrelation[i]); + qvvrResult.core[i] = (float) Core.getEntry(i); + } + for (int i = 0; i < slcorlength; i++) { + bjCore.setEntry(i, 1 - cancorrelation[i]); + qvvrResult.bjCore[i] = (float) bjCore.getEntry(i); + } + + float[] y = new float[TL]; + float[] xe = new float[TL]; + float[] slidecor; + RealVector temptPe = new ArrayRealVector(TL); + RealVector temptU = new ArrayRealVector(TL); + RealMatrix simCor = new Array2DRowRealMatrix(slcorlength, P); + + // 格式转换 + temptU = Udata.getSubVector(0, TL); + for (int m = 0; m < TL; m++) { + y[m] = (float) temptU.getEntry(m); + } + // 格式转换、计算系数、格式转换 + for (int i = 0; i < P; i++) { + temptPe = Pdata.getColumnVector(i); + for (int m = 0; m < TL; m++) { + xe[m] = (float) temptPe.getEntry(m); + } + slidecor = slideCor(xe, y, cancorrelation, width, TL); // 计算每个用户负荷与用户谐波的动态相关系数 + + for (int j = 0; j < slcorlength; j++) { + simCor.setEntry(j, i, slidecor[j]); + qvvrResult.simData[j][i] = (float) simCor.getEntry(j, i); + } + } + + //动态谐波责任指标计算 + //EK计算,用于后续计算FK(不含背景的用户责任指标)、HK(包含背景的用户责任指标) + //float **EKarr = (float **)malloc(TL * sizeof(float *));//先申请P个指针型字节的空间 + //for (int i = 0; i < TL; i++) + //EKarr[i] = (float *)malloc(TL * Float.SIZE / Byte.SIZE); + + float[][] EKarr; + EKarr = dyEKCom(simCor, Pdata, width, P, TL); + RealMatrix EKdata = MatrixUtils.createRealMatrix(slcorlength, P); + for (int i = 0; i < slcorlength; i++) { + for (int j = 0; j < P; j++) { + EKdata.setEntry(i, j, EKarr[i][j]); + } + } + + //不含背景的用户谐波责任指标 + //float **FKarr = (float **)malloc(TL * sizeof(float *));//先申请P个指针型字节的空间 + //for (int i = 0; i < TL; i++) + //FKarr[i] = (float *)malloc(TL * Float.SIZE / Byte.SIZE); + float[][] FKarr; + FKarr = DyFKCom(EKdata, width, P, TL); + RealMatrix FKdata = MatrixUtils.createRealMatrix(slcorlength, P); + for (int i = 0; i < slcorlength; i++) { + for (int j = 0; j < P; j++) { + FKdata.setEntry(i, j, FKarr[i][j]); + } + } + qvvrResult.fKData=FKarr; + //包含背景的谐波责任指标 + //float **HKarr = (float **)malloc(TL * sizeof(float *));//先申请P个指针型字节的空间 + //for (int i = 0; i < TL; i++) + //HKarr[i] = (float *)malloc(TL * Float.SIZE / Byte.SIZE); + float[][] HKarr; + HKarr = DyHKCom(bjCore, EKdata, width, P, TL); + RealMatrix HKdata = MatrixUtils.createRealMatrix(slcorlength, (P + 1)); + for (int i = 0; i < slcorlength; i++) { + for (int j = 0; j < (P + 1); j++) { + HKdata.setEntry(i, j, HKarr[i][j]); + qvvrResult.hKData[i][j] = (float) HKdata.getEntry(i, j); + } + } + qvvrResult.resNum = slcorlength; + + + //超限额长时谐波责任指标计算 + float[] arrHKsum = new float[TL]; + int colK = P + 1;//FKdata时为P + arrHKsum = SumHK(HKdata, Udata, width, colK, TL);//可更改FKdata,表示为不包含背景时的长时责任划分 + RealVector sumHKdata = new ArrayRealVector(colK); + for (int i = 0; i < colK; i++) { + sumHKdata.setEntry(i, 0); + } + + float sum_hk = 0; + for (int i = 0; i < colK; i++) { + sumHKdata.setEntry(i, arrHKsum[i]); + sum_hk += sumHKdata.getEntry(i); + qvvrResult.sumHKdata[i] = (float) sumHKdata.getEntry(i); + } + + colK = P;//FKdata时为P + arrHKsum = SumHK(FKdata, Udata, width, colK, TL);//可更改FKdata,表示为不包含背景时的长时责任划分 + RealVector sumFKdata = new ArrayRealVector(colK); + + for (int i = 0; i < colK; i++) { + sumFKdata.setEntry(i, 0); + } + float sum_fk = 0; + for (int i = 0; i < colK; i++) { + sumFKdata.setEntry(i, arrHKsum[i]); + sum_fk += sumFKdata.getEntry(i); + qvvrResult.sumFKdata[i] = (float) sumHKdata.getEntry(i); + } + //结果输出 + qvvrResult.calOk = 1; + return 0; + } + + + + static int data_init_all(QvvrDataEntity qvvrParam) { + qvvrResult = new QvvrDataEntity(); + //输入数据处理 + BeanUtil.copyProperties(qvvrParam, qvvrResult); + P = qvvrResult.pNode; + TL = qvvrResult.pNum; + LL = qvvrResult.harmNum; + JIANGE = qvvrResult.harmNum / qvvrResult.pNum; + wdith = qvvrResult.win; + XIANE = qvvrResult.harmMk; + + if ((JIANGE * TL != LL) || (JIANGE < 1)) { + return -1; + } + if ((wdith < QvvrDataEntity.MIN_WIN_LEN) || (wdith > QvvrDataEntity.MAX_WIN_LEN)) { +// System.out.printf("窗宽超限...\r\n"); + throw new BusinessException(AdvanceResponseEnum.EVENT_DATA_MISS); + } + + if (TL < (2 * wdith)) { + System.out.printf("窗宽和数据长度不匹配...\r\n"); + return -1; + } + + if ((P > QvvrDataEntity.MAX_P_NODE) || (TL > QvvrDataEntity.MAX_P_NUM) || (LL > QvvrDataEntity.MAX_HARM_NUM)) { + System.out.printf("数据长度超限...\r\n"); + throw new BusinessException(AdvanceResponseEnum.EVENT_DATA_MISS); + } + + + float[][] clone = new float[qvvrParam.getPData().length][]; + for (int i = 0; i < qvvrParam.getPData().length; i++) { + clone[i] = Arrays.copyOf(qvvrParam.getPData()[i], qvvrParam.getPData()[i].length); + } + qvvrResult.setPData(clone); + + + for (int i = 0; i < LL; i++) { + qvvrResult.getHarmData()[i] = qvvrParam.getHarmData()[i]; + } + //System.out.printf("win = %d\r\n",wdith); + return 0; + } + + + public static float[] SlideCanCor(float[][] x, float[] y, int width, int p_num, int tl_num) { + int slcorlength = tl_num - width; + float[][] a = new float[width][p_num]; + for (int i = 0; i < width; i++) { + a[i] = new float[p_num]; + } + float[] b = new float[width]; + + float[][] sxxmatrix = new float[p_num][p_num]; + for (int i = 0; i < p_num; i++) { + sxxmatrix[i] = new float[p_num]; + } + float[] sxymatrix = new float[tl_num]; + float[] x1 = new float[tl_num]; + float[] x2 = new float[tl_num]; + float[] x3 = new float[tl_num]; + float[][] xx = new float[width][p_num]; + for (int i = 0; i < width; i++) { + xx[i] = new float[p_num]; + } + float[] yy = new float[width]; + + RealMatrix Pdata = new Array2DRowRealMatrix(tl_num, p_num); + RealVector Udata = new ArrayRealVector(tl_num); + for (int i = 0; i < tl_num; i++) { + for (int j = 0; j < p_num; j++) { + Pdata.setEntry(i, j, x[i][j]); + } + Udata.setEntry(i, y[i]); + } + + RealMatrix temptP = new Array2DRowRealMatrix(width, p_num); + RealVector temptU = new ArrayRealVector(width); + float[] slideCancor = new float[tl_num]; + for (int i = 0; i < slcorlength; i++) { + temptU = Udata.getSubVector(i, width); + temptP = Pdata.getSubMatrix(i, i + width - 1, 0, p_num - 1); + slideCancor[i] = TransCancor(temptP, temptU, width, p_num, tl_num, sxxmatrix, sxymatrix, x1, x2, x3, xx, yy); + } + + return slideCancor; + } + + + public static float TransCancor(RealMatrix a, RealVector b, int width, int p_num, int tl_num, + float[][] sxxMatrix, float[] sxyMatrix, float[] x1, float[] x2, + float[] x3, float[][] x, float[] y) { + float syymatrix; + for (int i = 0; i < width; i++) { + for (int j = 0; j < p_num; j++) { + x[i][j] = (float) a.getEntry(i, j); + } + y[i] = (float) b.getEntry(i); + } + // 假设的SXX函数,需要根据实际实现来调整 + sxxMatrix = SXX(x, width, p_num, tl_num, sxxMatrix, x1, x2); + syymatrix = cov(y, y, width); // 假设的COV函数,需要根据实际实现来调整 + if (syymatrix == 0) { + syymatrix = 0.00001F; + } + // 假设的SXY函数,需要根据实际实现来调整 + sxyMatrix = SXY(x, y, width, p_num, tl_num, sxyMatrix, x3); + +// +// RealMatrix A = MatrixUtils.createRealMatrix(p_num, p_num); +// RealMatrix invSXX = MatrixUtils.createRealMatrix(p_num, p_num); +// RealMatrix I = MatrixUtils.createRealIdentityMatrix(p_num); // I is an identity matrix +// +// // 二维数组转为矩阵 +// for (int i = 0; i < p_num; i++) { +// for (int j = 0; j < p_num; j++) { +// A.setEntry(i, j, sxxMatrix[i][j]); +// } +// } +// +// // 使用 LU 分解方法计算矩阵 invSXX +// invSXX = new LUDecomposition(A).getSolver().solve(I); + + // 创建 p_num × p_num 的矩阵 A + SimpleMatrix A = new SimpleMatrix(p_num, p_num); + + // 创建 p_num × p_num 的逆矩阵 invSXX + SimpleMatrix invSXX = new SimpleMatrix(p_num, p_num); + + // 创建 p_num × p_num 的单位矩阵 I + SimpleMatrix I = SimpleMatrix.identity(p_num); + + // 二维数组转为矩阵 A + for (int i = 0; i < p_num; i++) { + for (int j = 0; j < p_num; j++) { + A.set(i, j, sxxMatrix[i][j]); + } + } + + // 使用 LU 分解方法计算矩阵 invSXX + //invSXX = A.invert().mult(I); + + // 创建长度为 p_num 的向量 sXYMa_Eigen + DMatrixRMaj sXYMa_Eigen = new DMatrixRMaj(p_num, 1); + for (int i = 0; i < p_num; i++) { + sXYMa_Eigen.set(i, 0, sxyMatrix[i]); + } + + // 计算 Umatrix + DMatrixRMaj Umatrix = new DMatrixRMaj(p_num, p_num); + CommonOps_DDRM.multOuter(sXYMa_Eigen, Umatrix); // 外积 + CommonOps_DDRM.divide(Umatrix, syymatrix); // 矩阵按标量除法 + CommonOps_DDRM.divide(Umatrix, syymatrix); // 再次按标量除法 + + // 计算特征值 + EigenDecomposition_F64 eigenDecomposition = DecompositionFactory_DDRM.eig(p_num, false); + eigenDecomposition.decompose(Umatrix); + + float corrMax = 0; + for (int i = 0; i < p_num; i++) { + float absCorr = (float) Math.abs(eigenDecomposition.getEigenvalue(i).getReal()); + if (absCorr > corrMax) { + corrMax = absCorr; + } + } + + float cancor = (float) Math.sqrt(corrMax); + if (cancor >= 1) { + cancor = 1; + } + + return cancor; + } + + + public static float[][] SXX(float[][] x, int width, int p_num, int tl_num, float[][] sxxmatrix, float[] x1, float[] x2) { + int i, j, m; + for (i = 0; i < p_num; i++) { + for (j = 0; j < p_num; j++) { + for (m = 0; m < width; m++) { + x1[m] = x[m][i]; + x2[m] = x[m][j]; + } + sxxmatrix[i][j] = cov(x1, x2, width); + } + } + return sxxmatrix; + } + + public static float[] SXY(float[][] x, float[] y, int width, int p_num, int tl_num, float[] sxymatrix, float[] x1) { + int i, m; + for (i = 0; i < p_num; i++) { + for (m = 0; m < width; m++) { + x1[m] = x[m][i]; + } + sxymatrix[i] = cov(x1, y, width); + } + return sxymatrix; + } + + public static float cov(float[] x, float[] y, int width) { + float d1, d2, d3, d4; + float mx, my; + float cov; + int i; + int xlength = width; + int ylength = width; + d1 = d2 = d3 = d4 = mx = my = 0.0f; + for (i = 0; i < xlength; i++) { + mx += x[i]; + my += y[i]; + } + mx = mx / xlength; + my = my / ylength; + for (i = 0; i < xlength; i++) { + d1 += (x[i] - mx) * (y[i] - my); + } + cov = d1 / (xlength - 1); + return cov; + } + + public static float[] slideCor(float[] x, float[] y, float[] slidecor, int width, int tlNum) { + int slcorLength = tlNum - width; + float[] slcor = new float[tlNum]; // 注意调整数组大小根据实际需要 + + // 动态相关系数 + for (int i = 0; i < slcorLength; i++) { + float[] temptp = new float[width]; + float[] temptq = new float[width]; + for (int j = 0; j < width; j++) { + temptp[j] = x[i + j]; + temptq[j] = y[i + j] * slidecor[i]; + } + slcor[i] = pearCor(temptq, temptp, width); + } + + return slcor; + } + + public static float pearCor(float[] x, float[] y, int count) { + float d1 = 0, d2 = 0, d3 = 0, d4 = 0; + float mx = 0, my = 0; + float result = 0; + + // 计算x和y的平均值 + for (int i = 0; i < count; i++) { + mx += x[i]; + my += y[i]; + } + mx /= count; + my /= count; + + // 计算相关系数的数据组成部分 + for (int i = 0; i < count; i++) { + d1 += (x[i] - mx) * (y[i] - my); + d2 += (x[i] - mx) * (x[i] - mx); + d3 += (y[i] - my) * (y[i] - my); + } + d4 = (float) Math.sqrt(d2 * d3); + + if (d4 == 0) { + // 除数为0时,相关系数为0 + result = 0; + } else { + result = d1 / d4; + } + + return result; + } + + private static float[][] dyEKCom(RealMatrix Dydata, RealMatrix Pdata, int width, int p_num, int tl_num) { + int slg = tl_num - width; + RealMatrix AKdata = MatrixUtils.createRealMatrix(slg, p_num); + RealMatrix SumP = MatrixUtils.createRealMatrix(slg, 1); + RealMatrix EKdata = MatrixUtils.createRealMatrix(slg, p_num); + + for (int i = 0; i < slg; i++) { + SumP.setEntry(i, 0, 0); + for (int j = 0; j < p_num; j++) { + float sumPValue = (float) (SumP.getEntry(i, 0) + Pdata.getEntry(i, j)); + SumP.setEntry(i, 0, sumPValue); + } + for (int j = 0; j < p_num; j++) { + float AKdataValue = (float) (Dydata.getEntry(i, j) * (Pdata.getEntry(i, j) / SumP.getEntry(i, 0))); + AKdata.setEntry(i, j, AKdataValue); + } + } + for (int i = 0; i < slg; i++) { + float maxdata = Float.MIN_VALUE; + float mindata = Float.MAX_VALUE; + for (int j = 0; j < p_num; j++) { + maxdata = Math.max(maxdata, (float) AKdata.getEntry(i, j)); + mindata = Math.min(mindata, (float) AKdata.getEntry(i, j)); + } + for (int j = 0; j < p_num; j++) { + EKdata.setEntry(i, j, (AKdata.getEntry(i, j) - mindata) / (maxdata - mindata)); + } + } + + float[][] arrEK = new float[slg][p_num]; + for (int i = 0; i < slg; i++) { + for (int j = 0; j < p_num; j++) { + arrEK[i][j] = (float) EKdata.getEntry(i, j); + } + } + return arrEK; + } + + private static float[][] DyFKCom(RealMatrix EKdata, int width, int p_num, int tl_num) { + int slg = tl_num - width; + RealMatrix FKdata = MatrixUtils.createRealMatrix(slg, p_num); + ArrayRealVector SumEK = new ArrayRealVector(slg); + + for (int i = 0; i < slg; i++) { + SumEK.setEntry(i, 0); + for (int j = 0; j < p_num; j++) { + float sumEKValue = (float) (SumEK.getEntry(i) + EKdata.getEntry(i, j)); + SumEK.setEntry(i, sumEKValue); + } + for (int j = 0; j < p_num; j++) { + float FKdataValue = (float) (EKdata.getEntry(i, j) / SumEK.getEntry(i)); + FKdata.setEntry(i, j, FKdataValue); + } + } + + float[][] arrFK = new float[tl_num][p_num]; + for (int i = 0; i < slg; i++) { + for (int j = 0; j < p_num; j++) { + arrFK[i][j] = (float) FKdata.getEntry(i, j); + } + } + return arrFK; + } + + private static float[][] DyHKCom(RealVector cancordata, RealMatrix EKdata, int width, int p_num, int tl_num) { + int slg = tl_num - width; + RealMatrix HKdata = MatrixUtils.createRealMatrix(slg, p_num + 1); + RealMatrix newEK = MatrixUtils.createRealMatrix(slg, p_num + 1); + ArrayRealVector SumEK = new ArrayRealVector(slg); + + for (int i = 0; i < slg; i++) { + for (int j = 0; j < p_num; j++) { + newEK.setEntry(i, j, EKdata.getEntry(i, j)); + } + newEK.setEntry(i, p_num, cancordata.getEntry(i)); + } + + for (int i = 0; i < slg; i++) { + SumEK.setEntry(i, 0); + for (int j = 0; j < p_num + 1; j++) { + float sumEKValue = (float) (SumEK.getEntry(i) + newEK.getEntry(i, j)); + SumEK.setEntry(i, sumEKValue); + } + for (int j = 0; j < p_num + 1; j++) { + float HKdataValue = (float) (newEK.getEntry(i, j) / SumEK.getEntry(i)); + HKdata.setEntry(i, j, HKdataValue); + } + } + + float[][] arrHK = new float[tl_num][p_num + 1]; + for (int i = 0; i < slg; i++) { + for (int j = 0; j < p_num + 1; j++) { + arrHK[i][j] = (float) HKdata.getEntry(i, j); + } + } + return arrHK; + } + + private static float[] SumHK(RealMatrix HKdata, RealVector Udata, int width, int colK, int tl_num) { + int P1 = colK; + RealVector HKSum = new ArrayRealVector(P1); + int slg = tl_num - width; + int coutt = 0; + + for (int j = 0; j < P1; j++) { + HKSum.setEntry(j, 0); + coutt = 0; + for (int i = 0; i < slg; i++) { + if (Udata.getEntry(i) > XIANE) { + float HKdataEntry = (float) HKdata.getEntry(i, j); + HKSum.setEntry(j, HKSum.getEntry(j) + HKdataEntry); + coutt++; + } + } + } + + float[] arrHKsum = new float[P1]; + for (int i = 0; i < P1; i++) { + arrHKsum[i] = 0; + } + for (int i = 0; i < P1; i++) { + if (coutt > 0) { + arrHKsum[i] = (float) (100 * (HKSum.getEntry(i) / coutt)); + } + } + return arrHKsum; + } + +} diff --git a/cn-advance/src/test/java/com/njcn/product/advance/CnAdvanceApplicationTests.java b/cn-advance/src/test/java/com/njcn/product/advance/CnAdvanceApplicationTests.java new file mode 100644 index 0000000..39f8920 --- /dev/null +++ b/cn-advance/src/test/java/com/njcn/product/advance/CnAdvanceApplicationTests.java @@ -0,0 +1,13 @@ +package com.njcn.product.advance; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class CnAdvanceApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/cn-begin/.gitignore b/cn-begin/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/cn-begin/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/cn-begin/pom.xml b/cn-begin/pom.xml new file mode 100644 index 0000000..fadcccb --- /dev/null +++ b/cn-begin/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + + com.njcn.product + CN_Product + 1.0.0 + + + cn-begin + 0.0.1-SNAPSHOT + cn-begin + cn-begin + + + + + com.njcn + njcn-common + 0.0.1 + + + + com.njcn + mybatis-plus + 0.0.1 + + + + com.njcn + spingboot2.3.12 + 2.3.12 + + + + com.njcn.product + cn-diagram + 1.0.0 + + + + + + + + + cn-begin + + + org.springframework.boot + spring-boot-maven-plugin + + + package + + repackage + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + + src/main/resources + + **/* + + + + + + diff --git a/cn-begin/src/main/java/com/njcn/product/begin/CnBeginApplication.java b/cn-begin/src/main/java/com/njcn/product/begin/CnBeginApplication.java new file mode 100644 index 0000000..a67b188 --- /dev/null +++ b/cn-begin/src/main/java/com/njcn/product/begin/CnBeginApplication.java @@ -0,0 +1,20 @@ +package com.njcn.product.begin; + +import lombok.extern.slf4j.Slf4j; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.DependsOn; + + +@Slf4j +@MapperScan("com.njcn.**.mapper") +@SpringBootApplication(scanBasePackages = "com.njcn") +@DependsOn("proxyMapperRegister") +public class CnBeginApplication { + + public static void main(String[] args) { + SpringApplication.run(CnBeginApplication.class, args); + } + +} diff --git a/cn-begin/src/main/resources/application-wuxi_dev.yml b/cn-begin/src/main/resources/application-wuxi_dev.yml new file mode 100644 index 0000000..53d7c92 --- /dev/null +++ b/cn-begin/src/main/resources/application-wuxi_dev.yml @@ -0,0 +1,99 @@ +#当前服务的基本信息 +spring: + datasource: + dynamic: + primary: master + strict: false # 是否严格匹配数据源,默认false + druid: # 如果使用Druid连接池 + validation-query: SELECT 1 FROM DUAL # 达梦专用校验SQL + initial-size: 10 + # 初始化大小,最小,最大 + min-idle: 20 + maxActive: 500 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + testWhileIdle: true + testOnBorrow: true + testOnReturn: false + # 打开PSCache,并且指定每个连接上PSCache的大小 + poolPreparedStatements: true + maxPoolPreparedStatementPerConnectionSize: 20 + datasource: + master: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://192.168.1.24:13306/pqsinfo_wuxi?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai + username: root + password: njcnpqs + salve: + url: jdbc:oracle:thin:@192.168.1.51:1521:pqsbase + username: pqsadmin_bj + password: pqsadmin + driver-class-name: oracle.jdbc.OracleDriver + + + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + #influxDB内容配置 + influx: + url: http://192.168.1.103:18086 + user: admin + password: 123456 + database: pqsbase_wx + mapper-location: com.njcn.**.imapper +#mybatis配置信息 +mybatis-plus: + mapper-locations: classpath*:com/njcn/**/mapping/*.xml + #别名扫描 + type-aliases-package: com.njcn.product.**.pojo + configuration: + #驼峰命名 + map-underscore-to-camel-case: true + #配置sql日志输出 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # #关闭日志输出 + # log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl + global-config: + db-config: + #指定主键生成策略 + id-type: assign_uuid +db: + type: mysql +#文件位置配置 +business: + #处理波形数据位置 + wavePath: D://comtrade + #wavePath: /usr/local/comtrade + #处理临时数据 + #tempPath: D://file + tempPath: /usr/local/file + #文件存储的方式 1.本地 + file: + storage: 1 + #localStoragePath: /usr/local/localStoragePath + localStoragePath: f://localStoragePath +#oss服务器配置 +min: + io: + endpoint: http://192.168.1.22:9009 + accessKey: minio + secretKey: minio@123 + bucket: excelreport + #华为obs服务器配置 +huawei: + access-key: J9GS9EA79PZ60OK23LWP + security-key: BirGrAFDSLxU8ow5fffyXgZRAmMRb1R1AdqCI60d + obs: + bucket: test-8601 + endpoint: https://obs.cn-east-3.myhuaweicloud.com + # 单位为秒 + expire: 3600 + + + + diff --git a/cn-begin/src/main/resources/application-wuxi_prod.yml b/cn-begin/src/main/resources/application-wuxi_prod.yml new file mode 100644 index 0000000..dd52cdf --- /dev/null +++ b/cn-begin/src/main/resources/application-wuxi_prod.yml @@ -0,0 +1,73 @@ +spring: + datasource: + dynamic: + primary: master + strict: false # 是否严格匹配数据源,默认false + druid: # 如果使用Druid连接池 + validation-query: SELECT 1 FROM DUAL # 达梦专用校验SQL + initial-size: 10 + # 初始化大小,最小,最大 + min-idle: 20 + maxActive: 500 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + testWhileIdle: true + testOnBorrow: true + testOnReturn: false + # 打开PSCache,并且指定每个连接上PSCache的大小 + poolPreparedStatements: true + maxPoolPreparedStatementPerConnectionSize: 20 + datasource: + master: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://192.168.1.103:13306/pqsinfo_wuxi?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai + username: root + password: njcnpqs + + + #influxDB内容配置 + influx: + url: http://192.168.1.103:18086 + user: admin + password: 123456 + database: pqsbase_wx + mapper-location: com.njcn.**.imapper + +db: + type: mysql +#文件位置配置 +business: + #处理波形数据位置 + #wavePath: D://comtrade + wavePath: /usr/local/comtrade + #处理临时数据 + #tempPath: D://file + tempPath: /usr/local/file + #文件存储的方式 1.本地 + file: + storage: 1 + localStoragePath: /usr/local/localStoragePath + #localStoragePath: f://localStoragePath +#oss服务器配置 +min: + io: + endpoint: http://192.168.1.22:9009 + accessKey: minio + secretKey: minio@123 + bucket: excelreport + #华为obs服务器配置 +huawei: + access-key: J9GS9EA79PZ60OK23LWP + security-key: BirGrAFDSLxU8ow5fffyXgZRAmMRb1R1AdqCI60d + obs: + bucket: test-8601 + endpoint: https://obs.cn-east-3.myhuaweicloud.com + # 单位为秒 + expire: 3600 + + + diff --git a/cn-begin/src/main/resources/application.yml b/cn-begin/src/main/resources/application.yml new file mode 100644 index 0000000..1a6d172 --- /dev/null +++ b/cn-begin/src/main/resources/application.yml @@ -0,0 +1,84 @@ +#当前服务的基本信息 +microservice: + ename: cn-begin + name: cn-begin +server: + port: 19001 +spring: + application: + name: cn-begin + + profiles: + active: wuxi_dev + + + jackson: + time-zone: GMT+8 + date-format: yyyy-MM-dd HH:mm:ss + locale: zh_CN + serialization: + # 格式化输出 + indent_output: false + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + +#mybatis配置信息 +mybatis-plus: + mapper-locations: classpath*:com/njcn/**/mapping/*.xml + #别名扫描 + type-aliases-package: com.njcn.product.**.pojo + configuration: + #驼峰命名 + map-underscore-to-camel-case: true + #配置sql日志输出 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # #关闭日志输出 + # log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl + global-config: + db-config: + #指定主键生成策略 + id-type: assign_uuid +db: + type: mysql +#文件位置配置 +business: + #处理波形数据位置 + wavePath: D://comtrade + #wavePath: /usr/local/comtrade + #处理临时数据 + #tempPath: D://file + tempPath: /usr/local/file + #文件存储的方式 1.本地 + file: + storage: 1 + #localStoragePath: /usr/local/localStoragePath + localStoragePath: f://localStoragePath +#oss服务器配置 +min: + io: + endpoint: http://192.168.1.22:9009 + accessKey: minio + secretKey: minio@123 + bucket: excelreport + #华为obs服务器配置 +huawei: + access-key: J9GS9EA79PZ60OK23LWP + security-key: BirGrAFDSLxU8ow5fffyXgZRAmMRb1R1AdqCI60d + obs: + bucket: test-8601 + endpoint: https://obs.cn-east-3.myhuaweicloud.com + # 单位为秒 + expire: 3600 + +#线程池配置信息 +threadPool: + corePoolSize: 10 + maxPoolSize: 20 + queueCapacity: 500 + keepAliveSeconds: 60 +file: + upload-dir: D:/carry + + diff --git a/cn-begin/src/main/resources/logback.xml b/cn-begin/src/main/resources/logback.xml new file mode 100644 index 0000000..7864c7b --- /dev/null +++ b/cn-begin/src/main/resources/logback.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger{36} - %msg%n + UTF-8 + + + + + + + + ${logHomeDir}/${log.projectName}/debug/debug.log + + + + + DEBUG + + ACCEPT + + DENY + + + + + + ${logHomeDir}/${log.projectName}/debug/debug.log.%d{yyyy-MM-dd}.%i.log + + 10MB + + ${log.maxHistory:-30} + + + + + + + + + + ${log.pattern} + + UTF-8 + + + + + + + INFO + ACCEPT + DENY + + + ${logHomeDir}/${log.projectName}/info/info.log + + + + ${logHomeDir}/${log.projectName}/info/info.log.%d{yyyy-MM-dd}.%i.log + + 10MB + ${log.maxHistory:-30} + + + + ${log.pattern} + + UTF-8 + + + + + + + + ${logHomeDir}/${log.projectName}/error/error.log + + + ERROR + ACCEPT + DENY + + + + ${logHomeDir}/${log.projectName}/error/error.log.%d{yyyy-MM-dd}.%i.log + + 10MB + ${log.maxHistory:-30} + + + + ${log.pattern} + + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cn-begin/src/test/java/com/njcn/product/begin/CnBeginApplicationTests.java b/cn-begin/src/test/java/com/njcn/product/begin/CnBeginApplicationTests.java new file mode 100644 index 0000000..768d156 --- /dev/null +++ b/cn-begin/src/test/java/com/njcn/product/begin/CnBeginApplicationTests.java @@ -0,0 +1,13 @@ +package com.njcn.product.begin; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class CnBeginApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/cn-diagram/.gitignore b/cn-diagram/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/cn-diagram/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/cn-diagram/pom.xml b/cn-diagram/pom.xml new file mode 100644 index 0000000..e8983f8 --- /dev/null +++ b/cn-diagram/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + com.njcn.product + CN_Product + 1.0.0 + + cn-diagram + 1.0.0 + cn-diagram + cn-diagram + + + + + + + com.njcn + njcn-common + 0.0.1 + + + + com.njcn + mybatis-plus + 0.0.1 + + + + com.njcn + spingboot2.3.12 + 2.3.12 + + + + com.njcn.product + cn-user + 1.0.0 + + + + + com.njcn.product + cn-auth + 1.0.0 + + + + com.njcn.product + cn-system + 1.0.0 + + + + com.njcn.product + cn-zutai + 1.0.0 + + + + com.njcn.product + cn-terminal + 1.0.0 + + + + org.springframework.boot + spring-boot-starter-websocket + ${websocket.version} + + + + com.njcn + common-oss + 1.0.0 + + + com.njcn + common-web + + + + + + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 3.5.1 + + + + + com.njcn.product + cn-advance + 1.0.0 + + + + + + diff --git a/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/controller/LedgerScaleController.java b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/controller/LedgerScaleController.java new file mode 100644 index 0000000..9058003 --- /dev/null +++ b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/controller/LedgerScaleController.java @@ -0,0 +1,171 @@ +package com.njcn.product.diagram.LedgerScale.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.advance.responsility.pojo.dto.CustomerResponsibility; +import com.njcn.product.diagram.LedgerScale.pojo.dto.EventSourceDTO; +import com.njcn.product.diagram.LedgerScale.pojo.dto.LedgerScaleDTO; +import com.njcn.product.diagram.LedgerScale.pojo.vo.EventDetailVO; +import com.njcn.product.diagram.LedgerScale.pojo.vo.EventLedgerVO; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerBaseInfo; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.TerminalShowVO; +import com.njcn.product.diagram.LedgerScale.service.LedgerScaleService; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.LargeScreenCountParam; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-09-01 + * @Description: 台账规模 + */ +@Slf4j +@RestController +@Api(tags = "大屏") +@RequestMapping("/scale") +@RequiredArgsConstructor +public class LedgerScaleController extends BaseController { + + private final LedgerScaleService ledgerScaleService; + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/ledgerScale") + @ApiOperation("台账规模") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult ledgerScale(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("ledgerScale"); + LedgerScaleDTO ledgerScaleDTO = ledgerScaleService.ledgerScaleStatistic(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, ledgerScaleDTO, methodDescribe); + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/eventSource") + @ApiOperation("暂降溯源统计") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult eventSource(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("eventSource"); + EventSourceDTO eventSourceDTO = ledgerScaleService.eventSource(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, eventSourceDTO, methodDescribe); + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/eventAggregation") + @ApiOperation("暂降聚合分析") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult eventAggregation(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("eventAggregation"); + EventSourceDTO eventSourceDTO = ledgerScaleService.eventAggregation(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, eventSourceDTO, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/hasEventList") + @ApiOperation("一次接线图发生暂降事件的监测点闪烁") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult> hasEventList(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("hasEventList"); + List result = ledgerScaleService.hasEventList(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/hasUpEventList") + @ApiOperation("一次接线图发生谐波放大的监测点闪烁") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult> hasUpEventList(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("hasUpEventList"); + List result = ledgerScaleService.hasUpEventList(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/clickImage") + @ApiOperation("一次接线图点击事件") + @ApiImplicitParam(name = "lineId", value = "查询参数", required = true) + public HttpResult clickImage(@RequestParam("lineId")String lineId) { + String methodDescribe = getMethodDescribe("clickImage"); + EventLedgerVO result = ledgerScaleService.clickImage(lineId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/eventList") + @ApiOperation("暂降实时数据") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult> eventList(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("eventList"); + Page result = ledgerScaleService.eventList(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/eventListByLineId") + @ApiOperation("暂降实时数据") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult> eventListByLineId(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("eventListByLineId"); + Page result = ledgerScaleService.eventListByLineId(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/stationPage") + @ApiOperation("电站详情") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult> stationPage(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("stationPage"); + Page result = ledgerScaleService.stationPage(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/devPage") + @ApiOperation("终端详情") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult> devPage(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("devPage"); + Page result = ledgerScaleService.devPage(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/linePage") + @ApiOperation("监测点详情") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult> linePage(@RequestBody LargeScreenCountParam param) { + String methodDescribe = getMethodDescribe("linePage"); + Page result = ledgerScaleService.linePage(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/harmOneImage") + @ApiOperation("谐波溯源事件点击关联一次接线图") + @ApiImplicitParam(name = "param", value = "查询参数", required = true) + public HttpResult> harmOneImage(@RequestParam("id")String id, @RequestParam("time")Integer time) { + String methodDescribe = getMethodDescribe("harmOneImage"); + List result = ledgerScaleService.harmOneImage(id,time); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + +} diff --git a/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/dto/EventSourceDTO.java b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/dto/EventSourceDTO.java new file mode 100644 index 0000000..f7c6738 --- /dev/null +++ b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/dto/EventSourceDTO.java @@ -0,0 +1,32 @@ +package com.njcn.product.diagram.LedgerScale.pojo.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-09-01 + * @Description: + */ +@Data +public class EventSourceDTO { + + private Integer eventCount = 0; + + private List innerList = new ArrayList<>(); + + + + @Data + public static class Inner{ + + private String id; + + private String name; + + private Integer count; + + } +} diff --git a/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/dto/LedgerScaleDTO.java b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/dto/LedgerScaleDTO.java new file mode 100644 index 0000000..fb61b05 --- /dev/null +++ b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/dto/LedgerScaleDTO.java @@ -0,0 +1,25 @@ +package com.njcn.product.diagram.LedgerScale.pojo.dto; + +import lombok.Data; + +/** + * @Author: cdf + * @CreateTime: 2025-09-01 + * @Description: 台账规模 + */ +@Data +public class LedgerScaleDTO { + + private Integer stationAll; + + private Integer stationRun; + + private Integer devAll; + + private Integer devRun; + + private Integer lineAll; + + private Integer lineRun; + +} diff --git a/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/vo/EventDetailVO.java b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/vo/EventDetailVO.java new file mode 100644 index 0000000..cde637b --- /dev/null +++ b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/vo/EventDetailVO.java @@ -0,0 +1,82 @@ +package com.njcn.product.diagram.LedgerScale.pojo.vo; + +import com.njcn.product.terminal.mysqlTerminal.pojo.po.RmpEventDetailPO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: cdf + * @CreateTime: 2025-09-03 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class EventDetailVO extends RmpEventDetailPO { + + + /** + * 监测点id + */ + private String lineId; + + /** + * 监测点名称 + */ + private String lineName; + + /** + * 装置通道 + */ + private Integer num; + + /** + * 监测点电压等级 + */ + private String voltageLevel; + + /** + * 监测点用户 + */ + private String objName; + + /** + * 0:通讯中断;1:通讯正常 + */ + private Integer comFlag; + + /** + * 0:投运;1:热备用;2:停运 + */ + private Integer runFlag; + + /** + * 设备id + */ + private String devId; + + /** + * 设备名称 + */ + private String devName; + + /** + * 电站id + */ + private String stationId; + + /** + * 电站名称 + */ + private String stationName; + + /** + * 供电id + */ + private String gdId; + + /** + * 供电公司 + */ + private String gdName; + +} diff --git a/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/vo/EventLedgerVO.java b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/vo/EventLedgerVO.java new file mode 100644 index 0000000..4d52aac --- /dev/null +++ b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/pojo/vo/EventLedgerVO.java @@ -0,0 +1,27 @@ +package com.njcn.product.diagram.LedgerScale.pojo.vo; + +import com.njcn.product.diagram.LedgerScale.pojo.dto.LedgerScaleDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerBaseInfo; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.RmpEventDetailPO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-09-03 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class EventLedgerVO extends LedgerBaseInfo { + + private List eventIds; + + private List eventList; + + private Integer isImport; + + +} diff --git a/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/service/LedgerScaleService.java b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/service/LedgerScaleService.java new file mode 100644 index 0000000..eb3e5d4 --- /dev/null +++ b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/service/LedgerScaleService.java @@ -0,0 +1,47 @@ +package com.njcn.product.diagram.LedgerScale.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.advance.responsility.pojo.dto.CustomerResponsibility; +import com.njcn.product.diagram.LedgerScale.pojo.dto.EventSourceDTO; +import com.njcn.product.diagram.LedgerScale.pojo.dto.LedgerScaleDTO; +import com.njcn.product.diagram.LedgerScale.pojo.vo.EventDetailVO; +import com.njcn.product.diagram.LedgerScale.pojo.vo.EventLedgerVO; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerBaseInfo; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.TerminalShowVO; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.LargeScreenCountParam; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-09-01 + * @Description: + */ +public interface LedgerScaleService { + + LedgerScaleDTO ledgerScaleStatistic(LargeScreenCountParam param); + + EventSourceDTO eventSource(LargeScreenCountParam param); + + EventSourceDTO eventAggregation(LargeScreenCountParam param); + + List hasEventList(LargeScreenCountParam param); + + List hasUpEventList(LargeScreenCountParam param); + + + EventLedgerVO clickImage(String lineId); + + Page eventList(LargeScreenCountParam param); + + Page eventListByLineId(LargeScreenCountParam param); + + Page stationPage(LargeScreenCountParam param); + + Page devPage(LargeScreenCountParam param); + + Page linePage(LargeScreenCountParam param); + + + List harmOneImage(String id, Integer time); +} diff --git a/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/service/impl/LedgerScaleServiceImpl.java b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/service/impl/LedgerScaleServiceImpl.java new file mode 100644 index 0000000..fc8ce45 --- /dev/null +++ b/cn-diagram/src/main/java/com/njcn/product/diagram/LedgerScale/service/impl/LedgerScaleServiceImpl.java @@ -0,0 +1,500 @@ +package com.njcn.product.diagram.LedgerScale.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONArray; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.oss.utils.FileStorageUtil; +import com.njcn.product.advance.eventSource.mapper.RmpEventDetailAssMapper; +import com.njcn.product.advance.eventSource.pojo.po.RmpEventDetailAssPO; +import com.njcn.product.advance.harmonicUp.mapper.UpHarmonicDetailMapper; +import com.njcn.product.advance.harmonicUp.pojo.po.UpHarmonicDetail; +import com.njcn.product.advance.responsility.mapper.RespDataResultMapper; +import com.njcn.product.advance.responsility.pojo.dto.CustomerData; +import com.njcn.product.advance.responsility.pojo.dto.CustomerResponsibility; +import com.njcn.product.advance.responsility.pojo.dto.ResponsibilityResult; +import com.njcn.product.advance.responsility.pojo.po.RespDataResult; +import com.njcn.product.diagram.LedgerScale.pojo.dto.EventSourceDTO; +import com.njcn.product.diagram.LedgerScale.pojo.dto.LedgerScaleDTO; +import com.njcn.product.diagram.LedgerScale.pojo.vo.EventDetailVO; +import com.njcn.product.diagram.LedgerScale.pojo.vo.EventLedgerVO; +import com.njcn.product.terminal.mysqlTerminal.mapper.LineMapper; +import com.njcn.product.terminal.mysqlTerminal.mapper.UserReportPOMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.UserReportPO; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.TerminalShowVO; +import com.njcn.product.diagram.LedgerScale.service.LedgerScaleService; +import com.njcn.product.system.dict.mapper.DictDataMapper; +import com.njcn.product.system.dict.pojo.enums.DicDataTypeEnum; +import com.njcn.product.system.dict.pojo.po.DictData; +import com.njcn.product.terminal.mysqlTerminal.mapper.LedgerScaleMapper; +import com.njcn.product.terminal.mysqlTerminal.mapper.RmpEventDetailMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerBaseInfo; +import com.njcn.product.terminal.mysqlTerminal.pojo.enums.RunFlagEnum; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.LargeScreenCountParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.RmpEventDetailPO; +import com.njcn.product.terminal.mysqlTerminal.service.CommGeneralService; +import com.njcn.web.factory.PageFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @Author: cdf + * @CreateTime: 2025-09-01 + * @Description: 台账规模 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class LedgerScaleServiceImpl implements LedgerScaleService { + + private final CommGeneralService commGeneralService; + + private final LedgerScaleMapper ledgerScaleMapper; + + private final RmpEventDetailMapper rmpEventDetailMapper; + + private final RmpEventDetailAssMapper rmpEventDetailAssMapper; + private final DictDataMapper dictDataMapper; + private final LineMapper lineMapper; + + private final RespDataResultMapper respDataResultMapper; + + private final FileStorageUtil fileStorageUtil; + private final UserReportPOMapper userReportPOMapper; + private final UpHarmonicDetailMapper upHarmonicDetailMapper; + + + @Override + public LedgerScaleDTO ledgerScaleStatistic(LargeScreenCountParam param) { + LedgerScaleDTO ledgerScaleDTO = new LedgerScaleDTO(); + + List deptIds = commGeneralService.getAllLineIdsByDept(param.getDeptId()); + if (CollUtil.isEmpty(deptIds)) { + throw new BusinessException(CommonResponseEnum.FAIL, "用户部门没有绑定监测点"); + } + + List ledgerBaseInfoList = ledgerScaleMapper.getLedgerBaseInfo(deptIds); + + List runList = ledgerBaseInfoList.stream().filter(it -> Objects.equals(it.getRunFlag(), RunFlagEnum.RUNNING.getStatus())).collect(Collectors.toList()); + + + List onlineList = runList.stream().filter(it -> it.getComFlag() == 1).collect(Collectors.toList()); + + ledgerScaleDTO.setLineAll(runList.size()); + ledgerScaleDTO.setLineRun(onlineList.size()); + + long devAll = ledgerBaseInfoList.stream().map(LedgerBaseInfo::getDevId).distinct().count(); + long devRun = runList.stream().map(LedgerBaseInfo::getDevId).distinct().count(); + ledgerScaleDTO.setDevAll((int) devAll); + ledgerScaleDTO.setDevRun((int) devRun); + + long stationAll = ledgerBaseInfoList.stream().map(LedgerBaseInfo::getStationId).distinct().count(); + long stationRun = runList.stream().map(LedgerBaseInfo::getStationId).distinct().count(); + ledgerScaleDTO.setStationAll((int) stationAll); + ledgerScaleDTO.setStationRun((int) stationRun); + return ledgerScaleDTO; + } + + @Override + public EventSourceDTO eventSource(LargeScreenCountParam param) { + EventSourceDTO eventSourceDTO = new EventSourceDTO(); + List deptIds = commGeneralService.getRunLineIdsByDept(param.getDeptId()); + if (CollUtil.isEmpty(deptIds)) { + return eventSourceDTO; + } + + DateTime start = DateUtil.beginOfDay(DateUtil.parse(param.getSearchBeginTime())); + DateTime end = DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime())); + + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.between(RmpEventDetailPO::getStartTime, start, end).in(RmpEventDetailPO::getLineId, deptIds); + List eventList = rmpEventDetailMapper.selectList(lambdaQueryWrapper); + if (CollUtil.isEmpty(eventList)) { + return eventSourceDTO; + } + + List ids = eventList.stream().map(RmpEventDetailPO::getLineId).distinct().collect(Collectors.toList()); + List ledgerBaseInfoList = ledgerScaleMapper.getBaseInfo(ids); + Map objMap = ledgerBaseInfoList.stream().collect(Collectors.toMap(LedgerBaseInfo::getLineId, Function.identity())); + + Map> map = eventList.stream().collect(Collectors.groupingBy(RmpEventDetailPO::getLineId)); + + List innerList = new ArrayList<>(); + map.forEach((k, val) -> { + EventSourceDTO.Inner inner = new EventSourceDTO.Inner(); + LedgerBaseInfo ledgerBaseInfo = objMap.get(k); + inner.setName(StrUtil.isNotBlank(ledgerBaseInfo.getObjName()) ? ledgerBaseInfo.getObjName() : ledgerBaseInfo.getLineName()); + inner.setCount(val.size()); + inner.setId(ledgerBaseInfo.getLineId()); + innerList.add(inner); + }); + eventSourceDTO.setEventCount(eventList.size()); + eventSourceDTO.setInnerList(innerList); + + return eventSourceDTO; + } + + @Override + public EventSourceDTO eventAggregation(LargeScreenCountParam param) { + EventSourceDTO eventSourceDTO = new EventSourceDTO(); + + + DateTime start = DateUtil.beginOfDay(DateUtil.parse(param.getSearchBeginTime())); + DateTime end = DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime())); + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.orderByDesc(RmpEventDetailAssPO::getTimeId).between(RmpEventDetailAssPO::getTimeId, start, end); + List assPOList = rmpEventDetailAssMapper.selectList(lambdaQueryWrapper); + if (CollUtil.isEmpty(assPOList)) { + return eventSourceDTO; + } + /* Map assObjMap = assPOList.stream().collect(Collectors.toMap(RmpEventDetailAssPO::getEventAssId, Function.identity())); + + List assIds = assPOList.stream().map(RmpEventDetailAssPO::getEventAssId).collect(Collectors.toList()); + List rmpEventDetailPOList = rmpEventDetailMapper.selectList(new LambdaQueryWrapper().in(RmpEventDetailPO::getEventassIndex, assIds)); + + + Map assMap = assPOList.stream().collect(Collectors.toMap(RmpEventDetailAssPO::getEventAssId, RmpEventDetailAssPO::getTimeId)); + rmpEventDetailPOList = rmpEventDetailPOList.stream().filter(it -> { + if (!assMap.containsKey(it.getEventassIndex())) { + return false; + } + LocalDateTime localDateTime = assMap.get(it.getEventassIndex()); + if (it.getStartTime().equals(localDateTime)) { + return true; + } else { + return false; + } + }).collect(Collectors.toList());*/ + + eventSourceDTO.setEventCount(assPOList.size()); + + List innerList = new ArrayList<>(); + for (RmpEventDetailAssPO it : assPOList) { + EventSourceDTO.Inner inner = new EventSourceDTO.Inner(); + inner.setId(it.getEventAssId()); + inner.setName(it.getContentDes()); + innerList.add(inner); + } + eventSourceDTO.setInnerList(innerList); + return eventSourceDTO; + } + + @Override + public List hasEventList(LargeScreenCountParam param) { + List result = new ArrayList<>(); + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + if (StrUtil.isBlank(param.getEventAssId())) { + List ids = commGeneralService.getRunLineIdsByDept(param.getDeptId()); + if (CollUtil.isEmpty(ids)) { + return result; + } + DateTime start = DateUtil.beginOfDay(DateUtil.parse(param.getSearchBeginTime())); + DateTime end = DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime())); + lambdaQueryWrapper.in(RmpEventDetailPO::getLineId, ids).between(RmpEventDetailPO::getStartTime, start, end); + } else { + lambdaQueryWrapper.eq(RmpEventDetailPO::getEventassIndex, param.getEventAssId()); + } + + + List rmpEventDetailPOList = rmpEventDetailMapper.selectList(lambdaQueryWrapper); + List lineIds = rmpEventDetailPOList.stream().map(RmpEventDetailPO::getLineId).distinct().collect(Collectors.toList()); + if (CollUtil.isEmpty(lineIds)) { + return result; + } + Map ledgerBaseInfoMap = ledgerScaleMapper.getLedgerBaseInfo(lineIds).stream().collect(Collectors.toMap(LedgerBaseInfo::getLineId, Function.identity())); + Map> map = rmpEventDetailPOList.stream().collect(Collectors.groupingBy(RmpEventDetailPO::getLineId)); + + RmpEventDetailPO minPo = rmpEventDetailPOList.stream().min(Comparator.comparingDouble(RmpEventDetailPO::getFeatureAmplitude)).get(); + map.forEach((lineId, list) -> { + + + + LedgerBaseInfo ledgerBaseInfo = ledgerBaseInfoMap.get(lineId); + EventLedgerVO eventLedgerVO = new EventLedgerVO(); + BeanUtil.copyProperties(ledgerBaseInfo, eventLedgerVO); + eventLedgerVO.setEventIds(list.stream().map(RmpEventDetailPO::getEventId).collect(Collectors.toList())); + if (StrUtil.isBlank(param.getEventAssId())) { + eventLedgerVO.setIsImport(DataStateEnum.DELETED.getCode()); + }else { + if(minPo.getLineId().equals(lineId)){ + eventLedgerVO.setIsImport(DataStateEnum.ENABLE.getCode()); + }else { + eventLedgerVO.setIsImport(DataStateEnum.DELETED.getCode()); + } + } + result.add(eventLedgerVO); + }); + return result; + } + + @Override + public List hasUpEventList(LargeScreenCountParam param) { + List result = new ArrayList<>(); + List ids = commGeneralService.getRunLineIdsByDept(param.getDeptId()); + if (CollUtil.isEmpty(ids)) { + return result; + } + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + DateTime start = DateUtil.beginOfDay(DateUtil.parse(param.getSearchBeginTime())); + DateTime end = DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime())); + lambdaQueryWrapper.in(UpHarmonicDetail::getMonitorId, ids).between(UpHarmonicDetail::getStartTime, start, end); + List upHarmonicDetailList = upHarmonicDetailMapper.selectList(lambdaQueryWrapper); + if(CollUtil.isEmpty(upHarmonicDetailList)){ + return result; + } + + List monitorIds = upHarmonicDetailList.stream().map(UpHarmonicDetail::getMonitorId).distinct().collect(Collectors.toList()); + List ledgerBaseInfoList = ledgerScaleMapper.getLedgerBaseInfo(monitorIds); + Map ledgerBaseInfoMap = ledgerBaseInfoList.stream().collect(Collectors.toMap(LedgerBaseInfo::getLineId,Function.identity())); + + Map> map = upHarmonicDetailList.stream().collect(Collectors.groupingBy(UpHarmonicDetail::getMonitorId)); + map.forEach((lineId,list)->{ + EventLedgerVO eventLedgerVO = new EventLedgerVO(); + BeanUtil.copyProperties(ledgerBaseInfoMap.get(lineId),eventLedgerVO); + eventLedgerVO.setEventIds(list.stream().map(UpHarmonicDetail::getId).collect(Collectors.toList())); + result.add(eventLedgerVO); + }); + return result; + } + + @Override + public EventLedgerVO clickImage(String lineId) { + List ledgerBaseInfoList = ledgerScaleMapper.getLedgerBaseInfo(Stream.of(lineId).collect(Collectors.toList())); + if (CollUtil.isEmpty(ledgerBaseInfoList)) { + throw new BusinessException(CommonResponseEnum.FAIL, "当前节点未查询到测点信息"); + } + + EventLedgerVO eventLedgerVO = new EventLedgerVO(); + BeanUtil.copyProperties(ledgerBaseInfoList.get(0), eventLedgerVO); + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(RmpEventDetailPO::getLineId, lineId); + List rmpEventDetailPOList = rmpEventDetailMapper.selectList(lambdaQueryWrapper); + + List dictDataList = dictDataMapper.getDicDataByTypeCode(DicDataTypeEnum.EVENT_STATIS.getCode()); + Map dictDataMap = dictDataList.stream().collect(Collectors.toMap(DictData::getId, Function.identity())); + rmpEventDetailPOList.forEach(item-> item.setEventType(dictDataMap.get(item.getEventType()).getName())); + eventLedgerVO.setEventList(rmpEventDetailPOList); + return eventLedgerVO; + } + + + @Override + public Page eventList(LargeScreenCountParam param) { + Page result = new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)); + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + if (StrUtil.isBlank(param.getEventAssId())) { + List ids = commGeneralService.getRunLineIdsByDept(param.getDeptId()); + if (CollUtil.isEmpty(ids)) { + return result; + } + + if(StrUtil.isNotBlank(param.getSearchValue())){ + ids = ledgerScaleMapper.getQueryLedger(ids,param.getSearchValue()); + if (CollUtil.isEmpty(ids)) { + return result; + } + } + + DateTime start = DateUtil.beginOfDay(DateUtil.parse(param.getSearchBeginTime())); + DateTime end = DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime())); + lambdaQueryWrapper.in(RmpEventDetailPO::getLineId, ids).between(RmpEventDetailPO::getStartTime, start, end).orderByDesc(RmpEventDetailPO::getStartTime); + + if(StrUtil.isNotBlank(param.getEventType())){ + lambdaQueryWrapper.eq(RmpEventDetailPO::getEventType,param.getEventType()); + } + + if(Objects.nonNull(param.getEventDurationMin()) ||Objects.nonNull(param.getEventDurationMax())){ + lambdaQueryWrapper.gt(Objects.nonNull(param.getEventDurationMin()),RmpEventDetailPO::getDuration,param.getEventDurationMin()); + lambdaQueryWrapper.lt(Objects.nonNull(param.getEventDurationMax()),RmpEventDetailPO::getDuration,param.getEventDurationMax()); + } + + if(Objects.nonNull(param.getEventValueMin()) ||Objects.nonNull(param.getEventValueMax())){ + lambdaQueryWrapper.gt(Objects.nonNull(param.getEventValueMin()),RmpEventDetailPO::getFeatureAmplitude,param.getEventValueMin()); + lambdaQueryWrapper.lt(Objects.nonNull(param.getEventValueMax()),RmpEventDetailPO::getFeatureAmplitude,param.getEventValueMax()); + } + + } else { + lambdaQueryWrapper.eq(RmpEventDetailPO::getEventassIndex, param.getEventAssId()); + } + + Page page = rmpEventDetailMapper.selectPage(new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)), lambdaQueryWrapper); + if (CollUtil.isEmpty(page.getRecords())) { + return result; + } + + List lineIds = page.getRecords().stream().map(RmpEventDetailPO::getLineId).distinct().collect(Collectors.toList()); + Map ledgerBaseInfoMap = ledgerScaleMapper.getLedgerBaseInfo(lineIds).stream().collect(Collectors.toMap(LedgerBaseInfo::getLineId, Function.identity())); + + List dictDataList = dictDataMapper.getDicDataByTypeCode(DicDataTypeEnum.EVENT_STATIS.getCode()); + Map dictDataMap = dictDataList.stream().collect(Collectors.toMap(DictData::getId, Function.identity())); + + List temList = new ArrayList<>(); + page.getRecords().forEach(item -> { + EventDetailVO eventDetailVO = new EventDetailVO(); + BeanUtil.copyProperties(item, eventDetailVO); + + eventDetailVO.setFeatureAmplitude(BigDecimal.valueOf(item.getFeatureAmplitude()).setScale(5, RoundingMode.HALF_UP).doubleValue()); + eventDetailVO.setEventType(dictDataMap.get(eventDetailVO.getEventType()).getName()); + + if (ledgerBaseInfoMap.containsKey(item.getLineId())) { + LedgerBaseInfo ledgerBaseInfo = ledgerBaseInfoMap.get(item.getLineId()); + BeanUtil.copyProperties(ledgerBaseInfo, eventDetailVO); + } + temList.add(eventDetailVO); + }); + result.setRecords(temList); + result.setTotal(page.getTotal()); + return result; + } + + + @Override + public Page eventListByLineId(LargeScreenCountParam param) { + Page result = new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)); + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + DateTime start = DateUtil.beginOfDay(DateUtil.parse(param.getSearchBeginTime())); + DateTime end = DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime())); + lambdaQueryWrapper.eq(RmpEventDetailPO::getLineId, param.getLineId()).between(RmpEventDetailPO::getStartTime, start, end).orderByDesc(RmpEventDetailPO::getStartTime); + + Page page = rmpEventDetailMapper.selectPage(new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)), lambdaQueryWrapper); + if (CollUtil.isEmpty(page.getRecords())) { + return result; + } + + List ledgerBaseInfoList = ledgerScaleMapper.getLedgerBaseInfo(Collections.singletonList(param.getLineId())); + LedgerBaseInfo ledgerBaseInfo = ledgerBaseInfoList.get(0); + + List dictDataList = dictDataMapper.getDicDataByTypeCode(DicDataTypeEnum.EVENT_STATIS.getCode()); + Map dictDataMap = dictDataList.stream().collect(Collectors.toMap(DictData::getId, Function.identity())); + + List temList = new ArrayList<>(); + page.getRecords().forEach(item -> { + EventDetailVO eventDetailVO = new EventDetailVO(); + BeanUtil.copyProperties(item, eventDetailVO); + + eventDetailVO.setFeatureAmplitude(BigDecimal.valueOf(item.getFeatureAmplitude()).setScale(5, RoundingMode.HALF_UP).doubleValue()); + eventDetailVO.setEventType(dictDataMap.get(eventDetailVO.getEventType()).getName()); + BeanUtil.copyProperties(ledgerBaseInfo, eventDetailVO); + temList.add(eventDetailVO); + }); + result.setRecords(temList); + result.setTotal(page.getTotal()); + return result; + } + + + @Override + public Page stationPage(LargeScreenCountParam param) { + List lineIds = commGeneralService.getAllLineIdsByDept(param.getDeptId()); + if (CollUtil.isEmpty(lineIds)) { + return new Page<>(); + } + Page page = lineMapper.getStationList(new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)), lineIds, param.getRunFlag(), param.getSearchValue()); + if (CollUtil.isNotEmpty(page.getRecords())) { + List dictData = dictDataMapper.getDicDataByTypeCode(DicDataTypeEnum.DEV_VOLTAGE_STAND.getCode()); + Map map = dictData.stream().collect(Collectors.toMap(DictData::getId, Function.identity())); + page.getRecords().forEach(it -> { + if (map.containsKey(it.getStationVoltageLevel())) { + it.setStationVoltageLevel(map.get(it.getStationVoltageLevel()).getName()); + } + }); + } + + return page; + } + + + @Override + public Page devPage(LargeScreenCountParam param) { + List lineIds = commGeneralService.getAllLineIdsByDept(param.getDeptId()); + if (CollUtil.isEmpty(lineIds)) { + return new Page<>(); + } + Page page = lineMapper.getDevList(new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)), lineIds, param.getRunFlag(), param.getSearchValue()); + if (CollUtil.isNotEmpty(page.getRecords())) { + List dictData = dictDataMapper.getDicDataByTypeCode(DicDataTypeEnum.DEV_MANUFACTURER.getCode()); + Map map = dictData.stream().collect(Collectors.toMap(DictData::getId, Function.identity())); + page.getRecords().forEach(it -> { + if (map.containsKey(it.getManufacturer())) { + it.setManufacturer(map.get(it.getManufacturer()).getName()); + } + }); + } + return page; + } + + @Override + public Page linePage(LargeScreenCountParam param) { + List lineIds = commGeneralService.getRunLineIdsByDept(param.getDeptId()); + if (CollUtil.isEmpty(lineIds)) { + return new Page<>(); + } + Page page = lineMapper.getLineList(new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)), lineIds, param.getComFlag(), param.getSearchValue()); + if (CollUtil.isNotEmpty(page.getRecords())) { + List dictData = dictDataMapper.getDicDataByTypeCode(DicDataTypeEnum.DEV_VOLTAGE_STAND.getCode()); + Map map = dictData.stream().collect(Collectors.toMap(DictData::getId, Function.identity())); + page.getRecords().forEach(it -> { + if (map.containsKey(it.getVoltageLevel())) { + it.setVoltageLevel(map.get(it.getVoltageLevel()).getName()); + } + }); + } + return page; + } + + @Override + public List harmOneImage(String id, Integer time) { + LambdaQueryWrapper respDataResultLambdaQueryWrapper = new LambdaQueryWrapper<>(); + respDataResultLambdaQueryWrapper.eq(RespDataResult::getResDataId, id) + .eq(RespDataResult::getTime, time).orderByDesc(RespDataResult::getCreateTime); + List respDataResults = respDataResultMapper.selectList(respDataResultLambdaQueryWrapper); + if (CollectionUtil.isNotEmpty(respDataResults)) { + + RespDataResult respDataResult = respDataResults.get(0); + //处理排名前10数据 + InputStream respStream = fileStorageUtil.getFileStream(respDataResult.getUserResponsibility()); + String respStr = IoUtil.readUtf8(respStream); + List respData = JSONArray.parseArray(respStr, CustomerResponsibility.class); + List userNos = respData.stream().map(it -> it.getCustomerName().substring(it.getCustomerName().indexOf("(") + 1, it.getCustomerName().indexOf(")"))).collect(Collectors.toList()); + + List ledgerBaseInfoList = lineMapper.queryMonitorByUser(userNos); + Map ledgerBaseInfoMap = ledgerBaseInfoList.stream().collect(Collectors.toMap(LedgerBaseInfo::getUserNo, Function.identity())); + respData.forEach(it -> { + String tem = it.getCustomerName().substring(it.getCustomerName().indexOf("(") + 1, it.getCustomerName().indexOf(")")); + if (ledgerBaseInfoMap.containsKey(tem)) { + LedgerBaseInfo ledgerBaseInfo = ledgerBaseInfoMap.get(tem); + it.setMonitorId(ledgerBaseInfo.getLineId()); + } + }); + return respData; + } + return new ArrayList<>(); + } + + +} diff --git a/cn-diagram/src/main/java/com/njcn/product/diagram/job/CustomJob.java b/cn-diagram/src/main/java/com/njcn/product/diagram/job/CustomJob.java new file mode 100644 index 0000000..af0d9f4 --- /dev/null +++ b/cn-diagram/src/main/java/com/njcn/product/diagram/job/CustomJob.java @@ -0,0 +1,39 @@ +package com.njcn.product.diagram.job; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import com.njcn.product.advance.harmonicUp.service.HarmonicUpService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * @Author: cdf + * @CreateTime: 2025-09-19 + * @Description: 定时任务 + */ +@Component +@EnableScheduling +@RequiredArgsConstructor +@Slf4j +public class CustomJob { + + private final HarmonicUpService harmonicUpService; + + // 每天凌晨4:30执行 + @Scheduled(cron = "0 30 4 * * ?") + public void UpHarmonicJob(){ + log.info("开始执行谐波放大调度任务--------------------------------"); + String date = LocalDate.now().minusDays(1).format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)); + harmonicUpService.analyzePreData(date); + log.info("执行谐波放大调度任务结束--------------------------------"); + } + + + +} diff --git a/cn-diagram/src/main/java/com/njcn/product/diagram/websocket/WebSocketConfig.java b/cn-diagram/src/main/java/com/njcn/product/diagram/websocket/WebSocketConfig.java new file mode 100644 index 0000000..21eeaf5 --- /dev/null +++ b/cn-diagram/src/main/java/com/njcn/product/diagram/websocket/WebSocketConfig.java @@ -0,0 +1,41 @@ +package com.njcn.product.diagram.websocket; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; +import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; + +/** + * Description: + * Date: 2024/12/13 15:09【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } + + /** + * 通信文本消息和二进制缓存区大小 + * 避免对接 第三方 报文过大时,Websocket 1009 错误 + * + * @return + */ + + @Bean + public ServletServerContainerFactoryBean createWebSocketContainer() { + ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); + // 在此处设置bufferSize + container.setMaxTextMessageBufferSize(10240000); + container.setMaxBinaryMessageBufferSize(10240000); + container.setMaxSessionIdleTimeout(15 * 60000L); + return container; + } + + +} diff --git a/cn-diagram/src/main/java/com/njcn/product/diagram/websocket/WebSocketServer.java b/cn-diagram/src/main/java/com/njcn/product/diagram/websocket/WebSocketServer.java new file mode 100644 index 0000000..a011594 --- /dev/null +++ b/cn-diagram/src/main/java/com/njcn/product/diagram/websocket/WebSocketServer.java @@ -0,0 +1,176 @@ +package com.njcn.product.diagram.websocket; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSONObject; +import com.njcn.product.cnzutai.zutai.pojo.dto.AskRealTimeDataDTO; +import com.njcn.product.cnzutai.zutai.pojo.dto.RealTimeDataDTO; +import com.njcn.product.cnzutai.zutai.pojo.vo.CsRtDataVO; +import com.njcn.product.cnzutai.zutai.service.ILineTargetService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + + +@Slf4j +@Component +@ServerEndpoint(value = "/ws/{userId}") +public class WebSocketServer { + + private static final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap lastHeartbeatTime = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap heartbeatExecutors = new ConcurrentHashMap<>(); + // 60秒超时 + private static final long HEARTBEAT_TIMEOUT = 60; + + @Autowired + private static ILineTargetService lineTargetService; + + @Autowired + public void setDataVQuery( ILineTargetService lineTargetService) { + WebSocketServer.lineTargetService = lineTargetService; + } + + + @OnOpen + public void onOpen(Session session, @PathParam("userId") String userId) { + if (StrUtil.isNotBlank(userId)) { + sessions.put(userId, session); + lastHeartbeatTime.put(userId, System.currentTimeMillis()); + sendMessage(session, "连接成功"); + System.out.println("用户 " + userId + " 已连接"); + + // 启动心跳检测 + startHeartbeat(session, userId); + } else { + try { + session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "用户ID不能为空")); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @OnMessage + public void onMessage(String message, Session session, @PathParam("userId") String userId) { + if ("alive".equalsIgnoreCase(message)) { + // 更新最后心跳时间 + lastHeartbeatTime.put(userId, System.currentTimeMillis()); + sendMessage(session, "over"); + } else { + System.out.println("收到用户 " + userId + " 的消息: " + message); + // TODO: 处理业务逻辑 + + AskRealTimeDataDTO param = JSONUtil.toBean(message,AskRealTimeDataDTO.class,true); + if(Objects.isNull(message)){ + RealTimeDataDTO recallReplyDTO = new RealTimeDataDTO(500,"参数有误",1); + sendMessage(session, JSONObject.toJSONString(recallReplyDTO)); + }else { + List lineData = lineTargetService.getLineData(param.getPageId()); + List collect = lineData.stream().filter(temp -> (!CollectionUtils.isEmpty(param.getLineIdList())) && param.getLineIdList().contains(temp.getLineId())).collect(Collectors.toList()); + RealTimeDataDTO recallReplyDTO = new RealTimeDataDTO(); + recallReplyDTO.setCode(200); + recallReplyDTO.setMessage(JSONObject.toJSONString(collect)); + + sendMessage(session,JSONObject.toJSONString(recallReplyDTO)); + + } + } + } + + @OnClose + public void onClose(Session session, CloseReason closeReason, @PathParam("userId") String userId) { + // 移除用户并取消心跳检测 + sessions.remove(userId); + lastHeartbeatTime.remove(userId); + ScheduledExecutorService executor = heartbeatExecutors.remove(userId); + if (executor != null) { + executor.shutdownNow(); + } + System.out.println("用户 " + userId + " 已断开连接,状态码: " + closeReason.getCloseCode()); + } + + @OnError + public void onError(Session session, Throwable throwable, @PathParam("userId") String userId) { + System.out.println("用户 " + userId + " 发生错误: " + throwable.getMessage()); + try { + session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, "发生错误")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void sendMessageToUser(String userId, String message) { + Session session = sessions.get(userId); + if (session != null && session.isOpen()) { + try { + session.getBasicRemote().sendText(message); + } catch (IOException e) { + System.out.println("发送消息给用户 " + userId + " 失败: " + e.getMessage()); + } + } else { + System.out.println("webSocket用户 " + userId + " 不在线或会话已关闭"); + } + } + + private final Object lock = new Object(); + + public void sendMessageToAll(String message) { + sessions.forEach((userId, session) -> { + System.out.println("给用户推送消息" + userId); + if (session.isOpen()) { + synchronized (lock) { + try { + session.getBasicRemote().sendText(message); + } catch (IOException e) { + System.out.println("发送消息给用户 " + userId + " 失败: " + e.getMessage()); + } + } + } + }); + } + + private void sendMessage(Session session, String message) { + try { + session.getBasicRemote().sendText(message); + } catch (IOException e) { + System.out.println("发送消息失败: " + e.getMessage()); + } + } + + private void startHeartbeat(Session session, String userId) { + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + heartbeatExecutors.put(userId, executor); + + // 定期检查心跳 + executor.scheduleAtFixedRate(() -> { + long lastTime = lastHeartbeatTime.getOrDefault(userId, 0L); + long currentTime = System.currentTimeMillis(); + + // 如果超过30秒没有收到心跳 + if (currentTime - lastTime > HEARTBEAT_TIMEOUT * 1000) { + try { + System.out.println("用户 " + userId + " 心跳超时,关闭连接"); + session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "心跳超时")); + } catch (IOException e) { + System.out.println("关闭用户 " + userId + " 连接时出错: " + e.getMessage()); + } + executor.shutdown(); + heartbeatExecutors.remove(userId); + } + }, 0, 5, TimeUnit.SECONDS); // 每5秒检查一次 + } +} \ No newline at end of file diff --git a/cn-terminal/.gitignore b/cn-terminal/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/cn-terminal/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/cn-terminal/pom.xml b/cn-terminal/pom.xml new file mode 100644 index 0000000..f14a981 --- /dev/null +++ b/cn-terminal/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + com.njcn.product + CN_Product + 1.0.0 + cn-terminal + 1.0.0 + cn-terminal + cn-terminal + + + + com.njcn + njcn-common + 0.0.1 + + + + com.njcn + mybatis-plus + 0.0.1 + + + + com.njcn + spingboot2.3.12 + 2.3.12 + + + + + com.oracle.database.jdbc + ojdbc8 + 21.6.0.0 + + + com.oracle.database.nls + orai18n + 21.1.0.0 + + + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 3.5.1 + + + + com.njcn + common-event + 1.0.0 + + + common-microservice + com.njcn + + + common-web + com.njcn + + + + + + com.njcn.product + cn-user + 1.0.0 + + + + com.njcn.product + cn-system + 1.0.0 + + + + com.alibaba + fastjson + 1.2.83 + + + + + + diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqDeviceDetailMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqDeviceDetailMapper.java new file mode 100644 index 0000000..2b9e285 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqDeviceDetailMapper.java @@ -0,0 +1,13 @@ +package com.njcn.product.terminal.device.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.terminal.device.pojo.po.PqDeviceDetail; + +/** + * CN_Gather + * + * @author cdf + * @date 2025/8/12 + */ +public interface PqDeviceDetailMapper extends BaseMapper { +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqDeviceMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqDeviceMapper.java new file mode 100644 index 0000000..efdda14 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqDeviceMapper.java @@ -0,0 +1,30 @@ +package com.njcn.product.terminal.device.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import com.njcn.product.terminal.device.pojo.dto.DeviceDTO; +import com.njcn.product.terminal.device.pojo.dto.DeviceDeptDTO; +import com.njcn.product.terminal.device.pojo.po.PqDevice; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqDeviceMapper extends BaseMapper { + List queryListByIds(@Param("ids") List ids); + + Page selectDeviceDTOPage(Page pqsEventdetailPage, @Param("searchValue") String searchValue,@Param("devIndexs") List devIndexs); + + Page queryListByLineIds(Page pqsEventdetailPage, @Param("searchValue") String searchValue,@Param("lineIds") List lineIds); + + + List selectDeviceDept(); +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqGdCompanyMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqGdCompanyMapper.java new file mode 100644 index 0000000..acc2ff6 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqGdCompanyMapper.java @@ -0,0 +1,16 @@ +package com.njcn.product.terminal.device.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.terminal.device.pojo.po.PqGdCompany; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:48【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqGdCompanyMapper extends BaseMapper { + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqLineMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqLineMapper.java new file mode 100644 index 0000000..68b3c91 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqLineMapper.java @@ -0,0 +1,28 @@ +package com.njcn.product.terminal.device.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.terminal.device.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.terminal.device.pojo.po.PqLine; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:43【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqLineMapper extends BaseMapper { + + List getBaseLineInfo(@Param("ids")List ids); + + + List getBaseLedger(@Param("ids")List ids,@Param("searchValue")String searchValue); + + + List getRunMonitorIds(@Param("ids")List ids); + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqLinedetailMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqLinedetailMapper.java new file mode 100644 index 0000000..f84d33e --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqLinedetailMapper.java @@ -0,0 +1,9 @@ +package com.njcn.product.terminal.device.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.terminal.device.pojo.po.PqLinedetail; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PqLinedetailMapper extends BaseMapper { +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqSubstationMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqSubstationMapper.java new file mode 100644 index 0000000..89c2085 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqSubstationMapper.java @@ -0,0 +1,21 @@ +package com.njcn.product.terminal.device.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.terminal.device.pojo.dto.SubstationDTO; +import com.njcn.product.terminal.device.pojo.po.PqSubstation; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:48【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqSubstationMapper extends BaseMapper { + List queryListByIds(@Param("ids")List ids); +} \ No newline at end of file diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqsDeptslineMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqsDeptslineMapper.java new file mode 100644 index 0000000..3f032c7 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqsDeptslineMapper.java @@ -0,0 +1,17 @@ +package com.njcn.product.terminal.device.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.terminal.device.pojo.po.PqsDeptsline; + +/** + * + * Description: + * Date: 2025/06/19 下午 3:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsDeptslineMapper extends BaseMapper { + + // List getPhoneUser(@Param("lineId")String lineId); +} \ No newline at end of file diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqsStationMapMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqsStationMapMapper.java new file mode 100644 index 0000000..bbf6f15 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/PqsStationMapMapper.java @@ -0,0 +1,18 @@ +package com.njcn.product.terminal.device.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.terminal.device.pojo.po.PqsStationMap; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:43【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsStationMapMapper extends BaseMapper { + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqDeviceMapper.xml b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqDeviceMapper.xml new file mode 100644 index 0000000..e5a326d --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqDeviceMapper.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + DEV_INDEX, GD_INDEX, SUB_INDEX, "NAME", "STATUS", DEVTYPE, LOGONTIME, UPDATETIME, + NODE_INDEX, PORTID, DEVFLAG, DEV_SERIES, DEV_KEY, IP, DEVMODEL, CALLFLAG, DATATYPE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqLineMapper.xml b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqLineMapper.xml new file mode 100644 index 0000000..db278fc --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqLineMapper.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + LINE_INDEX, GD_INDEX, SUB_INDEX, SUBV_INDEX, DEV_INDEX, "NAME", PT1, PT2, CT1, CT2, + DEVCMP, DLCMP, JZCMP, XYCMP, SUBV_NO, "SCALE", SUBV_NAME + + + + + + + + + + + + diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqSubstationMapper.xml b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqSubstationMapper.xml new file mode 100644 index 0000000..01c1e6a --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/mapper/mapping/PqSubstationMapper.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + SUB_INDEX, GD_INDEX, "NAME", "SCALE" + + + + \ No newline at end of file diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/DeviceDTO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/DeviceDTO.java new file mode 100644 index 0000000..1445451 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/DeviceDTO.java @@ -0,0 +1,45 @@ +package com.njcn.product.terminal.device.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * Description: + * Date: 2025/06/27 下午 3:25【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class DeviceDTO { + private Integer devId; + private String devName; + private Integer stationId; + private String stationName; + private String gdName; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + private String devFlag; + private String ip; + private String manufacturerName; + + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate thisTimeCheck; + + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate nextTimeCheck; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime logonTime; + + private String deptName; + //通讯状态 + private Integer runFlag=0; + //装置通讯状态(0:中断;1:正常) + private Integer status; + private double onLineRate=0.00; + private double integrityRate = 0.00; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/DeviceDeptDTO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/DeviceDeptDTO.java new file mode 100644 index 0000000..f084dd6 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/DeviceDeptDTO.java @@ -0,0 +1,18 @@ +package com.njcn.product.terminal.device.pojo.dto; + +import lombok.Data; + +/** + * Description: + * Date: 2025/06/27 下午 3:25【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class DeviceDeptDTO { + private Integer devId; + private String deptId; + private String deptName; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/LedgerBaseInfoDTO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/LedgerBaseInfoDTO.java new file mode 100644 index 0000000..b0ff2b8 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/LedgerBaseInfoDTO.java @@ -0,0 +1,39 @@ +package com.njcn.product.terminal.device.pojo.dto; + +import lombok.Data; + +/** + * @Author: cdf + * @CreateTime: 2025-06-25 + * @Description: + */ +@Data +public class LedgerBaseInfoDTO { + private String gdName; + private String gdIndex; + + private Integer lineId; + + private String lineName; + + private Integer busBarId; + + private String busBarName; + + private Integer devId; + + private String devName; + + private String objName; + + private Integer stationId; + + private String stationName; + //通讯状态 + private Integer runFlag=0; + + private Integer eventCount; + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/OracleLedgerTreeDTO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/OracleLedgerTreeDTO.java new file mode 100644 index 0000000..755a086 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/OracleLedgerTreeDTO.java @@ -0,0 +1,25 @@ +package com.njcn.product.terminal.device.pojo.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-08-28 + * @Description: + */ +@Data +public class OracleLedgerTreeDTO { + + private String name; + + private Integer id; + + private Integer pid; + + private Integer level; + + private List children = new ArrayList<>(); +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/PqsDeptDTO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/PqsDeptDTO.java new file mode 100644 index 0000000..8c74107 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/PqsDeptDTO.java @@ -0,0 +1,70 @@ +package com.njcn.product.terminal.device.pojo.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * Description: + * Date: 2025/07/29 下午 3:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class PqsDeptDTO { + /** + * 部门表Guid + */ + private String deptsIndex; + + /** + * 部门名称 + */ + + private String deptsname; + + /** + * 排序 + */ + + private Integer deptsDesc; + + /** + * (关联表PQS_User)用户表Guid + */ + + private String userIndex; + + /** + * 更新时间 + */ + + private LocalDateTime updatetime; + + /** + * 部门描述 + */ + + private String deptsDescription; + + /** + * 角色状态0:删除;1:正常; + */ + + private Integer state; + + /** + * 行政区域 + */ + + private String area; + + private String areaName; + + + private Integer customDept; + + + private String parentnodeid; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/SubstationDTO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/SubstationDTO.java new file mode 100644 index 0000000..7a8405d --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/dto/SubstationDTO.java @@ -0,0 +1,22 @@ +package com.njcn.product.terminal.device.pojo.dto; + +import lombok.Data; + +/** + * Description: + * Date: 2025/06/27 下午 3:37【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class SubstationDTO { + + private Integer stationId; + private String stationName; + private String gdName; + private double longitude; + private double latitude; + private Integer runFlag=0;; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqDevice.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqDevice.java new file mode 100644 index 0000000..dab495f --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqDevice.java @@ -0,0 +1,127 @@ +package com.njcn.product.terminal.device.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +/** + * 靠靠? + */ +@Data +@NoArgsConstructor +@TableName(value = "PQ_DEVICE") +public class PqDevice { + /** + * 靠靠 + */ + @TableId(value = "DEV_INDEX", type = IdType.INPUT) + private Integer devIndex; + + /** + * 靠靠靠 + */ + @TableField(value = "GD_INDEX") + private Integer gdIndex; + + /** + * 靠靠? + */ + @TableField(value = "SUB_INDEX") + private Integer subIndex; + + /** + * 靠靠 + */ + @TableField(value = "\"NAME\"") + private String name; + + /** + * 靠靠靠(0:靠;1:靠) + */ + @TableField(value = "\"STATUS\"") + private Integer status; + + /** + * (靠縋QS_Dicdata)靠靠Guid + */ + @TableField(value = "DEVTYPE") + private String devtype; + + /** + * 靠靠 + */ + @TableField(value = "LOGONTIME") + private LocalDateTime logontime; + + /** + * 靠靠靠 + */ + @TableField(value = "UPDATETIME") + private LocalDateTime updatetime; + + /** + * 靠縉odeInformation)靠靠靠,靠靠靠靠靠靠靠? + */ + @TableField(value = "NODE_INDEX") + private Integer nodeIndex; + + /** + * 靠ID,靠靠靠 + */ + @TableField(value = "PORTID") + private Long portid; + + /** + * 靠靠(0:投运;1:靠;2:靠) + */ + @TableField(value = "DEVFLAG") + private Integer devflag; + + /** + * 靠靠?靠3ds靠 + */ + @TableField(value = "DEV_SERIES") + private String devSeries; + + /** + * 靠靠,靠3ds靠 + */ + @TableField(value = "DEV_KEY") + private String devKey; + + /** + * IP靠 + */ + @TableField(value = "IP") + private String ip; + + /** + * 靠靠(0:靠靠;1:靠靠) + */ + @TableField(value = "DEVMODEL") + private Integer devmodel; + + /** + * 靠靠? + */ + @TableField(value = "CALLFLAG") + private Integer callflag; + + /** + * 靠靠(0:靠靠;1:靠靠;2:靠靠) + */ + @TableField(value = "DATATYPE") + private Integer datatype; +} \ No newline at end of file diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqDeviceDetail.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqDeviceDetail.java new file mode 100644 index 0000000..36df722 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqDeviceDetail.java @@ -0,0 +1,69 @@ +package com.njcn.product.terminal.device.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDate; + +/** + * CN_Gather + * + * @author cdf + * @date 2025/8/12 + */ +@TableName(value = "PQ_DEVICEDETAIL") +@Data +public class PqDeviceDetail { + + + @TableId(value = "DEV_INDEX") + private Long devIndex; + + @TableField(value = "Manufacturer") + private String manufacturer; + + @TableField(value = "CheckFlag") + private Long checkFlag; + + @TableField(value="ThisTimeCheck") + private LocalDate ThisTimeCheck; + + @TableField(value="NextTimeCheck") + private LocalDate NextTimeCheck; + + @TableField(value="DATAPLAN") + private Long dataplan; + + @TableField(value="NEWTRAFFIC") + private Long newtraffic; + + + @TableField(value = "electroplate") + private Integer electroplate = 0; + + @TableField(value = "ONTIME") + private Integer ontime; + @TableField(value = "contract") + private String contract; + + @TableField(value = "DEV_CATENA") + private String devCatnea; + + @TableField(value = "SIM") + private String sim; + + @TableField(value = "DEV_NO") + private String devNo; + + @TableField(value = "DEV_LOCATION") + private String devLocation; + + @TableField(value = "IS_ALARM") + private Integer isAlarm; + + + + + } diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqGdCompany.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqGdCompany.java new file mode 100644 index 0000000..6b8524e --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqGdCompany.java @@ -0,0 +1,26 @@ +package com.njcn.product.terminal.device.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * CN_Gather + * + * @author cdf + * @date 2025/8/9 + */ +@Data +@TableName(value = "PQ_GDINFORMATION") +public class PqGdCompany { + + @TableId(value = "GD_INDEX") + private Long gdIndex; + + @TableField(value="NAME") + private String name; + + @TableField(value="PROVINCE_INDEX") + private Long provinceIndex; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqLine.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqLine.java new file mode 100644 index 0000000..c701829 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqLine.java @@ -0,0 +1,132 @@ +package com.njcn.product.terminal.device.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:43【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +/** + * 靠靠? + */ +@Data +@NoArgsConstructor +@TableName(value = "PQ_LINE") +public class PqLine { + /** + * 靠靠 + */ + @TableId(value = "LINE_INDEX", type = IdType.INPUT) + private Integer lineIndex; + + /** + * 靠靠靠 + */ + @TableField(value = "GD_INDEX") + private Integer gdIndex; + + /** + * 靠靠? + */ + @TableField(value = "SUB_INDEX") + private Integer subIndex; + + /** + * 靠靠 + */ + @TableField(value = "SUBV_INDEX") + private Integer subvIndex; + + /** + * 靠靠 + */ + @TableField(value = "DEV_INDEX") + private Integer devIndex; + + /** + * 靠靠 + */ + @TableField(value = "\"NAME\"") + private String name; + + /** + * PT靠靠 + */ + @TableField(value = "PT1") + private Double pt1; + + /** + * PT靠靠 + */ + @TableField(value = "PT2") + private Double pt2; + + /** + * CT靠靠 + */ + @TableField(value = "CT1") + private Double ct1; + + /** + * CT靠靠 + */ + @TableField(value = "CT2") + private Double ct2; + + /** + * 靠靠 + */ + @TableField(value = "DEVCMP") + private Double devcmp; + + /** + * 靠靠 + */ + @TableField(value = "DLCMP") + private Double dlcmp; + + /** + * 靠靠 + */ + @TableField(value = "JZCMP") + private Double jzcmp; + + /** + * 靠靠 + */ + @TableField(value = "XYCMP") + private Double xycmp; + + /** + * 靠?靠靠靠靠靠靠? + */ + @TableField(value = "SUBV_NO") + private Integer subvNo; + + /** + * (靠PQS_Dictionary?靠靠Guid + */ + @TableField(value = "\"SCALE\"") + private String scale; + + /** + * 靠靠 + */ + @TableField(value = "SUBV_NAME") + private String subvName; + + @TableField(exist = false) + private String subName; + + @TableField(exist = false) + private String deptName; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqLinedetail.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqLinedetail.java new file mode 100644 index 0000000..f35456c --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqLinedetail.java @@ -0,0 +1,52 @@ +package com.njcn.product.terminal.device.pojo.po; + +/** + * @Author: cdf + * @CreateTime: 2025-06-26 + * @Description: + */ +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +@Data +@TableName("PQ_LINEDETAIL") +public class PqLinedetail { + + @TableId(value = "LINE_INDEX", type = IdType.INPUT) + private Integer lineIndex; + + private Integer gdIndex; + + private Integer subIndex; + + private String lineName; + + private Integer pttype; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date lastTime; + + private Integer tinterval; + + private String loadtype; + + private String businesstype; + + private String remark; + + private String monitorId; + + private Integer powerid; + + private String objname; + + @TableField(fill = FieldFill.INSERT) + private Integer statflag; + + private String lineGrade; + + private String powerSubstationName; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqSubstation.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqSubstation.java new file mode 100644 index 0000000..119b880 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqSubstation.java @@ -0,0 +1,45 @@ +package com.njcn.product.terminal.device.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:48【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +/** + * 靠靠靠 + */ +@Data +@NoArgsConstructor +@TableName(value = "PQ_SUBSTATION") +public class PqSubstation { + /** + * 靠靠? + */ + @TableId(value = "SUB_INDEX", type = IdType.INPUT) + private Integer subIndex; + + /** + * 靠靠靠 + */ + @TableField(value = "GD_INDEX") + private Integer gdIndex; + + /** + * 靠靠? + */ + @TableField(value = "\"NAME\"") + private String name; + + @TableField(value = "\"SCALE\"") + private String scale; +} \ No newline at end of file diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqsDeptsline.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqsDeptsline.java new file mode 100644 index 0000000..24795d7 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqsDeptsline.java @@ -0,0 +1,30 @@ +package com.njcn.product.terminal.device.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/06/19 下午 3:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@TableName(value = "PQS_DEPTSLINE") +public class PqsDeptsline { + /** + * 部门表Guid + */ + @TableField(value = "DEPTS_INDEX") + private String deptsIndex; + + @TableField(value = "LINE_INDEX") + private Integer lineIndex; + + @TableField(value = "SYSTYPE") + private String systype; +} \ No newline at end of file diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqsStationMap.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqsStationMap.java new file mode 100644 index 0000000..81a0fd5 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/pojo/po/PqsStationMap.java @@ -0,0 +1,57 @@ +package com.njcn.product.terminal.device.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * CN_Gather + * + * @author cdf + * @date 2025/8/11 + */ +@TableName(value="PQS_MAP") +@Data +public class PqsStationMap { + + + @TableId(value = "MAP_INDEX") + private String mapIndex; + + + @TableField(value = "SUB_INDEX") + private Long subIndex; + + + @TableField(value = "GD_INDEX") + private Long gdIndex; + + //经度 + + @TableField(value = "LONGITUDE") + private Float longItude; + + //纬度 + + @TableField(value = "LATITUDE") + private Float latItude; + + //数据状态 + + @TableField(value = "STATE") + private Long state; + + //用户ID + + @TableField(value = "USER_INDEX") + private String userIndex; + + //更新时间 + + @TableField(value = "UPDATETIME") + private Date updateTime; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/LedgerTreeService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/LedgerTreeService.java new file mode 100644 index 0000000..b360b48 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/LedgerTreeService.java @@ -0,0 +1,10 @@ +package com.njcn.product.terminal.device.service; + +import com.njcn.product.terminal.device.pojo.dto.OracleLedgerTreeDTO; + +import java.util.List; + +public interface LedgerTreeService { + + List getTree(); +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqDeviceService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqDeviceService.java new file mode 100644 index 0000000..48011df --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqDeviceService.java @@ -0,0 +1,27 @@ +package com.njcn.product.terminal.device.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.terminal.device.pojo.dto.DeviceDTO; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.terminal.device.pojo.dto.DeviceDeptDTO; +import com.njcn.product.terminal.device.pojo.po.PqDevice; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqDeviceService extends IService{ + + List queryListByIds(List lineIds); + + Page selectDeviceDTOPage(Page pqsEventdetailPage, String searchValue, List devIndexs); + + List selectDeviceDept(); +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqLineService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqLineService.java new file mode 100644 index 0000000..11a49be --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqLineService.java @@ -0,0 +1,25 @@ +package com.njcn.product.terminal.device.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.terminal.device.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.terminal.device.pojo.po.PqLine; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:43【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqLineService extends IService{ + + + List getBaseLineInfo(List ids); + + List getBaseLedger(@Param("ids") List ids, @Param("searchValue") String searchValue); +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqSubstationService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqSubstationService.java new file mode 100644 index 0000000..0722b88 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqSubstationService.java @@ -0,0 +1,21 @@ +package com.njcn.product.terminal.device.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.terminal.device.pojo.dto.SubstationDTO; +import com.njcn.product.terminal.device.pojo.po.PqSubstation; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:48【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqSubstationService extends IService{ + + List queryListByIds(List lineIds); +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqsDeptslineService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqsDeptslineService.java new file mode 100644 index 0000000..8ca0593 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/PqsDeptslineService.java @@ -0,0 +1,17 @@ +package com.njcn.product.terminal.device.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.terminal.device.pojo.po.PqsDeptsline; + +/** + * + * Description: + * Date: 2025/06/19 下午 3:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsDeptslineService extends IService{ + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/LedgerTreeServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/LedgerTreeServiceImpl.java new file mode 100644 index 0000000..f5fe5d4 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/LedgerTreeServiceImpl.java @@ -0,0 +1,85 @@ +package com.njcn.product.terminal.device.service.impl; + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.njcn.product.terminal.device.mapper.PqDeviceMapper; +import com.njcn.product.terminal.device.mapper.PqGdCompanyMapper; +import com.njcn.product.terminal.device.mapper.PqLineMapper; +import com.njcn.product.terminal.device.mapper.PqSubstationMapper; +import com.njcn.product.terminal.device.pojo.dto.OracleLedgerTreeDTO; +import com.njcn.product.terminal.device.pojo.po.PqDevice; +import com.njcn.product.terminal.device.pojo.po.PqGdCompany; +import com.njcn.product.terminal.device.pojo.po.PqLine; +import com.njcn.product.terminal.device.pojo.po.PqSubstation; +import com.njcn.product.terminal.device.service.LedgerTreeService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @Author: cdf + * @CreateTime: 2025-08-28 + * @Description: + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class LedgerTreeServiceImpl implements LedgerTreeService { + + private final PqLineMapper pqLineMapper; + + private final PqGdCompanyMapper pqGdCompanyMapper; + + private final PqDeviceMapper pqDeviceMapper; + + private final PqSubstationMapper substationMapper; + + + @Override + public List getTree() { + List pqLineList = pqLineMapper.selectList(null); + List lineDtoList = pqLineList.stream().map(it->{ + OracleLedgerTreeDTO oracleLedgerTreeDTO = new OracleLedgerTreeDTO(); + oracleLedgerTreeDTO.setId(it.getLineIndex()); + oracleLedgerTreeDTO.setName(it.getSubvName()+"_"+it.getName()); + oracleLedgerTreeDTO.setPid(it.getDevIndex()); + oracleLedgerTreeDTO.setLevel(4); + return oracleLedgerTreeDTO; + }).collect(Collectors.toList()); + List deviceList = pqDeviceMapper.selectList(new LambdaQueryWrapper().eq(PqDevice::getDevflag,0).eq(PqDevice::getDevmodel,1)); + List devDtoList = deviceList.stream().map(it->{ + OracleLedgerTreeDTO oracleLedgerTreeDTO = new OracleLedgerTreeDTO(); + oracleLedgerTreeDTO.setId(it.getDevIndex()); + oracleLedgerTreeDTO.setName(it.getName()); + oracleLedgerTreeDTO.setPid(it.getSubIndex()); + oracleLedgerTreeDTO.setLevel(3); + oracleLedgerTreeDTO.setChildren(lineDtoList.stream().filter(line->Objects.equals(it.getDevIndex(),line.getPid())).collect(Collectors.toList())); + return oracleLedgerTreeDTO; + }).collect(Collectors.toList()); + List substationList = substationMapper.selectList(null); + List stationDtoList = substationList.stream().map(it->{ + OracleLedgerTreeDTO oracleLedgerTreeDTO = new OracleLedgerTreeDTO(); + oracleLedgerTreeDTO.setId(it.getSubIndex()); + oracleLedgerTreeDTO.setName(it.getName()); + oracleLedgerTreeDTO.setPid(it.getGdIndex()); + oracleLedgerTreeDTO.setLevel(2); + oracleLedgerTreeDTO.setChildren(devDtoList.stream().filter(dev->Objects.equals(it.getSubIndex(),dev.getPid())).collect(Collectors.toList())); + return oracleLedgerTreeDTO; + }).collect(Collectors.toList()); + List pqGdCompanyList = pqGdCompanyMapper.selectList(null); + List gdDtoList = pqGdCompanyList.stream().map(it->{ + OracleLedgerTreeDTO oracleLedgerTreeDTO = new OracleLedgerTreeDTO(); + oracleLedgerTreeDTO.setId(it.getGdIndex().intValue()); + oracleLedgerTreeDTO.setName(it.getName()); + oracleLedgerTreeDTO.setPid(0); + oracleLedgerTreeDTO.setLevel(1); + oracleLedgerTreeDTO.setChildren(stationDtoList.stream().filter(sub->Objects.equals(it.getGdIndex().intValue(),sub.getPid())).collect(Collectors.toList())); + return oracleLedgerTreeDTO; + }).collect(Collectors.toList()); + return gdDtoList; + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqDeviceServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqDeviceServiceImpl.java new file mode 100644 index 0000000..a7da9fa --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqDeviceServiceImpl.java @@ -0,0 +1,40 @@ +package com.njcn.product.terminal.device.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import com.njcn.product.terminal.device.mapper.PqDeviceMapper; +import com.njcn.product.terminal.device.pojo.dto.DeviceDTO; +import com.njcn.product.terminal.device.pojo.dto.DeviceDeptDTO; +import com.njcn.product.terminal.device.pojo.po.PqDevice; +import com.njcn.product.terminal.device.service.PqDeviceService; +import org.springframework.stereotype.Service; + +import java.util.List; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqDeviceServiceImpl extends ServiceImpl implements PqDeviceService { + + @Override + public List queryListByIds(List lineIds) { + return this.baseMapper.queryListByIds(lineIds); + } + + @Override + public Page selectDeviceDTOPage(Page pqsEventdetailPage, String searchValue, List devIndexs) { + return this.baseMapper.selectDeviceDTOPage(pqsEventdetailPage,searchValue,devIndexs); + } + + @Override + public List selectDeviceDept() { + return this.baseMapper.selectDeviceDept(); + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqLineServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqLineServiceImpl.java new file mode 100644 index 0000000..0141bfb --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqLineServiceImpl.java @@ -0,0 +1,70 @@ +package com.njcn.product.terminal.device.service.impl; + +import cn.hutool.core.collection.CollUtil; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.terminal.device.mapper.PqLineMapper; +import com.njcn.product.terminal.device.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.terminal.device.pojo.po.PqLine; +import com.njcn.product.terminal.device.service.PqLineService; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.util.CollectionUtils; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:43【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqLineServiceImpl extends ServiceImpl implements PqLineService { + + + @Override + public List getBaseLineInfo(List ids){ + List ledgerBaseInfoDTOS = new ArrayList<>(); + + if(CollectionUtils.isEmpty(ids)){ + return ledgerBaseInfoDTOS; + } + if(ids.size()>1000){ + List> listIds = CollUtil.split(ids,1000); + for(List itemIds : listIds){ + List temp =this.baseMapper.getBaseLineInfo(itemIds); + ledgerBaseInfoDTOS.addAll(temp); + } + }else { + List temp =this.baseMapper.getBaseLineInfo(ids); + ledgerBaseInfoDTOS.addAll(temp); + } + return ledgerBaseInfoDTOS; + } + + @Override + public List getBaseLedger(List ids,String searchValue) { + List ledgerBaseInfoDTOS = new ArrayList<>(); + + if(CollectionUtils.isEmpty(ids)){ + return ledgerBaseInfoDTOS; + } + if(ids.size()>1000){ + List> listIds = CollUtil.split(ids,1000); + for(List itemIds : listIds){ + List temp =this.baseMapper.getBaseLedger(itemIds,searchValue); + ledgerBaseInfoDTOS.addAll(temp); + } + }else { + List temp =this.baseMapper.getBaseLedger(ids,searchValue); + ledgerBaseInfoDTOS.addAll(temp); + } + return ledgerBaseInfoDTOS; + }; + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqSubstationServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqSubstationServiceImpl.java new file mode 100644 index 0000000..c87bb4d --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqSubstationServiceImpl.java @@ -0,0 +1,26 @@ +package com.njcn.product.terminal.device.service.impl; + +import com.njcn.product.terminal.device.mapper.PqSubstationMapper; +import com.njcn.product.terminal.device.pojo.dto.SubstationDTO; +import com.njcn.product.terminal.device.pojo.po.PqSubstation; +import com.njcn.product.terminal.device.service.PqSubstationService; +import org.springframework.stereotype.Service; +import java.util.List; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:48【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqSubstationServiceImpl extends ServiceImpl implements PqSubstationService { + + @Override + public List queryListByIds(List lineIds) { + return this.baseMapper.queryListByIds(lineIds); + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqsDeptslineServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqsDeptslineServiceImpl.java new file mode 100644 index 0000000..41a94b0 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/device/service/impl/PqsDeptslineServiceImpl.java @@ -0,0 +1,20 @@ +package com.njcn.product.terminal.device.service.impl; + +import com.njcn.product.terminal.device.mapper.PqsDeptslineMapper; +import com.njcn.product.terminal.device.pojo.po.PqsDeptsline; +import com.njcn.product.terminal.device.service.PqsDeptslineService; +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +/** + * + * Description: + * Date: 2025/06/19 下午 3:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqsDeptslineServiceImpl extends ServiceImpl implements PqsDeptslineService { + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/event/controller/EventGateController.java b/cn-terminal/src/main/java/com/njcn/product/terminal/event/controller/EventGateController.java new file mode 100644 index 0000000..bd0bc1a --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/event/controller/EventGateController.java @@ -0,0 +1,45 @@ +package com.njcn.product.terminal.event.controller; + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.event.file.pojo.dto.WaveDataDTO; +import com.njcn.product.terminal.event.pojo.param.MonitorTerminalParam; +import com.njcn.product.terminal.event.service.EventGateService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @Author: cdf + * @CreateTime: 2025-09-03 + * @Description: + */ +@Api(tags = "暂降接收") +@RequestMapping("accept") +@RestController +@RequiredArgsConstructor +@Slf4j +public class EventGateController extends BaseController { + + private final EventGateService eventGateService; + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/getTransientAnalyseWave") + @ApiOperation("暂态事件波形分析") + public HttpResult getTransientAnalyseWave(@RequestBody @Validated MonitorTerminalParam param) { + String methodDescribe = getMethodDescribe("getTransientAnalyseWave"); + WaveDataDTO wave = eventGateService.getTransientAnalyseWave(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, wave, methodDescribe); + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/event/pojo/param/MonitorTerminalParam.java b/cn-terminal/src/main/java/com/njcn/product/terminal/event/pojo/param/MonitorTerminalParam.java new file mode 100644 index 0000000..3afe8e1 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/event/pojo/param/MonitorTerminalParam.java @@ -0,0 +1,23 @@ +package com.njcn.product.terminal.event.pojo.param; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * <监测点波形入参> + * + * @author wr + * @createTime: 2023-03-23 + */ +@Data +public class MonitorTerminalParam { + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @NotBlank(message = "事件id不能为空") + private String id; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/event/service/EventGateService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/event/service/EventGateService.java new file mode 100644 index 0000000..da73be6 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/event/service/EventGateService.java @@ -0,0 +1,15 @@ +package com.njcn.product.terminal.event.service; + +import com.njcn.event.file.pojo.dto.WaveDataDTO; +import com.njcn.product.terminal.event.pojo.param.MonitorTerminalParam; + +public interface EventGateService { + + + /** + * 功能描述: 暂态事件波形分析 + * @param param + * @return + */ + WaveDataDTO getTransientAnalyseWave(MonitorTerminalParam param); +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/event/service/impl/EventGateServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/event/service/impl/EventGateServiceImpl.java new file mode 100644 index 0000000..dbd9e88 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/event/service/impl/EventGateServiceImpl.java @@ -0,0 +1,80 @@ +package com.njcn.product.terminal.event.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.njcn.common.config.GeneralInfo; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.event.file.component.WaveFileComponent; +import com.njcn.event.file.pojo.dto.WaveDataDTO; +import com.njcn.event.file.pojo.enums.WaveFileResponseEnum; + +import com.njcn.product.terminal.event.pojo.param.MonitorTerminalParam; +import com.njcn.product.terminal.event.service.EventGateService; +import com.njcn.product.terminal.mysqlTerminal.mapper.LedgerScaleMapper; +import com.njcn.product.terminal.mysqlTerminal.mapper.RmpEventDetailMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerBaseInfo; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.RmpEventDetailPO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.InputStream; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @Author: cdf + * @CreateTime: 2025-06-30 + * @Description: + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class EventGateServiceImpl implements EventGateService { + + private final RmpEventDetailMapper rmpEventDetailMapper; + + private final WaveFileComponent waveFileComponent; + + private final LedgerScaleMapper ledgerScaleMapper; + + private final GeneralInfo generalInfo; + + @Override + public WaveDataDTO getTransientAnalyseWave(MonitorTerminalParam param) { + WaveDataDTO waveDataDTO; + //获取暂降事件 + RmpEventDetailPO eventDetail = rmpEventDetailMapper.selectById(param.getId()); + if(Objects.isNull(eventDetail)){ + throw new BusinessException(CommonResponseEnum.FAIL,"查询事件为空,请检查参数"); + } + String lineid = eventDetail.getLineId(); + LedgerBaseInfo pqLine = ledgerScaleMapper.getLedgerBaseInfo(Stream.of(lineid).collect(Collectors.toList())).get(0); + String waveName = eventDetail.getWavePath(); + String cfgPath, datPath; + if (StrUtil.isBlank(waveName)) { + throw new BusinessException(WaveFileResponseEnum.ANALYSE_WAVE_NOT_FOUND); + } + cfgPath = generalInfo.getBusinessWavePath()+ File.separator+pqLine.getIp()+"/"+waveName+".CFG"; + datPath = generalInfo.getBusinessWavePath()+ File.separator+pqLine.getIp()+"/"+waveName+".DAT"; + log.info("本地磁盘波形文件路径----" + cfgPath); + InputStream cfgStream = waveFileComponent.getFileInputStreamByFilePath(cfgPath); + InputStream datStream = waveFileComponent.getFileInputStreamByFilePath(datPath); + if (Objects.isNull(cfgStream) || Objects.isNull(datStream)) { + throw new BusinessException(WaveFileResponseEnum.ANALYSE_WAVE_NOT_FOUND); + } + waveDataDTO = waveFileComponent.getComtrade(cfgStream, datStream, 1); + + waveDataDTO = waveFileComponent.getValidData(waveDataDTO); + + waveDataDTO.setPtType(pqLine.getPtType()); + waveDataDTO.setPt(pqLine.getPt1()/ pqLine.getPt2()); + waveDataDTO.setCt(pqLine.getCt1()/ pqLine.getCt2()); + waveDataDTO.setMonitorName(pqLine.getLineName()); + return waveDataDTO; + + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/controller/LedgerTreeController.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/controller/LedgerTreeController.java new file mode 100644 index 0000000..f56b37f --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/controller/LedgerTreeController.java @@ -0,0 +1,72 @@ +package com.njcn.product.terminal.mysqlTerminal.controller; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.dto.SimpleDTO; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.terminal.device.service.LedgerTreeService; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerTreeDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.enums.StatisticsEnum; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.DeviceInfoParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.TerminalTree; +import com.njcn.product.terminal.mysqlTerminal.service.LineService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-08-28 + * @Description: + */ +@RestController +@RequiredArgsConstructor +@Api(tags = "台账树") +@RequestMapping("/terminalTree") +public class LedgerTreeController extends BaseController { + + private final LineService lineService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("获取台账树") + @GetMapping(value = "/tree") + public HttpResult tree() { + String methodDescribe = getMethodDescribe("tree"); + List treeDTOList = lineService.getTree(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, treeDTOList, methodDescribe); + } + + + /** + * 获取终端台账设备树 + * @author cdf + * @date 2021/7/19 + */ + + @ApiOperation("获取5层终端树") + @OperateInfo(info = LogEnum.BUSINESS_MEDIUM) + @PostMapping("getTerminalTreeForFive") + @ApiImplicitParam(name = "deviceInfoParam", value = "台账查询参数", required = true) + public HttpResult> getTerminalTreeForFive(@RequestBody @Validated DeviceInfoParam deviceInfoParam){ + String methodDescribe = getMethodDescribe("getTerminalTreeForFive"); + SimpleDTO simpleDTO = new SimpleDTO(); + simpleDTO.setCode(StatisticsEnum.POWER_NETWORK.getCode()); + deviceInfoParam.setStatisticalType(simpleDTO); + List tree = lineService.getTerminalTreeForFive(deviceInfoParam); + + return com.njcn.common.utils.HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, tree, methodDescribe); + } + + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/controller/LineController.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/controller/LineController.java new file mode 100644 index 0000000..31b5bbf --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/controller/LineController.java @@ -0,0 +1,46 @@ +package com.njcn.product.terminal.mysqlTerminal.controller; + + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.terminal.device.pojo.dto.OracleLedgerTreeDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerTreeDTO; +import com.njcn.product.terminal.mysqlTerminal.service.LineService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @author denghuajun + * @date 2022/2/23 + * 监测点相关 + */ +@Slf4j +@Api(tags = "监测点管理") +@RestController +@RequestMapping("/line") +@RequiredArgsConstructor +public class LineController extends BaseController { + + private final LineService lineService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("获取台账树") + @GetMapping(value = "/tree") + public HttpResult tree() { + String methodDescribe = getMethodDescribe("tree"); + List treeDTOList = lineService.getTree(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, treeDTOList, methodDescribe); + } + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/DeptLineMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/DeptLineMapper.java new file mode 100644 index 0000000..4fa8769 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/DeptLineMapper.java @@ -0,0 +1,28 @@ +package com.njcn.product.terminal.mysqlTerminal.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LineDevGetDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.DeptLine; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + +/** + *

+ * Mapper 接口 + *

+ * + * @author denghuajun + * @since 2022-01-12 18:04 + */ +public interface DeptLineMapper extends BaseMapper { + + + List getLineIdByDeptIds(@Param("deptIds") List deptIds,@Param("runFlag")List runFlag); + + List lineDevGet(@Param("list")List devType, @Param("type")Integer type, @Param("lineRunFlag") Integer lineRunFlag); + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/DeviceMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/DeviceMapper.java new file mode 100644 index 0000000..410db6c --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/DeviceMapper.java @@ -0,0 +1,24 @@ +package com.njcn.product.terminal.mysqlTerminal.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Device; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author cdf + * @since 2022-01-04 + */ +public interface DeviceMapper extends BaseMapper { + + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LedgerScaleMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LedgerScaleMapper.java new file mode 100644 index 0000000..2143bc0 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LedgerScaleMapper.java @@ -0,0 +1,19 @@ +package com.njcn.product.terminal.mysqlTerminal.mapper; + +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerBaseInfo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface LedgerScaleMapper { + + List getLedgerBaseInfo(@Param("lineIds") List lineIds); + + List getQueryLedger(@Param("lineIds") List lineIds,@Param("searchValue")String searchValue); + + + + List getBaseInfo(@Param("lineIds") List lineIds); + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LineDetailMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LineDetailMapper.java new file mode 100644 index 0000000..3fc1845 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LineDetailMapper.java @@ -0,0 +1,86 @@ +package com.njcn.product.terminal.mysqlTerminal.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.terminal.mysqlTerminal.pojo.po.LineDetail; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author cdf + * @since 2022-01-04 + */ +public interface LineDetailMapper extends BaseMapper { + +/* + */ +/** + * 查询装置下监测点号号是否已被占用 + * @param devIndex 装置索引 + * @param num 线路号 + * @return 监测点信息 + *//* + + List getLineDetail(@Param("devIndex") String devIndex, @Param("num") List num); + + */ +/** + * 实际设备下的监测点&&稳态系统和两个系统的监测点&&投运终端下的监测点 + * 获取指定条件的监测点信息 + * @param list 监测点集合 + * @return 结果 + *//* + + List getSpecifyLineDetail(@Param("list") List list); + + + */ +/** + * 获取指定的监测点信息以及电压等级(需要其他字段可在基础上扩充) + * @param lineIds 监测点集合 + * @return 结果 + *//* + + List getLineDetailInfo(@Param("lineIds") List lineIds); + + + @Select ("select count(1) from pq_line a where a.Level=4 and SUBSTRING_INDEX(SUBSTRING_INDEX(a.Pids, ',', 4),',',-1)=#{subIndex}") + Integer getDeviceCountBySubstation(@Param("subIndex")String subIndex); + + @Select ("select count(1) from pq_line a where a.Level=6 and SUBSTRING_INDEX(SUBSTRING_INDEX(a.Pids, ',', 4),',',-1)=#{subIndex}") + Integer getLineCountBySubstation(@Param("subIndex")String subIndex); + + + LineDevGetDTO getMonitorDetail(@Param("monitorId")String monitorId); + + void updateLineRunFlag(@Param("id")String lineId, @Param("runFlag")Integer status); + + void updateLineRunFlagBatch(@Param("lineIds") List lineIds, @Param("runFlag") Integer status); + + */ +/** + * 根据监测点信息获取监测点详情(关联终端和母线) + * 获取指定条件的监测点信息 + * @param Ids 监测点集合 + * @return 结果 + *//* + + List getLineDetailByIds(@Param("ids") List Ids); + + */ +/** + * 判断该新能源场站信息是否绑定了测点ID + *//* + + Integer checkExistsLineByNewStationId(@Param("newStationId") String newStationId); +*/ + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LineMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LineMapper.java new file mode 100644 index 0000000..70ccddf --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/LineMapper.java @@ -0,0 +1,114 @@ +package com.njcn.product.terminal.mysqlTerminal.mapper; + + +import cn.hutool.core.date.DateTime; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.dto.SimpleDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.DeviceType; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerBaseInfo; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LineDevGetDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.DeviceInfoParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Line; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.LineDataVO; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.LineDetailVO; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.TerminalShowVO; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + *

+ * Mapper 接口 + *

+ * + * @author cdf + * @since 2022-01-04 + */ +public interface LineMapper extends BaseMapper { + + + Page getStationList(@Param("page")Page page,@Param("lineIds")List lineIds,@Param("runFlag")Integer runFlag,@Param("searchValue") String searchValue); + + Page getDevList(@Param("page")Page page,@Param("lineIds")List lineIds,@Param("runFlag")Integer runFlag,@Param("searchValue") String searchValue); + + Page getLineList(@Param("page")Page page,@Param("lineIds")List lineIds,@Param("comFlag")Integer comFlag,@Param("searchValue") String searchValue); + + + LineDevGetDTO getMonitorDetail(@Param("monitorId")String monitorId); + + /** + * 根据监测点id,获取所有监测点 + * + * @param ids 监测点id + * @param deviceInfoParam 监测点查询条件 + * @return 监测点数据 + */ + List getLineByCondition(@Param("ids") List ids, @Param("deviceInfoParam") DeviceInfoParam deviceInfoParam); + + /** + * 查询母线信息 + * + * @param voltageIds 母线索引 + * @param scale 电压等级 + */ + List getVoltageByCondition(@Param("voltageIds") List voltageIds, @Param("scale") List scale); + + /** + * 查询终端信息 + * + * @param devIds 终端索引 + * @param deviceType 终端筛选条件 + * @param manufacturer 终端厂家 + */ + List getDeviceByCondition(@Param("devIds") List devIds, @Param("deviceType") DeviceType deviceType, @Param("manufacturer") List manufacturer); + + + List getSubByCondition(@Param("subIds") List subIds, @Param("scale") List scale); + + + /** + * 查询变电站id + * + * @param subIds 变电站索引集合 + * @param scale 电压等级 + */ + List getSubIdByScale(@Param("subIds") List subIds, @Param("scale") String scale); + + + /** + * 查询监测点id + * + * @param lineIds 监测点索引集合 + * @param loadType 干扰源类型 + */ + List getLineIdByLoadType(@Param("lineIds") List lineIds, @Param("loadType") String loadType); + + + /** + * 查询终端id + * + * @param deviceIds 终端索引集合 + * @param manufacturer 制造厂家 + */ + List getDeviceIdByManufacturer(@Param("deviceIds") List deviceIds, @Param("manufacturer") String manufacturer); + + + List getDeviceIdByPowerFlag(@Param("lineIds")List lineIds, @Param("powerFlag")Integer manufacturer); + + + /** + * 获取监测点信息 + * + * @param id 监测点id + * @return 结果 + */ + LineDetailVO getLineSubGdDetail(@Param("id") String id); + + List getLineDetail(@Param("ids") List ids); + + List queryMonitorByUser(@Param("userIds")List userIds); +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/OverlimitMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/OverlimitMapper.java new file mode 100644 index 0000000..a050dd3 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/OverlimitMapper.java @@ -0,0 +1,17 @@ +package com.njcn.product.terminal.mysqlTerminal.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Overlimit; + +/** + *

+ * Mapper 接口 + *

+ * + * @author cdf + * @since 2022-01-04 + */ +public interface OverlimitMapper extends BaseMapper { + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/RmpEventDetailMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/RmpEventDetailMapper.java new file mode 100644 index 0000000..32106f6 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/RmpEventDetailMapper.java @@ -0,0 +1,20 @@ +package com.njcn.product.terminal.mysqlTerminal.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.RmpEventDetailPO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 暂态事件明细 + * + * @author yzh + * @date 2022/10/12 + */ +@Mapper +public interface RmpEventDetailMapper extends BaseMapper { + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/TreeMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/TreeMapper.java new file mode 100644 index 0000000..bbd603b --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/TreeMapper.java @@ -0,0 +1,45 @@ +package com.njcn.product.terminal.mysqlTerminal.mapper; + +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.TerminalTree; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2022/2/28 + */ +public interface TreeMapper { + + /** + * 根据供电公司索引获取出省会的信息 + * @param gdIndexes 供电公司索引 + * @return 省会信息 + */ + List getProvinceList(@Param("gdIndex")List gdIndexes); + + /** + * 获取出供电公司的信息 + * @param gdIndexes 供电公司索引 + * @return 供电公司信息 + */ + List getGdList(@Param("gdIndex")List gdIndexes); + + /** + * 获取出变电站的信息 + * @param subIndexes 变电站索引 + * @return 变电站信息 + */ + List getSubList(@Param("subIndex")List subIndexes); + + /** + * 根据监测点索引获取监测点级五层树数据 + * @param lineIndexes 监测点索引 + * @return 监测点信息 + */ + List getLineList(@Param("lineIndex")List lineIndexes); + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/UserReportPOMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/UserReportPOMapper.java new file mode 100644 index 0000000..cae8451 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/UserReportPOMapper.java @@ -0,0 +1,20 @@ +package com.njcn.product.terminal.mysqlTerminal.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import com.njcn.product.terminal.mysqlTerminal.pojo.po.UserReportPO; +import org.apache.ibatis.annotations.Param; + +/** + * + * Description: + * Date: 2024/4/25 10:07【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface UserReportPOMapper extends BaseMapper { + +} \ No newline at end of file diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/VoltageMapper.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/VoltageMapper.java new file mode 100644 index 0000000..b6457d7 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/VoltageMapper.java @@ -0,0 +1,37 @@ +package com.njcn.product.terminal.mysqlTerminal.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.terminal.mysqlTerminal.pojo.po.LineDetail; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Voltage; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author cdf + * @since 2022-01-04 + */ +public interface VoltageMapper extends BaseMapper { + + /** + * 查询装置下母线号是否已被占用 + * @param devIndex 装置索引 + * @param num 线路号 + * @return 母线信息 + */ + List getVoltageByNum(@Param("devIndex") String devIndex, @Param("num") List num); + + + /** + * 通过母线id获取下层所有监测点详情 + * @author cdf + * @date 2023/5/24 + */ + List getLineDetailByBusBarId(@Param("busBarId")String busBarId); +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/DeptLineMapper.xml b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/DeptLineMapper.xml new file mode 100644 index 0000000..baa9482 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/DeptLineMapper.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/DeptMapper.xml b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/DeptMapper.xml new file mode 100644 index 0000000..66a1124 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/DeptMapper.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/LedgerScaleMapper.xml b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/LedgerScaleMapper.xml new file mode 100644 index 0000000..e38b190 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/LedgerScaleMapper.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/LineMapper.xml b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/LineMapper.xml new file mode 100644 index 0000000..3c4f6e0 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/LineMapper.xml @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/TreeMapper.xml b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/TreeMapper.xml new file mode 100644 index 0000000..8170d0c --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/mapper/mapping/TreeMapper.xml @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/constant/ValidMessage.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/constant/ValidMessage.java new file mode 100644 index 0000000..cc4a9d5 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/constant/ValidMessage.java @@ -0,0 +1,77 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.constant; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月17日 10:16 + */ +public interface ValidMessage { + + String MISS_PREFIX="字段不能为空,请检查"; + + String ID_NOT_BLANK = "id不能为空,请检查id参数"; + + String ID_FORMAT_ERROR = "id格式错误,请检查id参数"; + + String DICT_TYPE_ID_NOT_BLANK = "typeId不能为空,请检查typeId参数"; + + String DICT_TYPE_ID_FORMAT_ERROR = "typeId格式错误,请检查typeId参数"; + + String NAME_NOT_BLANK = "名称不能为空,请检查name参数"; + + String NAME_FORMAT_ERROR = "名称格式错误,存在特殊符号或超过20字符,请检查name参数"; + + String INDUSTRY_NOT_BLANK = "行业不能为空,请检查industry参数"; + String INDUSTRY_FORMAT_ERROR = "行业格式错误,请检查industry参数"; + String ADDR_NOT_BLANK = "所属区域不能为空,请检查addr参数"; + + String CODE_NOT_BLANK = "编号不能为空,请检查code参数"; + + String CODE_FORMAT_ERROR = "编号格式错误,请检查code参数"; + + String SORT_NOT_NULL = "排序不能为空,请检查sort参数"; + + String SORT_FORMAT_ERROR = "排序格式错误,请检查sort参数"; + + String OPEN_LEVEL_NOT_NULL = "开启等级不能为空,请检查openLevel参数"; + + String OPEN_LEVEL_FORMAT_ERROR = "开启等级格式错误,请检查openLevel参数"; + + String OPEN_DESCRIBE_NOT_NULL = "开启描述不能为空,请检查openDescribe参数"; + + String OPEN_DESCRIBE_FORMAT_ERROR = "开启描述格式错误,请检查openDescribe参数"; + + String AREA_NOT_BLANK = "行政区域不能为空,请检查area参数"; + + String AREA_FORMAT_ERROR = "行政区域格式错误,请检查area参数"; + + String PID_NOT_BLANK = "父节点不能为空,请检查pid参数"; + + String PID_FORMAT_ERROR = "父节点格式错误,请检查pid参数"; + + String COLOR_NOT_BLANK = "主题色不能为空,请检查color参数"; + + String COLOR_FORMAT_ERROR = "主题色格式错误,请检查color参数"; + + String LOGO_NOT_BLANK = "iconUrl不能为空,请检查iconUrl参数"; + + String FAVICON_NOT_BLANK = "faviconUrl不能为空,请检查faviconUrl参数"; + + String REMARK_NOT_BLANK = "描述不能为空,请检查remark参数"; + + String REMARK_FORMAT_ERROR = "描述格式错误,请检查remark参数"; + + String PARAM_FORMAT_ERROR = "参数值非法"; + + String IP_FORMAT_ERROR = "IP格式非法"; + + String DEVICE_VERSION_NOT_BLANK = "装置版本json文件不能为空,请检查deviceVersionFile参数"; + + String SEARCH_DATA_ERROR = "搜索值过长,请检查搜索参数"; + String SPECIAL_REGEX = "搜索值包含特殊字符"; + + String NAME_SPECIAL_REGEX = "包含特殊字符"; + + String DATA_TOO_LONG = "参数过长,请检查参数"; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeptGetBase.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeptGetBase.java new file mode 100644 index 0000000..896ae4f --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeptGetBase.java @@ -0,0 +1,41 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2023/5/10 + */ +@Data +public class DeptGetBase implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 单位索引 + */ + private String unitId; + + /** + * 单位名称 + */ + private String unitName; + + /** + * 部门层级 1.全国 2.省级 3.市级 4.县级 (具体根据单位层级调整) + * @author cdf + * @date 2023/6/26 + */ + private Integer deptLevel; + + /** + * 所有子级单位索引 + */ + private List unitChildrenList; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeptGetChildrenMoreDTO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeptGetChildrenMoreDTO.java new file mode 100644 index 0000000..3d0180b --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeptGetChildrenMoreDTO.java @@ -0,0 +1,24 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2023/4/24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DeptGetChildrenMoreDTO extends DeptGetBase { + + @ApiModelProperty(name = "lineBaseList",value = "主网监测点信息") + private List lineBaseList; + + @ApiModelProperty(name = "pwMonitorIds",value = "配网监测点信息") + private List pwMonitorIds; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeviceType.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeviceType.java new file mode 100644 index 0000000..00e0b1e --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/DeviceType.java @@ -0,0 +1,42 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.dto; + + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + + +/** + * 设备状态类 + * @author hongawen + * @version 1.0.0 + * @date 2022年02月11日 14:54 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DeviceType implements Serializable { + + /** + * 终端模型(0:虚拟设备;1:实际设备;2:离线设备;)默认是实际设备 + */ + private List devModel; + + /** + * 终端状态(0:投运;1:热备用;2:停运) + */ + private List runFlag; + + /** + * 数据类型(0:暂态系统;1:稳态系统;2:两个系统) + */ + private List dataType ; + + /** + * 通讯状态(0:中断;1:正常) + */ + private List comFlag ; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/GeneralDeviceDTO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/GeneralDeviceDTO.java new file mode 100644 index 0000000..a2f735a --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/GeneralDeviceDTO.java @@ -0,0 +1,67 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年09月07日 10:48 + * name对应统计名称:如 区域:南京市、苏州市;电压等级:10kV、220kV... + * index对应统计索引:如 区域:南京市索引、苏州市索引;电压等级:10kV索引、220kV索引... + * gdIndexes:供电公司索引集合 + * subIndexes:变电站索引集合 + * deviceIndexes:终端索引集合 + * voltageIndexes:母线索引集合 + * lineIndexes:监测点索引集合 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class GeneralDeviceDTO implements Serializable { + + /** + * name对应统计名称:如 区域:南京市、苏州市;电压等级:10kV、220kV... + */ + @ApiModelProperty(name = "name", value = "名称") + private String name; + + /** + * index对应统计索引:如 区域:南京市索引、苏州市索引;电压等级:10kV索引、220kV索引... + */ + private String index; + + /** + * gdIndexes:供电公司索引集合 + */ + private List gdIndexes = new ArrayList<>(); + + /** + * subIndexes:变电站索引集合 + */ + private List subIndexes = new ArrayList<>(); + + /** + * deviceIndexes:终端索引集合 + */ + private List deviceIndexes = new ArrayList<>(); + + /** + * voltageIndexes:母线索引集合 + */ + private List voltageIndexes = new ArrayList<>(); + + /** + * lineIndexes:监测点索引集合 + */ + private List lineIndexes = new ArrayList<>(); + @ApiModelProperty(name = "tail", value = "总数") + private Integer tail; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LedgerBaseInfo.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LedgerBaseInfo.java new file mode 100644 index 0000000..9826715 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LedgerBaseInfo.java @@ -0,0 +1,75 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDate; + +/** + * @Author: cdf + * @CreateTime: 2025-09-01 + * @Description: + */ +@Data +public class LedgerBaseInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + private String lineId; + + private String lineName; + + private Integer num; + + private String voltageLevel; + + private String objName; + + private String userNo; + + private Double ct1; + + private Double ct2; + + private Double pt1; + + private Double pt2; + + private Integer ptType; + + private Integer timeInterval; + + private String busBarId; + + private String busBarName; + + /** + * 0:中断;1:正常 + */ + private Integer comFlag; + + /** + * 0:投运;1:热备用;2:停运 + */ + private Integer runFlag; + + private String devId; + + private String devName; + + private String ip; + + private String manufacturer; + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd") + private LocalDate loginTime; + + private String stationId; + + private String stationName; + + private String gdId; + + private String gdName; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LedgerTreeDTO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LedgerTreeDTO.java new file mode 100644 index 0000000..d959070 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LedgerTreeDTO.java @@ -0,0 +1,27 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.dto; + +import com.njcn.product.terminal.device.pojo.dto.OracleLedgerTreeDTO; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-08-29 + * @Description: + */ +@Data +public class LedgerTreeDTO { + + + private String name; + + private String id; + + private String pid; + + private Integer level; + + private List children = new ArrayList<>(); +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LineDevGetDTO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LineDevGetDTO.java new file mode 100644 index 0000000..8404e31 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LineDevGetDTO.java @@ -0,0 +1,123 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.dto; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +/** + * pqs + * + * @author cdf + * @date 2023/5/10 + */ +@Data +public class LineDevGetDTO { + /** + * 部门索引 + */ + private String unitId; + + private String unitName; + + /** + * 监测点索引 + */ + private String pointId; + + private String pointName; + + /** + * 装置监测点索引集合 + */ + private List monitorIds; + + /** + * 监测点电压等级 + */ + private String voltageLevel; + + /** + * 监测点统计间隔 + */ + private Integer interval; + + /** + * 装置索引 + */ + private String devId; + + /** + * 0.主网 1.配网 + */ + private Integer type; + + /** + * 1.I类监测点 2.II类监测点 3.III类监测点 + */ + private Integer lineType; + + /** + * pq返回干扰源类型 pms主网返回监测对象类型,配网返回监测点类别 + */ + private String lineTag; + + /** + * 监测点对象类型 + */ + private String objType; + + /** + * 监测点对象 + */ + private String objId; + + /** + * 装置通讯状态 + */ + private Integer comFlag; + + /** + * 装置数据最新更新时间 + */ + private LocalDateTime updateTime; + + /** + * 是否上送国网是否是上送国网监测点,0-否 1-是 + */ + private Integer isUpToGrid; + + /** + * 0.未上送 1.已上送 2.取消上送 3.待重新上送(用于典型负荷) + */ + private Integer isUploadHead; + + /** + * 0.未上送 1.已上送 2.取消上送 3.待重新上送(用于主网监测点) + */ + private Integer monitorUploadStatus; + + /** + * oracle监测点id + */ + private Integer oracleLineId; + + /** + * 接线方式 0.星型 1.星三角 2.三角 + */ + private String wiringMethod; + + /** + * 监测点统计间隔(解决MySQL关键字问题) + */ + private Integer timeInterval; + + + public void setTimeInterval(Integer timeInterval) { + if(Objects.nonNull(timeInterval)) { + this.interval = timeInterval; + this.timeInterval = timeInterval; + } + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LineInfo.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LineInfo.java new file mode 100644 index 0000000..92eb88c --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/dto/LineInfo.java @@ -0,0 +1,30 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.dto; + +import lombok.Data; + +/** + * @Author: cdf + * @CreateTime: 2025-09-03 + * @Description: + */ +@Data +public class LineInfo { + + private String lineId; + + private String lineName; + + private String devId; + + private String ip; + + private Integer ct1; + + private Integer ct2; + + private Integer pt1; + + private Integer pt2; + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/LineBaseEnum.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/LineBaseEnum.java new file mode 100644 index 0000000..f7f443f --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/LineBaseEnum.java @@ -0,0 +1,63 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.enums; + +import lombok.Getter; + +import java.util.Arrays; + +/** + * pqs + * + * @author cdf + * @date 2022/1/4 + */ +@Getter +public enum LineBaseEnum { + + /** + * 系统拓扑各层级描述 + */ + PROJECT_LEVEL(0, "项目"), + PROVINCE_LEVEL(1, "省份"), + GD_LEVEL(2, "供电公司"), + SUB_LEVEL(3, "变电站"), + DEVICE_LEVEL(4, "终端"), + SUB_V_LEVEL(5, "母线"), + LINE_LEVEL(6, "监测点"), + USER_LEVEL(7,"用户"), + INVALID_LEVEL(-1, "非法拓扑等级"), + + + + /** + * 分布式光伏树层级 + */ + PV_UNIT_LEVEL(0,"单位"), + PV_SUB_LEVEL(1,"变电站"), + PV_SUB_AREA_LEVEL(2,"台区"), + + /** + * 电网标志 + */ + POWER_FLAG(0,"电网侧"), + POWER_FLAG_NOT(1,"非电网侧"), + + + + ; + + private final Integer code; + private final String message; + + LineBaseEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + public static LineBaseEnum getLineBaseEnumByCode(Integer code) { + return Arrays.stream(LineBaseEnum.values()) + .filter(lineBaseEnum -> lineBaseEnum.getCode().equals(code)) + .findAny() + .orElse(INVALID_LEVEL); + } + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/LineFlagEnum.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/LineFlagEnum.java new file mode 100644 index 0000000..5bf0f5c --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/LineFlagEnum.java @@ -0,0 +1,35 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.enums; + +import lombok.Getter; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年02月23日 15:24 + */ +@Getter +public enum LineFlagEnum { + + /** + * 区分监测点的类型标志 + */ + //非网公司 + LINE_MONITOR_NOT_NET_COMPANY(0), + //网公司 + LINE_MONITOR_NET_COMPANY(1), + //所有公司 + LINE_MONITOR_ALL(2), + //电网侧 + LINE_POWER_GRID(0), + //非电网侧 + LINE_POWER(1), + //所有 + LINE_POWER_ALL(2); + + private final int flag; + + LineFlagEnum(int flag) { + this.flag = flag; + } + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/PowerFlagEnum.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/PowerFlagEnum.java new file mode 100644 index 0000000..89ec135 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/PowerFlagEnum.java @@ -0,0 +1,52 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.enums; + +import lombok.Getter; + +import java.util.Arrays; + +/** + * pqs + * + * @author cdf + * @date 2022/1/4 + */ +@Getter +public enum PowerFlagEnum { + + /** + * 系统拓扑各层级描述 + */ + GRID_SIDE(0, "电网侧"), + NO_GRID_SIDE(1, "非电网侧"), + NEW_ENERGY(2, "电网侧(新能源)"), + NO_NEW_ENERGY(3, "非电网侧(新能源)"), + SEND_NETWORK(4, "上送国网"), + PCC(5, "PCC"), + + + VIRTUAL_DEVICE(0,"虚拟终端"), + REAL_DEVICE(1,"实际终端"), + OFFLINE_DEICE(2,"离线终端") + + + + + + ; + + private final Integer code; + private final String message; + + PowerFlagEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + public static PowerFlagEnum getPowerFlagEnumByCode(Integer code) { + return Arrays.stream(PowerFlagEnum.values()) + .filter(x -> x.getCode().equals(code)) + .findAny() + .orElse(GRID_SIDE); + } + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/RunFlagEnum.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/RunFlagEnum.java new file mode 100644 index 0000000..1a2dbd8 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/RunFlagEnum.java @@ -0,0 +1,34 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.enums; + +import lombok.Getter; + +import java.util.Arrays; + +@Getter +public enum RunFlagEnum { + + /** + * 运行状态枚举 + */ + RUNNING(0, "运行"), + OVERHAUL(1, "检修"), + OFF_LINE(2, "停运"), + DEBUG(3, "调试"), + QUIT(4, "退运"); + + private final Integer status; + private final String remark; + + RunFlagEnum(Integer status, String remark) { + this.status = status; + this.remark = remark; + } + + public static String getRunFlagRemarkByStatus(Integer status) { + RunFlagEnum runFlagEnum = Arrays.stream(RunFlagEnum.values()) + .filter(runFlagEnum1 -> runFlagEnum1.getStatus().equals(status)) + .findAny() + .orElse(RUNNING); + return runFlagEnum.getRemark(); + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/StatisticsEnum.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/StatisticsEnum.java new file mode 100644 index 0000000..db24b2a --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/enums/StatisticsEnum.java @@ -0,0 +1,49 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.enums; + + +import lombok.Getter; + +import java.util.Arrays; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年03月18日 13:27 + */ +@Getter +public enum StatisticsEnum { + + /** + * 统计类型字典枚举 + */ + POWER_NETWORK("网络拓扑", "Power_Network"), + VOLTAGE_LEVEL("电压等级", "Voltage_Level"), + LOAD_TYPE("干扰源类型", "Load_Type"), + MANUFACTURER("终端厂家", "Manufacturer"), + POWER_FLAG("监测点性质", "Power_Flag"), + REPORT_TYPE("上报类型", "Report_Type"); + + private final String name; + + private final String code; + + StatisticsEnum(String name, String code) { + this.name = name; + this.code = code; + } + + + /** + * 没有匹配到,则默认为网络拓扑 + * @param code 统计类型code + * @return 统计枚举实例 + */ + public static StatisticsEnum getStatisticsEnumByCode(String code) { + return Arrays.stream(StatisticsEnum.values()) + .filter(statisticsEnum -> statisticsEnum.getCode().equalsIgnoreCase(code)) + .findAny() + .orElse(POWER_NETWORK); + } + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/DeptGetLineParam.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/DeptGetLineParam.java new file mode 100644 index 0000000..bf4b6ba --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/DeptGetLineParam.java @@ -0,0 +1,51 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.param; + + +import com.njcn.product.terminal.mysqlTerminal.pojo.constant.ValidMessage; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; + + +/** + * pqs + * + * @author cdf + * @date 2023/4/24 + */ +@Data +public class DeptGetLineParam { + + @NotBlank(message = "部门id不可为空") + @ApiModelProperty(name = "deptId",value = "部门id") + private String deptId; + + @ApiModelProperty(name = "serverName",value = "系统类型 0.event-boot 1.harmonic-boot") + private String serverName; + + @ApiModelProperty(name = "systemType",value = "0.只返回主网的监测点信息; 1.只返回配网的监测点信息; null、2.返回主网配网两种监测点信息") + private Integer systemType; + + @ApiModelProperty(name = "monitorStateAll",value = "true.只返回在线监测点信息 false.返回全部监测点信息") + private Boolean monitorStateRunning=true; + + @ApiModelProperty(name = "isUpToGrid",value = "0.非送国网 1.需要送国网的") + + private Integer isUpToGrid; + /** + * 0-电网侧 + * 1-非电网侧 + */ + @ApiModelProperty("电网侧标识") + @Range(min = 0, max = 2, message = "电网侧标识" + ValidMessage.PARAM_FORMAT_ERROR) + private Integer powerFlag; + + /** + * 监测点运行状态(0:运行;1:检修;2:停运;3:调试;4:退运)pq使用 + */ + @ApiModelProperty("监测点运行状态") + @Range(min = 0, max = 2, message = "监测点运行状态" + ValidMessage.PARAM_FORMAT_ERROR) + private Integer lineRunFlag; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/DeviceInfoParam.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/DeviceInfoParam.java new file mode 100644 index 0000000..196d5f1 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/DeviceInfoParam.java @@ -0,0 +1,218 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.common.pojo.dto.SimpleDTO; + +import com.njcn.product.terminal.mysqlTerminal.pojo.constant.ValidMessage; +import com.njcn.product.terminal.mysqlTerminal.pojo.enums.LineFlagEnum; +import com.njcn.product.terminal.mysqlTerminal.pojo.enums.PowerFlagEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年02月23日 19:04 + */ +@Data +@ApiModel +@NoArgsConstructor +public class DeviceInfoParam implements Serializable { + + /** + * 统计类型 + */ + @ApiModelProperty(name = "statisticalType", value = "统计类型", required = true) + private SimpleDTO statisticalType; + + @ApiModelProperty(name = "deptIndex", value = "部门索引", required = true) + @NotBlank(message = "部门索引不可为空") + private String deptIndex; + + @ApiModelProperty(name = "serverName", value = "服务名称") + private String serverName; + + + @ApiModelProperty(name = "scale", value = "电压等级") + private List scale; + + + @ApiModelProperty(name = "manufacturer", value = "终端厂家") + private List manufacturer; + + + @ApiModelProperty(name = "loadType", value = "干扰源类型") + private List loadType; + + /** + * xy添加 + * 默认true + * true statFlag = 1 + * false statFlag = 0 or 1 + */ + @ApiModelProperty(name = "statFlag", value = "人为干预是否参与统计") + private Boolean statFlag; + + /** + * 0-非网公司 + * 1-网公司 + * 2-全部数据 + */ + @ApiModelProperty("网公司标识") + @Range(min = 0, max = 2, message = "网公司标识" + ValidMessage.PARAM_FORMAT_ERROR) + private Integer monitorFlag; + + /** + * 0-电网侧 + * 1-非电网侧 + */ + @ApiModelProperty("电网侧标识") + @Range(min = 0, max = 2, message = "电网侧标识" + ValidMessage.PARAM_FORMAT_ERROR) + private Integer powerFlag; + + /** + * 0-极重要 + * 1-重要 + * 2-普通 + * 3-不重要 + */ + @ApiModelProperty("监测点等级") + private String lineGrade; + + /** + * 通讯状态(0:中断;1:正常) + */ + @ApiModelProperty("通讯状态") + @Range(min = 0, max = 2, message = "通讯状态" + ValidMessage.PARAM_FORMAT_ERROR) + private Integer comFlagStatus; + + + /** + * 监测点运行状态(0:运行;1:检修;2:停运;3:调试;4:退运) + */ + @ApiModelProperty("监测点运行状态") + @Range(min = 0, max = 2, message = "监测点运行状态" + ValidMessage.PARAM_FORMAT_ERROR) + private Integer lineRunFlag; + + /** + * 默认全部监测点 + * + * @param deptIndex 部门索引 + * @param serverName 服务名 + */ + public DeviceInfoParam(String deptIndex, String serverName) { + this.deptIndex = deptIndex; + this.serverName = serverName; + monitorFlag = LineFlagEnum.LINE_MONITOR_ALL.getFlag(); + powerFlag = LineFlagEnum.LINE_POWER_ALL.getFlag(); + } + + + /** + * 默认全部监测点 + * + * @param deptIndex 部门索引 + * @param serverName 服务名 + */ + public DeviceInfoParam(SimpleDTO statisticalType, String deptIndex, String serverName, List scale, List manufacturer, List loadType) { + this.statisticalType = statisticalType; + this.deptIndex = deptIndex; + this.serverName = serverName; + this.scale = scale; + this.manufacturer = manufacturer; + this.loadType = loadType; + monitorFlag = LineFlagEnum.LINE_MONITOR_ALL.getFlag(); + powerFlag = LineFlagEnum.LINE_POWER_ALL.getFlag(); + } + + /** + * 自定义上报方式、电网侧方式的统计 + */ + public DeviceInfoParam(SimpleDTO statisticalType, String deptIndex, String serverName, List scale, List manufacturer, List loadType, int monitorFlag, int powerFlag) { + this.statisticalType = statisticalType; + this.deptIndex = deptIndex; + this.serverName = serverName; + this.scale = scale; + this.manufacturer = manufacturer; + this.loadType = loadType; + this.monitorFlag = monitorFlag; + this.powerFlag = powerFlag; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class BusinessParam extends DeviceInfoParam { + + @ApiModelProperty("开始时间") + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String searchBeginTime; + + @ApiModelProperty("结束时间") + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String searchEndTime; + + @ApiModelProperty("时间范围标志 0.查询展示天 1.查询展示月") + @Deprecated + private Integer timeFlag; + + @ApiModelProperty("统计类型 1.年 2.季 3.月 4.周 5.天") + private String reportFlag; + + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class CompareBusinessParam extends BusinessParam { + + @ApiModelProperty("比较开始时间") + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String periodBeginTime; + + @ApiModelProperty("比较结束时间") + @Pattern(regexp = PatternRegex.TIME_FORMAT, message = "时间格式错误") + private String periodEndTime; + + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class CompareLimitParam extends BusinessParam { + + @ApiModelProperty("查询条数") + @NotNull(message = " 查询条数查询条数不能为空") + private Integer limit; + + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class GridDiagram extends BusinessParam { + + @ApiModelProperty("查询总数监测点") + private List coutList; + + @ApiModelProperty("查询告警监测点") + private List alarmList; + + @ApiModelProperty("是否是冀北电网一张图树 0:否 1:是") + private Integer type = 0; + } + + public Boolean isUserLedger() { + if (Objects.isNull(this.powerFlag) || !PowerFlagEnum.GRID_SIDE.getCode().equals(this.powerFlag)) { + return true; + } + return false; + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/LargeScreenCountParam.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/LargeScreenCountParam.java new file mode 100644 index 0000000..8d7b10d --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/LargeScreenCountParam.java @@ -0,0 +1,41 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.param; + +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @Author: cdf + * @CreateTime: 2025-08-29 + * @Description: + */ +@Data +public class LargeScreenCountParam extends BaseParam { + + @ApiModelProperty(name="deptId",value="部门id") + private String deptId; + + @ApiModelProperty(name="lineId",value="监测点id") + private String lineId; + + @ApiModelProperty(name="runFlag",value="运行状态") + private Integer runFlag; + + @ApiModelProperty(name="runFlag",value="通讯状态") + private Integer comFlag; + + @ApiModelProperty(name="runFlag",value="暂降事件关联分析聚合id") + private String eventAssId; + + private String eventType; + + private Float eventValueMin; + + private Float eventValueMax; + + private Float eventDurationMin; + + private Float eventDurationMax; + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/UserReportParam.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/UserReportParam.java new file mode 100644 index 0000000..ce562aa --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/param/UserReportParam.java @@ -0,0 +1,215 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.param; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.product.terminal.mysqlTerminal.pojo.constant.ValidMessage; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.UserReportProjectPO; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.UserReportSensitivePO; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.UserReportSubstationPO; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Pattern; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +/** + * Description: + * Date: 2024/4/25 10:07【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserReportParam { + + private String id; + + /** + * 填报人 + */ + @ApiModelProperty(value = "填报人") + private String reporter; + + /** + * 填报日期 + */ + @ApiModelProperty(value = "填报日期") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private LocalDate reportDate; + + /** + * 填报部门 + */ + @ApiModelProperty(value = "填报部门") + private String orgId; + + /** + * 填报部门 + */ + @ApiModelProperty(value = "填报部门名称") + private String orgName; + + /** + * 工程预期投产日期 + */ + @ApiModelProperty(value = "工程预期投产日期") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private LocalDate expectedProductionDate; + + /** + * 用户性质 + */ + @ApiModelProperty(value = "用户性质") + private Integer userType; + + /** + * 所属地市 + */ + @ApiModelProperty(value = "所属地市") + private String city; + + /** + * 归口管理部门 + */ + @ApiModelProperty(value = "归口管理部门") + @Pattern(regexp = PatternRegex.DES32_REGEX, message = ValidMessage.DATA_TOO_LONG) + private String responsibleDepartment; + + /** + * 用户状态 + */ + @ApiModelProperty(value = "用户状态") + private Integer userStatus; + + /** + * 变电站 + */ + @ApiModelProperty(value = "变电站") + @Pattern(regexp = PatternRegex.DES32_REGEX, message = ValidMessage.DATA_TOO_LONG) + private String substation; + + /** + * 变电站 + */ + @ApiModelProperty(value = "变电站id") + @Pattern(regexp = PatternRegex.DES32_REGEX, message = ValidMessage.DATA_TOO_LONG) + private String stationId; + + /** + * 电压等级 + */ + @ApiModelProperty(value = "电压等级") + private String voltageLevel; + + /** + * 工程名称 + */ + @ApiModelProperty(value = "工程名称") + @Pattern(regexp = PatternRegex.DES32_REGEX, message = ValidMessage.DATA_TOO_LONG) + private String projectName; + + /** + * 预测评估单位 + */ + @ApiModelProperty(value = "预测评估单位") + private String evaluationDept; + + @ApiModelProperty(value = "经度") + private BigDecimal longitude; + + @ApiModelProperty(value = "纬度") + private BigDecimal latitude; + + @ApiModelProperty(value = "额定容量") + private Double ratePower; + + /** + * 预测评估结论 + */ + @ApiModelProperty(value = "预测评估结论") + @Pattern(regexp = PatternRegex.DES400_REGEX, message = ValidMessage.DATA_TOO_LONG) + private String evaluationConclusion; + + @ApiModelProperty("发起人自选审批人 Map") + private Map> startUserSelectAssignees; + + + @ApiModelProperty(value = "保存1,提交审批2") + private String saveOrCheckflag; + + @ApiModelProperty(value = "数据来源类型 0:正常审核流程 1:批量导入") + private Integer dataType; + + private UserReportProjectPO userReportProjectPO; + + private UserReportSensitivePO userReportSensitivePO; + + private UserReportSubstationPO userReportSubstationPO; + + + /** + * 流程实例的编号 + */ + + @ApiModelProperty(value = "流程实例的编号") + private String processInstanceId; + + @ApiModelProperty(value = "历史流程实例的编号") + private String historyInstanceId; + + /** + * 终端id + */ + @ApiModelProperty(value = "终端id") + private String devId; + + /** + * 监测点id + */ + @ApiModelProperty(value = "监测点id") + private String lineId; + + @Data + @EqualsAndHashCode(callSuper = true) + public static class UserReportUpdate extends UserReportParam { + + @ApiModelProperty("id") + private String id; + + } + + /** + * 分页查询实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class UserReportQueryParam extends BaseParam { + + @ApiModelProperty(value = "所属区域") + private String city; + + @ApiModelProperty(value = "工程名称") + private String projectName; + + @ApiModelProperty(value = "填报部门") + private String orgId; + + @ApiModelProperty(value = "数据来源类型 0:正常审核流程 1:批量导入") + private Integer dataType; + + @ApiModelProperty(value = "审核状态") + private Integer status; + + } + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/DeptLine.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/DeptLine.java new file mode 100644 index 0000000..b56e3c9 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/DeptLine.java @@ -0,0 +1,31 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@TableName("pq_dept_line") +public class DeptLine { + + private static final long serialVersionUID = 1L; + + /** + * 部门Id + */ + private String id; + + /** + * 监测点Id + */ + private String lineId; + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Device.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Device.java new file mode 100644 index 0000000..49b23f0 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Device.java @@ -0,0 +1,165 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@TableName("pq_device") +public class Device implements Serializable{ + + private static final long serialVersionUID = 1L; + + /** + * 装置序号 + */ + @TableId + private String id; + + /** + * 装置模型(0:虚拟设备;1:实际设备;2:离线设备;)默认是实际设备 + */ + private Integer devModel; + + /** + * 数据类型(0:暂态系统;1:稳态系统;2:两个系统) + */ + private Integer devDataType; + + /** + * 终端运行状态(0:运行;1:检修;2:停运;3:调试;4:退运) + */ + private Integer runFlag; + + /** + * 通讯状态(0:中断;1:正常) + */ + private Integer comFlag; + + /** + * 设备制造商,字典表 + */ + private String manufacturer; + + /** + * 定检状态(0:未检 1:已检) + */ + private Integer checkFlag; + + /** + * 前置类型(MMS、CLD)字典表 + */ + private String frontType; + + /** + * 终端型号(570、580……)字典表 + */ + private String devType; + + /** + * 网络参数 + */ + private String ip; + + /** + * 召唤标志(0:周期触发;1:变为触发) + */ + private Integer callFlag; + + /** + * 端口 + */ + private Integer port; + + /** + * 装置识别码(3ds加密) + */ + private String series; + + /** + * 装置秘钥(3ds加密) + */ + private String devKey; + + /** + * 前置序号Id,前置表 + */ + private String nodeId; + + /** + * 投运时间 + */ + private LocalDate loginTime; + + /** + * 数据更新时间 + */ + private LocalDateTime updateTime; + + /** + * 本次定检时间,默认等于投运时间 + */ + private LocalDate thisTimeCheck; + + /** + * 下次定检时间,默认为投运时间后推3年,假如时间小于3个月则为待检 + */ + private LocalDate nextTimeCheck; + + /** + * 电度功能 0 关闭 1开启 + */ + private Integer electroplate; + + /** + * 对时功能 0 关闭, 1开启 + */ + private Integer onTime; + + /** + * 合同号 + */ + private String contract; + + /** + * 设备sim卡号 + */ + private String sim; + + + /** + * 装置系列 + */ + private String devSeries; + + + /** + * 监测装置安装位置 + */ + private String devLocation; + + + /** + * 监测厂家设备编号 + */ + private String devNo; + + + /** + * 告警功能 0:关闭 null、1:开启 + */ + private Integer isAlarm; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Line.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Line.java new file mode 100644 index 0000000..e1e927d --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Line.java @@ -0,0 +1,66 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("pq_line") +public class Line extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 监测点Id + */ + private String id; + + /** + * 父节点(0为根节点) + */ + private String pid; + + /** + * 上层所有节点 + */ + private String pids; + + /** + * 名称 + */ + private String name; + + /** + * 等级:0-项目名称;1- 工程名称;2-单位;3-部门;4-终端;5-母线;6-监测点 + */ + private Integer level; + + /** + * 排序(默认为0,有特殊排序需要时候人为输入) + */ + private Integer sort; + + /** + * 备注 + */ + private String remark; + + /** + * 状态 0-删除;1-正常;默认正常 + */ + private Integer state; + + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/LineDetail.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/LineDetail.java new file mode 100644 index 0000000..4f282e6 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/LineDetail.java @@ -0,0 +1,219 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@TableName("pq_line_detail") +public class LineDetail { + + private static final long serialVersionUID = 1L; + + /** + * 监测点序号 + */ + private String id; + + + @TableField(exist = false) + private String monitorName; + + /** + * 线路号(在同一台设备中的监测点号) + */ + private Integer num; + + /** + * PT一次变比 + */ + private Float pt1; + + /** + * PT二次变比 + */ + private Float pt2; + + /** + * CT一次变比 + */ + private Float ct1; + + /** + * CT二次变比 + */ + private Float ct2; + + /** + * 设备容量 + */ + private Float devCapacity; + + /** + * 短路容量 + */ + private Float shortCapacity; + + /** + * 基准容量 + */ + private Float standardCapacity; + + /** + * 协议容量 + */ + private Float dealCapacity; + + /** + * 接线类型(0:星型接法;1:三角型接法;2:开口三角型接法) + */ + private Integer ptType; + + /** + * 测量间隔(1-10分钟) + */ + private Integer timeInterval; + + /** + * 干扰源类型,字典表 + */ + private String loadType; + + /** + * 行业类型,字典表 + */ + private String businessType; + + /** + * 网公司谐波监测平台标志(0-否;1-是),默认否 + */ + private Integer monitorFlag; + + /** + * 电网标志(0-电网侧;1-非电网侧) + */ + private Integer powerFlag; + + /** + * 国网谐波监测平台监测点号 + */ + private String monitorId; + + /** + * 监测点对象名称 + */ + @Deprecated + private String objName; + + /** + * 监测点对象id + */ + private String objId; + + /** + * 监测对象大类 + */ + private String bigObjType; + + /** + * 监测对象小类 + */ + private String smallObjType; + + /** + * 人为干预 0 不参与统计 1 参与统计 + */ + private Integer statFlag; + + /** + * 关联字典的终端等级 + */ + private String lineGrade; + + /** + * 备注 + */ + private String remark; + + + + /** + * 电网侧变电站 + */ + private String powerSubstationName; + /** + * 分类等级 + */ + private String calssificationGrade; + + + /** + * 上级电站 + */ + @Deprecated + private String superiorsSubstation; + + /** + * 挂接线路 + */ + @Deprecated + private String hangLine; + + /** + * 监测点拥有者 + */ + @Deprecated + private String owner; + + /** + * 拥有者职务 + */ + @Deprecated + private String ownerDuty; + + /** + * 拥有者联系方式 + */ + @Deprecated + private String ownerTel; + + /** + * 接线图 + */ + private String wiringDiagram; + /** + * 监测点接线相别(0,单相,1,三相,默认三相) + */ + private Integer ptPhaseType; + + /** + * 监测点实际安装位置 + */ + private String actualArea; + + /** + * 监测点运行状态(0:运行;1:检修;2:停运;3:调试;4:退运) + */ + private Integer runFlag; + + /** + * 新能源场站信息ID + */ + @Deprecated + private String newStationId; + + /** + * 通讯状态 + */ + @TableField(exist = false) + private Integer comFlag; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Overlimit.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Overlimit.java new file mode 100644 index 0000000..9dfcbed --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Overlimit.java @@ -0,0 +1,876 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@TableName("pq_overlimit") +public class Overlimit implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 监测点序号 + */ + private String id; + + /** + * 频率限值 + */ + private Float freqDev; + + /** + * 电压波动 + */ + private Float voltageFluctuation; + + /** + * 电压上偏差限值 + */ + private Float voltageDev; + + /** + * 电压下偏差限值 + */ + private Float uvoltageDev; + + /** + * 三相电压不平衡度限值 + */ + private Float ubalance; + + /** + * 短时电压不平衡度限值 + */ + private Float shortUbalance; + + /** + * 闪变限值 + */ + private Float flicker; + + /** + * 电压总谐波畸变率限值 + */ + private Float uaberrance; + + /** + * 负序电流限值 + */ + private Float iNeg; + + /** + * 2次谐波电压限值 + */ + @TableField("uharm_2") + private Float uharm2; + + /** + * 3次谐波电压限值 + */ + @TableField("uharm_3") + private Float uharm3; + + /** + * 4次谐波电压限值 + */ + @TableField("uharm_4") + private Float uharm4; + + /** + * 5次谐波电压限值 + */ + @TableField("uharm_5") + private Float uharm5; + + /** + * 6次谐波电压限值 + */ + @TableField("uharm_6") + private Float uharm6; + + /** + * 7次谐波电压限值 + */ + @TableField("uharm_7") + private Float uharm7; + + /** + * 8次谐波电压限值 + */ + @TableField("uharm_8") + private Float uharm8; + + /** + * 9次谐波电压限值 + */ + @TableField("uharm_9") + private Float uharm9; + + /** + * 10次谐波电压限值 + */ + @TableField("uharm_10") + private Float uharm10; + + /** + * 11次谐波电压限值 + */ + @TableField("uharm_11") + private Float uharm11; + + /** + * 12次谐波电压限值 + */ + @TableField("uharm_12") + private Float uharm12; + + /** + * 13次谐波电压限值 + */ + @TableField("uharm_13") + private Float uharm13; + + /** + * 14次谐波电压限值 + */ + @TableField("uharm_14") + private Float uharm14; + + /** + * 15次谐波电压限值 + */ + @TableField("uharm_15") + private Float uharm15; + + /** + * 16次谐波电压限值 + */ + @TableField("uharm_16") + private Float uharm16; + + /** + * 17次谐波电压限值 + */ + @TableField("uharm_17") + private Float uharm17; + + /** + * 18次谐波电压限值 + */ + @TableField("uharm_18") + private Float uharm18; + + /** + * 19次谐波电压限值 + */ + @TableField("uharm_19") + private Float uharm19; + + /** + * 20次谐波电压限值 + */ + @TableField("uharm_20") + private Float uharm20; + + /** + * 21次谐波电压限值 + */ + @TableField("uharm_21") + private Float uharm21; + + /** + * 22次谐波电压限值 + */ + @TableField("uharm_22") + private Float uharm22; + + /** + * 23次谐波电压限值 + */ + @TableField("uharm_23") + private Float uharm23; + + /** + * 24次谐波电压限值 + */ + @TableField("uharm_24") + private Float uharm24; + + /** + * 25次谐波电压限值 + */ + @TableField("uharm_25") + private Float uharm25; + + /** + * 2次谐波电压限值 + */ + @TableField("uharm_26") + private Float uharm26; + + /** + * 3次谐波电压限值 + */ + @TableField("uharm_27") + private Float uharm27; + + /** + * 4次谐波电压限值 + */ + @TableField("uharm_28") + private Float uharm28; + + /** + * 5次谐波电压限值 + */ + @TableField("uharm_29") + private Float uharm29; + + /** + * 6次谐波电压限值 + */ + @TableField("uharm_30") + private Float uharm30; + + /** + * 7次谐波电压限值 + */ + @TableField("uharm_31") + private Float uharm31; + + /** + * 8次谐波电压限值 + */ + @TableField("uharm_32") + private Float uharm32; + + /** + * 9次谐波电压限值 + */ + @TableField("uharm_33") + private Float uharm33; + + /** + * 10次谐波电压限值 + */ + @TableField("uharm_34") + private Float uharm34; + + /** + * 11次谐波电压限值 + */ + @TableField("uharm_35") + private Float uharm35; + + /** + * 12次谐波电压限值 + */ + @TableField("uharm_36") + private Float uharm36; + + /** + * 13次谐波电压限值 + */ + @TableField("uharm_37") + private Float uharm37; + + /** + * 14次谐波电压限值 + */ + @TableField("uharm_38") + private Float uharm38; + + /** + * 15次谐波电压限值 + */ + @TableField("uharm_39") + private Float uharm39; + + /** + * 16次谐波电压限值 + */ + @TableField("uharm_40") + private Float uharm40; + + /** + * 17次谐波电压限值 + */ + @TableField("uharm_41") + private Float uharm41; + + /** + * 18次谐波电压限值 + */ + @TableField("uharm_42") + private Float uharm42; + + /** + * 19次谐波电压限值 + */ + @TableField("uharm_43") + private Float uharm43; + + /** + * 20次谐波电压限值 + */ + @TableField("uharm_44") + private Float uharm44; + + /** + * 21次谐波电压限值 + */ + @TableField("uharm_45") + private Float uharm45; + + /** + * 22次谐波电压限值 + */ + @TableField("uharm_46") + private Float uharm46; + + /** + * 23次谐波电压限值 + */ + @TableField("uharm_47") + private Float uharm47; + + /** + * 24次谐波电压限值 + */ + @TableField("uharm_48") + private Float uharm48; + + /** + * 25次谐波电压限值 + */ + @TableField("uharm_49") + private Float uharm49; + + /** + * 50次谐波电压限值 + */ + @TableField("uharm_50") + private Float uharm50; + + + + /** + * 2次谐波电流限值 + */ + @TableField("iharm_2") + private Float iharm2; + + /** + * 3次谐波电流限值 + */ + @TableField("iharm_3") + private Float iharm3; + + /** + * 4次谐波电流限值 + */ + @TableField("iharm_4") + private Float iharm4; + + /** + * 5次谐波电流限值 + */ + @TableField("iharm_5") + private Float iharm5; + + /** + * 6次谐波电流限值 + */ + @TableField("iharm_6") + private Float iharm6; + + /** + * 7次谐波电流限值 + */ + @TableField("iharm_7") + private Float iharm7; + + /** + * 8次谐波电流限值 + */ + @TableField("iharm_8") + private Float iharm8; + + /** + * 9次谐波电流限值 + */ + @TableField("iharm_9") + private Float iharm9; + + /** + * 10次谐波电流限值 + */ + @TableField("iharm_10") + private Float iharm10; + + /** + * 11次谐波电流限值 + */ + @TableField("iharm_11") + private Float iharm11; + + /** + * 12次谐波电流限值 + */ + @TableField("iharm_12") + private Float iharm12; + + /** + * 13次谐波电流限值 + */ + @TableField("iharm_13") + private Float iharm13; + + /** + * 14次谐波电流限值 + */ + @TableField("iharm_14") + private Float iharm14; + + /** + * 15次谐波电流限值 + */ + @TableField("iharm_15") + private Float iharm15; + + /** + * 16次谐波电流限值 + */ + @TableField("iharm_16") + private Float iharm16; + + /** + * 17次谐波电流限值 + */ + @TableField("iharm_17") + private Float iharm17; + + /** + * 18次谐波电流限值 + */ + @TableField("iharm_18") + private Float iharm18; + + /** + * 19次谐波电流限值 + */ + @TableField("iharm_19") + private Float iharm19; + + /** + * 20次谐波电流限值 + */ + @TableField("iharm_20") + private Float iharm20; + + /** + * 21次谐波电流限值 + */ + @TableField("iharm_21") + private Float iharm21; + + /** + * 22次谐波电流限值 + */ + @TableField("iharm_22") + private Float iharm22; + + /** + * 23次谐波电流限值 + */ + @TableField("iharm_23") + private Float iharm23; + + /** + * 24次谐波电流限值 + */ + @TableField("iharm_24") + private Float iharm24; + + /** + * 25次谐波电流限值 + */ + @TableField("iharm_25") + private Float iharm25; + + /** + * 2次谐波电压限值 + */ + @TableField("iharm_26") + private Float iharm26; + + /** + * 3次谐波电压限值 + */ + @TableField("iharm_27") + private Float iharm27; + + /** + * 4次谐波电压限值 + */ + @TableField("iharm_28") + private Float iharm28; + + /** + * 5次谐波电压限值 + */ + @TableField("iharm_29") + private Float iharm29; + + /** + * 6次谐波电压限值 + */ + @TableField("iharm_30") + private Float iharm30; + + /** + * 7次谐波电压限值 + */ + @TableField("iharm_31") + private Float iharm31; + + /** + * 8次谐波电压限值 + */ + @TableField("iharm_32") + private Float iharm32; + + /** + * 9次谐波电压限值 + */ + @TableField("iharm_33") + private Float iharm33; + + /** + * 10次谐波电压限值 + */ + @TableField("iharm_34") + private Float iharm34; + + /** + * 11次谐波电压限值 + */ + @TableField("iharm_35") + private Float iharm35; + + /** + * 12次谐波电压限值 + */ + @TableField("iharm_36") + private Float iharm36; + + /** + * 13次谐波电压限值 + */ + @TableField("iharm_37") + private Float iharm37; + + /** + * 14次谐波电压限值 + */ + @TableField("iharm_38") + private Float iharm38; + + /** + * 15次谐波电压限值 + */ + @TableField("iharm_39") + private Float iharm39; + + /** + * 16次谐波电压限值 + */ + @TableField("iharm_40") + private Float iharm40; + + /** + * 17次谐波电压限值 + */ + @TableField("iharm_41") + private Float iharm41; + + /** + * 18次谐波电压限值 + */ + @TableField("iharm_42") + private Float iharm42; + + /** + * 19次谐波电压限值 + */ + @TableField("iharm_43") + private Float iharm43; + + /** + * 20次谐波电压限值 + */ + @TableField("iharm_44") + private Float iharm44; + + /** + * 21次谐波电压限值 + */ + @TableField("iharm_45") + private Float iharm45; + + /** + * 22次谐波电压限值 + */ + @TableField("iharm_46") + private Float iharm46; + + /** + * 23次谐波电压限值 + */ + @TableField("iharm_47") + private Float iharm47; + + /** + * 24次谐波电压限值 + */ + @TableField("iharm_48") + private Float iharm48; + + /** + * 25次谐波电压限值 + */ + @TableField("iharm_49") + private Float iharm49; + + /** + * 50次谐波电压限值 + */ + @TableField("iharm_50") + private Float iharm50; + + + + /** + * 0.5次间谐波电压限值 + */ + @TableField("inuharm_1") + private Float inuharm1; + + /** + * 1.5次间谐波电压限值 + */ + @TableField("inuharm_2") + private Float inuharm2; + + /** + * 2.5次间谐波电压限值 + */ + @TableField("inuharm_3") + private Float inuharm3; + + /** + * 3.5次间谐波电压限值 + */ + @TableField("inuharm_4") + private Float inuharm4; + + /** + * 4.5次间谐波电压限值 + */ + @TableField("inuharm_5") + private Float inuharm5; + + /** + * 5.5次间谐波电压限值 + */ + @TableField("inuharm_6") + private Float inuharm6; + + /** + * 6.5次间谐波电压限值 + */ + @TableField("inuharm_7") + private Float inuharm7; + + /** + * 7.5次间谐波电压限值 + */ + @TableField("inuharm_8") + private Float inuharm8; + + /** + * 8.5次间谐波电压限值 + */ + @TableField("inuharm_9") + private Float inuharm9; + + /** + * 9.5次间谐波电压限值 + */ + @TableField("inuharm_10") + private Float inuharm10; + + /** + * 10.5次间谐波电压限值 + */ + @TableField("inuharm_11") + private Float inuharm11; + + /** + * 11.5次间谐波电压限值 + */ + @TableField("inuharm_12") + private Float inuharm12; + + /** + * 12.5次间谐波电压限值 + */ + @TableField("inuharm_13") + private Float inuharm13; + + /** + * 13.5次间谐波电压限值 + */ + @TableField("inuharm_14") + private Float inuharm14; + + /** + * 14.5次间谐波电压限值 + */ + @TableField("inuharm_15") + private Float inuharm15; + + /** + * 15.5次间谐波电压限值 + */ + @TableField("inuharm_16") + private Float inuharm16; + + public Overlimit(){} + + + public void buildIHarm(Float[] iHarmTem){ + this.iharm2= iHarmTem[0]; + this.iharm4= iHarmTem[2]; + this.iharm6= iHarmTem[4]; + this.iharm8= iHarmTem[6]; + this.iharm10= iHarmTem[8]; + this.iharm12= iHarmTem[10]; + this.iharm14= iHarmTem[12]; + this.iharm16= iHarmTem[14]; + this.iharm18= iHarmTem[16]; + this.iharm20= iHarmTem[18]; + this.iharm22= iHarmTem[20]; + this.iharm24= iHarmTem[22]; + this.iharm26= iHarmTem[24]; + this.iharm28= iHarmTem[26]; + this.iharm30= iHarmTem[28]; + this.iharm32= iHarmTem[30]; + this.iharm34= iHarmTem[32]; + this.iharm36= iHarmTem[34]; + this.iharm38= iHarmTem[36]; + this.iharm40= iHarmTem[38]; + this.iharm42= iHarmTem[40]; + this.iharm44= iHarmTem[42]; + this.iharm46= iHarmTem[44]; + this.iharm48= iHarmTem[46]; + this.iharm50= iHarmTem[48]; + + + + this.iharm3= iHarmTem[1]; + this.iharm5= iHarmTem[3]; + this.iharm7= iHarmTem[5]; + this.iharm9= iHarmTem[7]; + this.iharm11= iHarmTem[9]; + this.iharm13= iHarmTem[11]; + this.iharm15= iHarmTem[13]; + this.iharm17= iHarmTem[15]; + this.iharm19= iHarmTem[17]; + this.iharm21= iHarmTem[19]; + this.iharm23= iHarmTem[21]; + this.iharm25= iHarmTem[23]; + this.iharm27= iHarmTem[25]; + this.iharm29= iHarmTem[27]; + this.iharm31= iHarmTem[29]; + this.iharm33= iHarmTem[31]; + this.iharm35= iHarmTem[33]; + this.iharm37= iHarmTem[35]; + this.iharm39= iHarmTem[37]; + this.iharm41= iHarmTem[39]; + this.iharm43= iHarmTem[41]; + this.iharm45= iHarmTem[43]; + this.iharm47= iHarmTem[45]; + this.iharm49= iHarmTem[47]; + } + + public void buildUharm(Float resultEven,Float resultOdd){ + this.uharm2=resultEven; + this.uharm4=resultEven; + this.uharm6=resultEven; + this.uharm8=resultEven; + this.uharm10=resultEven; + this.uharm12=resultEven; + this.uharm14=resultEven; + this.uharm16=resultEven; + this.uharm18=resultEven; + this.uharm20=resultEven; + this.uharm22=resultEven; + this.uharm24=resultEven; + this.uharm26=resultEven; + this.uharm28=resultEven; + this.uharm30=resultEven; + this.uharm32=resultEven; + this.uharm34=resultEven; + this.uharm36=resultEven; + this.uharm38=resultEven; + this.uharm40=resultEven; + this.uharm42=resultEven; + this.uharm44=resultEven; + this.uharm46=resultEven; + this.uharm48=resultEven; + this.uharm50=resultEven; + + + this.uharm3=resultOdd; + this.uharm5=resultOdd; + this.uharm7=resultOdd; + this.uharm9=resultOdd; + this.uharm11=resultOdd; + this.uharm13=resultOdd; + this.uharm15=resultOdd; + this.uharm17=resultOdd; + this.uharm19=resultOdd; + this.uharm21=resultOdd; + this.uharm23=resultOdd; + this.uharm25=resultOdd; + this.uharm27=resultOdd; + this.uharm29=resultOdd; + this.uharm31=resultOdd; + this.uharm33=resultOdd; + this.uharm35=resultOdd; + this.uharm37=resultOdd; + this.uharm39=resultOdd; + this.uharm41=resultOdd; + this.uharm43=resultOdd; + this.uharm45=resultOdd; + this.uharm47=resultOdd; + this.uharm49=resultOdd; + } + + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsDeviceUnit.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsDeviceUnit.java new file mode 100644 index 0000000..2c6b9d8 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsDeviceUnit.java @@ -0,0 +1,120 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @Description: 数据单位管理表 + * @Author: wr + * @Date: 2023/8/21 9:56 + */ +@Data +@TableName("pqs_device_unit") +public class PqsDeviceUnit { + + private static final long serialVersionUID = 1L; + + @TableId(value = "DEV_INDEX") + @ApiModelProperty(value = "终端编号") + private String devIndex; + + @TableField("UNIT_FREQUENCY") + @ApiModelProperty(value = "频率") + private String unitFrequency = "Hz"; + + @TableField("UNIT_FREQUENCY_DEV") + @ApiModelProperty(value = "频率偏差") + private String unitFrequencyDev = "Hz"; + + @TableField("PHASE_VOLTAGE") + @ApiModelProperty(value = "相电压有效值") + private String phaseVoltage = "kV"; + + @TableField("LINE_VOLTAGE") + @ApiModelProperty(value = "线电压有效值") + private String lineVoltage = "kV"; + + @TableField("VOLTAGE_DEV") + @ApiModelProperty(value = "电压上偏差") + private String voltageDev = "%"; + + @TableField("UVOLTAGE_DEV") + @ApiModelProperty(value = "电压下偏差") + private String uvoltageDev = "%"; + + @TableField("I_EFFECTIVE") + @ApiModelProperty(value = "电流有效值") + private String ieffective = "A"; + + @TableField("SINGLE_P") + @ApiModelProperty(value = "单相有功功率") + private String singleP = "kW"; + + @TableField("SINGLE_VIEW_P") + @ApiModelProperty(value = "单相视在功率") + private String singleViewP = "kVA"; + + @TableField("SINGLE_NO_P") + @ApiModelProperty(value = "单相无功功率") + private String singleNoP = "kVar"; + + @TableField("TOTAL_ACTIVE_P") + @ApiModelProperty(value = "总有功功率") + private String totalActiveP = "kW"; + + @TableField("TOTAL_VIEW_P") + @ApiModelProperty(value = "总视在功率") + private String totalViewP = "kVA"; + + @TableField("TOTAL_NO_P") + @ApiModelProperty(value = "总无功功率") + private String totalNoP = "kVar"; + + @TableField("V_FUND_EFFECTIVE") + @ApiModelProperty(value = "相(线)电压基波有效值") + private String vfundEffective = "kV"; + + @TableField("I_FUND") + @ApiModelProperty(value = "基波电流") + private String ifund = "A"; + + @TableField("FUND_ACTIVE_P") + @ApiModelProperty(value = "基波有功功率") + private String fundActiveP = "kW"; + + @TableField("FUND_NO_P") + @ApiModelProperty(value = "基波无功功率") + private String fundNoP = "kVar"; + + @TableField("V_DISTORTION") + @ApiModelProperty(value = "电压总谐波畸变率") + private String vdistortion = "%"; + + @TableField("V_HARMONIC_RATE") + @ApiModelProperty(value = "2~50次谐波电压含有率") + private String vharmonicRate = "%"; + + @TableField("I_HARMONIC") + @ApiModelProperty(value = "2~50次谐波电流有效值") + private String iharmonic = "A"; + + @TableField("P_HARMONIC") + @ApiModelProperty(value = "2~50次谐波有功功率") + private String pharmonic = "kW"; + + @TableField("I_IHARMONIC") + @ApiModelProperty(value = "0.5~49.5次间谐波电流有效值") + private String iiharmonic = "A"; + + @TableField("POSITIVE_V") + @ApiModelProperty(value = "正序电压") + private String positiveV = "kV"; + + @TableField("NO_POSITIVE_V") + @ApiModelProperty(value = "零序负序电压") + private String noPositiveV = "V"; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgass.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgass.java new file mode 100644 index 0000000..eb71bc9 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgass.java @@ -0,0 +1,46 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 变压器逻辑节点关系表 + *

+ * + * @author wr + * @since 2023-07-19 + */ +@Getter +@Setter +@TableName("pqs_tflgass") +public class PqsTflgass extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "变压器跟逻辑节点关系Guid") + @TableId(value = "Ass_Index") + private String assIndex; + + @ApiModelProperty(value = "变压器台账Guid") + private String tfIndex; + + /** + * 上级逻辑节点 + */ + @ApiModelProperty(value = "上级逻辑节点") + private String logicBefore; + + /** + * 下级逻辑节点 + */ + @ApiModelProperty(value = "下级逻辑节点") + private String logicNext; + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgploy.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgploy.java new file mode 100644 index 0000000..9ebcd50 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgploy.java @@ -0,0 +1,55 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * PQS_TfLgPloy变压器策略表 + *

+ * + * @author wr + * @since 2023-07-19 + */ +@Getter +@Setter +@TableName("pqs_tflgploy") +public class PqsTflgploy extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 变压器策略Guid + */ + @ApiModelProperty(value = "变压器策略Guid") + @TableId(value = "tp_Index") + private String tpIndex; + + /** + * 变压器策略名称 + */ + @ApiModelProperty(value = "变压器策略名称") + private String tpName; + + /** + * 变压器策略描述 + */ + @ApiModelProperty(value = "变压器策略描述") + private String tfDescribe; + + /** + * 0删除 1.正常 + */ + @ApiModelProperty(value = "0删除 1.正常") + @TableLogic(value="1",delval="0") + private Integer status; + + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgployass.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgployass.java new file mode 100644 index 0000000..97b8946 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/PqsTflgployass.java @@ -0,0 +1,37 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * PQS_TfLgPloyAss策略、变压器关系表 + *

+ * + * @author wr + * @since 2023-07-19 + */ +@Getter +@Setter +@TableName("pqs_tflgployass") +public class PqsTflgployass extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 策略Guid(外键PQS_TfLgPloy中TP_Index) + */ + @ApiModelProperty(value = "策略Guid(外键PQS_TfLgPloy中TP_Index)") + private String tpIndex; + + /** + * 报告基础项Guid(外键PQS_Transformer中Tf_Index) + */ + @ApiModelProperty(value = "报告基础项Guid(外键PQS_Transformer中Tf_Index)") + private String tfIndex; + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/RmpEventDetailPO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/RmpEventDetailPO.java new file mode 100644 index 0000000..15e2e4f --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/RmpEventDetailPO.java @@ -0,0 +1,127 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 暂降明细实体类 + * + * @author yzh + * @since 2022-10-12 18:34:55 + */ +@Data +@TableName("r_mp_event_detail") +@ApiModel(value="RmpEventDetail对象") +public class RmpEventDetailPO implements Serializable { + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "暂时事件ID") + @TableId(value = "event_id", type = IdType.ASSIGN_ID) + private String eventId; + + @ApiModelProperty(value = "监测点ID") + private String measurementPointId; + + @ApiModelProperty(value = "监测点ID(复制)") + @TableField("measurement_point_id") + private String lineId; + + @ApiModelProperty(value = "统计类型") + private String eventType; + + @ApiModelProperty(value = "暂降原因(Event_Reason)") + @TableField("advance_reason") + private String advanceReason; + + @ApiModelProperty(value = "暂降类型(Event_Type)") + @TableField("advance_type") + private String advanceType; + + @ApiModelProperty(value = "事件关联分析表Guid") + private String eventassIndex; + + @ApiModelProperty(value = "dq计算持续时间 ") + private Double dqTime; + + @ApiModelProperty(value = "特征值计算更新时间(外键PQS_Relevance的Time字段)") + private LocalDateTime dealTime; + + @ApiModelProperty(value = "默认事件个数为0") + private Integer num; + + @ApiModelProperty(value = "波形文件是否从装置招到本地(0:未招,1:已招)默认值为0") + private Integer fileFlag; + + @ApiModelProperty(value = "特征值计算标志(0,未处理;1,已处理; 2,已处理,无结果;3,计算失败)默认值为0") + private Integer dealFlag; + + @ApiModelProperty(value = "处理结果第一条事件发生时间(读comtra文件获取)") + private LocalDateTime firstTime; + + @ApiModelProperty(value = "处理结果第一条事件暂降类型(字典表PQS_Dicdata)") + private String firstType; + + @ApiModelProperty(value = "处理结果第一条事件发生时间毫秒(读comtra文件获取)") + private Double firstMs; + + @ApiModelProperty(value = "暂降能量") + private Double energy; + + @ApiModelProperty(value = "暂降严重度") + private Double severity; + + @ApiModelProperty(value = "暂降源与监测位置关系 Upper:上游;Lower :下游;Unknown :未知;为空则是未计算") + private String sagsource; + + @ApiModelProperty(value = "开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") + private LocalDateTime startTime; + + @ApiModelProperty(value = "格式化开始时间") + @TableField(exist = false) + private String formatTime; + + + @ApiModelProperty(value = "持续时间,单位秒") + private Double duration; + + @ApiModelProperty(value = "特征幅值") + private Double featureAmplitude; + + @ApiModelProperty(value = "相别") + private String phase; + + @ApiModelProperty(value = "事件描述") + private String eventDescribe; + + @ApiModelProperty(value = "波形路径") + private String wavePath; + + @ApiModelProperty(value = "暂降核实原因") + @TableField("verify_reason") + private String verifyReason; + + @ApiModelProperty(value = "暂降核实原因详情") + @TableField("verify_reason_detail") + private String verifyReasonDetail; + + private Double transientValue; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @ApiModelProperty(value = "用于计算数量") + @TableField(exist = false) + private Integer count; + +} + diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportNormalPO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportNormalPO.java new file mode 100644 index 0000000..93acbb0 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportNormalPO.java @@ -0,0 +1,53 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 干扰源用户常态化管理 + *

+ * + * @author hongawen + * @since 2024-05-17 + */ +@Getter +@Setter +@TableName("supervision_user_report_normal") +public class UserReportNormalPO extends BaseEntity { + + private static final long serialVersionUID = 1L; + + private String id; + + /** + * 关联干扰源用户表 + */ + private String userReportId; + + /** + * 类型0:方案审查 1:治理工程 + */ + private Integer type; + + /** + * 报告存放路径 + */ + private String reportUrl; + + private String processInstanceId; + + private String historyInstanceId; + + /** + * 1:审批中;2:审批通过;3:审批不通过;4:已取消 + */ + private Integer status; + + private Integer state; + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportPO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportPO.java new file mode 100644 index 0000000..7514d15 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportPO.java @@ -0,0 +1,184 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * + * Description: + * Date: 2024/4/25 10:07【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "supervision_user_report") +public class UserReportPO extends BaseEntity { + /** + * id + */ + @TableId(value = "id", type = IdType.ASSIGN_UUID) + private String id; + + /** + * 填报人 + */ + @TableField(value = "reporter") + private String reporter; + + /** + * 填报日期 + */ + @TableField(value = "report_date") + private LocalDate reportDate; + + /** + * 填报部门 + */ + @TableField(value = "org_id") + private String orgId; + + /** + * 工程预期投产日期 + */ + @TableField(value = "expected_production_date") + private LocalDate expectedProductionDate; + + /** + * 用户性质 + */ + @TableField(value = "user_type") + private Integer userType; + + /** + * 所属地市 + */ + @TableField(value = "city") + private String city; + + /** + * 归口管理部门 + */ + @TableField(value = "responsible_department") + private String responsibleDepartment; + + /** + * 用户状态 + */ + @TableField(value = "user_status") + private Integer userStatus; + + /** + * 变电站 + */ + @TableField(value = "substation") + private String substation; + + /** + * 电压等级 + */ + @TableField(value = "voltage_level") + private String voltageLevel; + + /** + * 用户编号 + */ + @TableField(value = "user_no") + private String userNo; + + /** + * 工程名称 + */ + @TableField(value = "project_name") + private String projectName; + + /** + * 预测评估单位 + */ + @TableField(value = "evaluation_dept") + private String evaluationDept; + + /** + * 预测评估结论 + */ + @TableField(value = "evaluation_conclusion") + private String evaluationConclusion; + + /** + * 流程实例的编号 + */ + @TableField(value = "process_instance_id") + private String processInstanceId; + + + @TableField(value = "history_instance_id") + private String historyInstanceId; + + /** + * 数据来源类型 0.正常流程审核入库 1.批量导入 + */ + @TableField(value = "data_type") + private Integer dataType; + + /** + * 电站id + */ + private String stationId; + + /** + * 额定容量 + */ + private Double ratePower; + + /** + * 经度 + */ + private BigDecimal longitude; + + + /** + * 纬度 + */ + private BigDecimal latitude; + + /** + * 终端id + */ + @TableField(value = "dev_id") + private String devId; + + /** + * 监测点id + */ + @TableField(value = "line_id") + private String lineId; + + /** + * 审批状态:1:审批中;2:审批通过;3:审批不通过;4:已取消 + */ + @TableField(value = "status") + private Integer status; + + + /** + * 状态:0-删除 1-正常 + */ + @TableField(value = "State") + private Integer state; + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportProjectPO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportProjectPO.java new file mode 100644 index 0000000..fb23ed5 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportProjectPO.java @@ -0,0 +1,96 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.common.pojo.constant.PatternRegex; + +import com.njcn.db.mybatisplus.bo.BaseEntity; +import com.njcn.product.terminal.mysqlTerminal.pojo.constant.ValidMessage; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Pattern; + +/** + * + * Description: + * Date: 2024/4/25 10:08【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "supervision_user_report_project") +public class UserReportProjectPO extends BaseEntity { + /** + * 关联id + */ + @TableId(value = "id", type = IdType.INPUT) + private String id; + + /** + * 用户协议容量 + */ + @TableField(value = "agreement_capacity") + @Pattern(regexp = PatternRegex.COORDINATE, message = ValidMessage.PARAM_FORMAT_ERROR) + private Double agreementCapacity; + + /** + * 非线性设备类型 + */ + @TableField(value = "nonlinear_device_type") + private String nonlinearDeviceType; + + /** + * 是否需要治理 + */ + @TableField(value = "need_governance") + private Integer needGovernance; + + /** + * 是否开展背景测试 + */ + @TableField(value = "background_test_performed") + private Integer backgroundTestPerformed; + + /** + * 可研报告告地址 + */ + @TableField(value = "feasibility_report") + private String feasibilityReport; + + /** + * 项目初步设计说明书告地址 + */ + @TableField(value = "preliminary_design_description") + private String preliminaryDesignDescription; + + /** + * 预测评估报告告地址 + */ + @TableField(value = "prediction_evaluation_report") + private String predictionEvaluationReport; + + /** + * 预测评估评审意见报告地址 + */ + @TableField(value = "prediction_evaluation_review_opinions") + private String predictionEvaluationReviewOpinions; + + /** + * 其他附件告地址 + */ + @TableField(value = "additional_attachments") + private String additionalAttachments; + + /** + * 数据状态 + */ + @TableField(value = "state") + private Integer state; +} \ No newline at end of file diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportRenewalPO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportRenewalPO.java new file mode 100644 index 0000000..fb90071 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportRenewalPO.java @@ -0,0 +1,67 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; + +import com.njcn.db.mybatisplus.bo.BaseEntity; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.UserReportParam; +import lombok.Data; + +/** + *

+ * 用户档案信息表 + *

+ * + * @author wr + * @since 2024-06-26 + */ +@Data +@TableName("supervision_user_report_renewal") +public class UserReportRenewalPO extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + private String id; + + /** + * 常态化用户跟新后信息 + */ + private String userReportMessage; + + /** + * 常态化用户跟新后信息 + */ + @TableField(exist = false) + private UserReportParam userReportMessageJson; + + /** + * 流程实例的编号 + */ + private String processInstanceId; + + /** + * 历史流程实列 + */ + private String historyInstanceId; + + /** + * 1:审批中;2:审批通过;3:审批不通过;4:已取消 + */ + private Integer status; + /** + * 状态:0-删除 1-正常 + */ + private Integer state; + + public void setUserReportMessage(String userReportMessage) { + if(StrUtil.isNotBlank(userReportMessage)){ + this.userReportMessageJson= JSONObject.parseObject(userReportMessage,UserReportParam.class); + } + this.userReportMessage = userReportMessage; + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportSensitivePO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportSensitivePO.java new file mode 100644 index 0000000..2be3fed --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportSensitivePO.java @@ -0,0 +1,191 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.common.pojo.constant.PatternRegex; + +import com.njcn.db.mybatisplus.bo.BaseEntity; +import com.njcn.product.terminal.mysqlTerminal.pojo.constant.ValidMessage; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Pattern; + +/** + * + * Description: + * Date: 2024/4/25 10:09【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "supervision_user_report_sensitive") +public class UserReportSensitivePO extends BaseEntity { + /** + * 关联id + */ + @TableId(value = "id", type = IdType.INPUT) + private String id; + + /** + * PCC点 + */ + @TableField(value = "pcc_point") + @Pattern(regexp = PatternRegex.DES32_REGEX, message = ValidMessage.DATA_TOO_LONG) + private String pccPoint; + + /** + * 行业 + */ + @TableField(value = "industry") + private String industry; + + /** + * 敏感装置名称 + */ + @TableField(value = "device_name") + @Pattern(regexp = PatternRegex.DES32_REGEX, message = ValidMessage.DATA_TOO_LONG) + private String deviceName; + + /** + * 供电电源数量 + */ + @TableField(value = "power_supply_count") + private Integer powerSupplyCount; + + /** + * 敏感电能质量指标 + */ + @TableField(value = "energy_quality_index") + private String energyQualityIndex; + + /** + * 评估类型 + */ + @TableField(value = "evaluation_type") + private String evaluationType; + + /** + * 是否开展抗扰度测试 + */ + @TableField(value = "anti_interference_test") + private String antiInterferenceTest; + + /** + * 预测评估审核单位 + */ + @TableField(value = "evaluation_chek_dept") + @Pattern(regexp = PatternRegex.DES32_REGEX, message = ValidMessage.DATA_TOO_LONG) + private String evaluationChekDept; + + /** + * 是否需要治理 + */ + @TableField(value = "need_governance") + private Integer needGovernance; + + /** + * 是否开展背景测试 + */ + @TableField(value = "background_test_performed") + private Integer backgroundTestPerformed; + + /** + * 用户接入变电站主接线示意图地址 + */ + @TableField(value = "substation_main_wiring_diagram") + private String substationMainWiringDiagram; + + /** + * 主要敏感设备清单 + */ + @TableField(value = "sensitive_devices") + private String sensitiveDevices; + + /** + * 抗扰度测试报告 + */ + @TableField(value = "anti_interference_report") + private String antiInterferenceReport; + + /** + * 背景电能质量测试报告 + */ + @TableField(value = "power_quality_report") + private String powerQualityReport; + + /** + * 可研报告地址 + */ + @TableField(value = "feasibility_report") + private String feasibilityReport; + + /** + * 项目初步设计说明书地址 + */ + @TableField(value = "preliminary_design_description") + private String preliminaryDesignDescription; + + /** + * 预测评估报告地址 + */ + @TableField(value = "prediction_evaluation_report") + private String predictionEvaluationReport; + + /** + * 预测评估评审意见报告地址 + */ + @TableField(value = "prediction_evaluation_review_opinions") + private String predictionEvaluationReviewOpinions; + + /** + * 其他附件 + */ + @TableField(value = "additional_attachments") + private String additionalAttachments; + + /** + * 数据状态 + */ + @TableField(value = "state") + private Integer state; + + /** + * 供电电源 + */ + @TableField(value = "power_supply") + private String powerSupply; + + /** + * 接入电压等级 + */ + @TableField(value = "supply_voltage_level") + private String supplyVoltageLevel; + + /** + * 负荷级别 + */ + @TableField(value = "load_level") + private String loadLevel; + + + /** + * 供电电源情况(单电源、双电源、多电源) + */ + @TableField(value = "power_supply_info") + private String powerSupplyInfo; + + /** + * 运维单位 + */ + @TableField(value = "maintenance_unit") + private String maintenanceUnit; + + +} \ No newline at end of file diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportSubstationPO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportSubstationPO.java new file mode 100644 index 0000000..1203911 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/UserReportSubstationPO.java @@ -0,0 +1,142 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import com.njcn.product.terminal.mysqlTerminal.pojo.constant.ValidMessage; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Pattern; +import java.math.BigDecimal; + +/** + * + * Description: + * Date: 2024/4/25 10:09【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName(value = "supervision_user_report_substation") +public class UserReportSubstationPO extends BaseEntity { + @TableId(value = "id", type = IdType.INPUT) + private String id; + + /** + * PCC点 + */ + @TableField(value = "pcc_point") + @Pattern(regexp = PatternRegex.DES32_REGEX, message = ValidMessage.DATA_TOO_LONG) + private String pccPoint; + + /** + * 基准短路容量(MVA) + */ + @TableField(value = "base_short_circuit_capacity") + @Pattern(regexp = PatternRegex.COORDINATE, message = ValidMessage.PARAM_FORMAT_ERROR) + private BigDecimal baseShortCircuitCapacity; + + /** + * 系统最小短路容量(MVA) + */ + @TableField(value = "min_short_circuit_capacity") + @Pattern(regexp = PatternRegex.COORDINATE, message = ValidMessage.PARAM_FORMAT_ERROR) + private BigDecimal minShortCircuitCapacity; + + /** + * PCC供电设备容量(MVA) + */ + @TableField(value = "pcc_equipment_capacity") + @Pattern(regexp = PatternRegex.COORDINATE, message = ValidMessage.PARAM_FORMAT_ERROR) + private BigDecimal pccEquipmentCapacity; + + /** + * 用户用电协议容量(MVA) + */ + @TableField(value = "user_agreement_capacity") + @Pattern(regexp = PatternRegex.COORDINATE, message = ValidMessage.PARAM_FORMAT_ERROR) + private BigDecimal userAgreementCapacity; + + /** + * 评估类型 + */ + @TableField(value = "evaluation_type") + private String evaluationType; + + /** + * 非线性负荷类型 + */ + @TableField(value = "nonlinear_load_type") + private String nonlinearLoadType; + + /** + * 预测评估审核单位 + */ + @TableField(value = "evaluation_chek_dept") + @Pattern(regexp = PatternRegex.DES32_REGEX, message = ValidMessage.DATA_TOO_LONG) + private String evaluationChekDept; + + /** + * 是否需要治理 + */ + @TableField(value = "need_governance") + private Integer needGovernance; + + /** + * 是否开展背景测试 + */ + @TableField(value = "background_test_performed") + private Integer backgroundTestPerformed; + + /** + * 用户接入变电站主接线示意图地址 + */ + @TableField(value = "substation_main_wiring_diagram") + private String substationMainWiringDiagram; + + /** + * 可研报告地址 + */ + @TableField(value = "feasibility_report") + private String feasibilityReport; + + /** + * 项目初步设计说明书地址 + */ + @TableField(value = "preliminary_design_description") + private String preliminaryDesignDescription; + + /** + * 预测评估报告地址 + */ + @TableField(value = "prediction_evaluation_report") + private String predictionEvaluationReport; + + /** + * 预测评估评审意见报告地址 + */ + @TableField(value = "prediction_evaluation_review_opinions") + private String predictionEvaluationReviewOpinions; + + /** + * 其他附件 + */ + @TableField(value = "additional_attachments") + private String additionalAttachments; + + /** + * 数据状态 + */ + @TableField(value = "state") + private Integer state; + + +} \ No newline at end of file diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Voltage.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Voltage.java new file mode 100644 index 0000000..6af1041 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/po/Voltage.java @@ -0,0 +1,42 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + *

+ * + *

+ * + * @author cdf + * @since 2022-01-04 + */ +@Data +@TableName("pq_voltage") +public class Voltage { + + private static final long serialVersionUID = 1L; + + /** + * 母线序号 + */ + private String id; + + /** + * 母线号(在同一台设备中的电压通道号) + */ + private Integer num; + + /** + * 电压等级Id,字典表 + */ + private String scale; + + /** + * 母线模型(0:虚拟母线;1:实际母线)默认是实际母线 + */ + private Integer model; + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/AdvanceEventDetailVO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/AdvanceEventDetailVO.java new file mode 100644 index 0000000..972af7d --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/AdvanceEventDetailVO.java @@ -0,0 +1,116 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * pqs + * + * @author cdf + * @date 2023/7/20 + */ +@Data +public class AdvanceEventDetailVO { + + + @ApiModelProperty(value = "暂时事件ID") + private String eventId; + + @ApiModelProperty(value = "监测点ID") + private String measurementPointId; + + @ApiModelProperty(value = "监测点ID(复制)") + private String lineId; + + @ApiModelProperty(value = "统计类型") + private String eventType; + + @ApiModelProperty(value = "暂降原因(Event_Reason)") + private String advanceReason; + + @ApiModelProperty(value = "暂降类型(Event_Type)") + private String advanceType; + + @ApiModelProperty(value = "事件关联分析表Guid") + private String eventassIndex; + + @ApiModelProperty(value = "dq计算持续时间 ") + private Double dqTime; + + @ApiModelProperty(value = "特征值计算更新时间(外键PQS_Relevance的Time字段)") + @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss") + private LocalDateTime dealTime; + + @ApiModelProperty(value = "默认事件个数为0") + private Integer num; + + @ApiModelProperty(value = "波形文件是否从装置招到本地(0:未招,1:已招)默认值为0") + private Integer fileFlag; + + @ApiModelProperty(value = "特征值计算标志(0,未处理;1,已处理; 2,已处理,无结果;3,计算失败)默认值为0") + private Integer dealFlag; + + @ApiModelProperty(value = "处理结果第一条事件发生时间(读comtra文件获取)") + @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss") + private LocalDateTime firstTime; + + @ApiModelProperty(value = "处理结果第一条事件暂降类型(字典表PQS_Dicdata)") + private String firstType; + + @ApiModelProperty(value = "处理结果第一条事件发生时间毫秒(读comtra文件获取)") + private Integer firstMs; + + @ApiModelProperty(value = "暂降能量") + private Double energy; + + @ApiModelProperty(value = "暂降严重度") + private Double severity; + + @ApiModelProperty(value = "暂降源与监测位置关系 Upper:上游;Lower :下游;Unknown :未知;为空则是未计算") + private String sagsource; + + @ApiModelProperty(value = "开始时间") + @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss.SSS") + private LocalDateTime startTime; + + @ApiModelProperty(value = "持续时间,单位秒") + private Integer duration; + + @ApiModelProperty(value = "特征幅值") + private Double featureAmplitude; + + @ApiModelProperty(value = "相别") + private String phase; + + @ApiModelProperty(value = "事件描述") + private String eventDescribe; + + @ApiModelProperty(value = "波形路径") + private String wavePath; + + private Double transientValue; + + @ApiModelProperty(value = "供电公司名称") + private String gdName; + + @ApiModelProperty(value = "变电站名称") + private String subName; + + @ApiModelProperty(value = "监测点名称") + private String lineName; + + @ApiModelProperty(value = "母线节点id") + private String busBarId; + + @ApiModelProperty(value = "母线电压等级") + private String voltageId; + + @ApiModelProperty(value = "特征值是否计算") + private String featureAmplitudeFlag; + + @ApiModelProperty(value = "录波文件是否存在") + private String boFileFlag; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDataVO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDataVO.java new file mode 100644 index 0000000..0a0e8e8 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDataVO.java @@ -0,0 +1,52 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.vo; + +import lombok.Data; + +/** + * @author denghuajun + * @date 2022/2/23 + * 保存line信息表 + */ +@Data +public class LineDataVO { + /** + * 监测点Id + */ + private String id; + + /** + * 父节点(0为根节点) + */ + private String pid; + + /** + * 上层所有节点 + */ + private String pids; + + /** + * 名称 + */ + private String name; + + /** + * 等级:0-项目名称;1- 工程名称;2-单位;3-部门;4-终端;5-母线;6-监测点 + */ + private Integer level; + + /** + * 排序(默认为0,有特殊排序需要时候人为输入) + */ + private Integer sort; + + /** + * 备注 + */ + private String remark; + + /** + * 状态 0-删除;1-正常;默认正常 + */ + private Integer state; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDetailDataVO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDetailDataVO.java new file mode 100644 index 0000000..0391836 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDetailDataVO.java @@ -0,0 +1,135 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * @author denghuajun + * @date 2022/2/23 + * 监测点信息 + */ +@Data +@ApiModel +public class LineDetailDataVO { + + private String lineId; + + @ApiModelProperty(name = "id",value = "监测点序号") + private Integer id; + + @ApiModelProperty(name = "lineName",value = "监测点名称") + private String lineName; + + @ApiModelProperty(name = "areaName",value = "工程名称") + private String areaName; + + @ApiModelProperty(name = "gdName",value = "单位") + private String gdName; + + @ApiModelProperty(name = "bdName",value = "部门") + private String bdName; + + @ApiModelProperty(name = "scale",value = "电压等级") + private String scale; + + @ApiModelProperty(name = "manufacturer",value = "厂家") + private String manufacturer; + + @ApiModelProperty(name = "devId",value = "终端Id") + private String devId; + + @ApiModelProperty(name = "devName",value = "终端名称") + private String devName; + + @ApiModelProperty(name = "ip",value = "网络参数") + private String ip; + + @ApiModelProperty(name = "runFlag",value = "终端运行状态") + private String runFlag; + + @ApiModelProperty(name = "comFlag",value = "通讯状态") + private String comFlag; + + @ApiModelProperty(name = "loadType",value = "干扰源类型") + private String loadType; + + @ApiModelProperty(name = "businessType",value = "行业类型") + private String businessType; + + @ApiModelProperty(name = "objName",value = "监测点对象名称") + private String objName; + + @ApiModelProperty(name = "ptType",value = "接线方式") + private String ptType; + + @ApiModelProperty(name = "pt",value = "PT变比") + private String pt; + + @ApiModelProperty(name = "ct",value = "CT变比") + private String ct; + + @ApiModelProperty(name = "standardCapacity",value = "基准容量(MVA)") + private Float standardCapacity; + + @ApiModelProperty(name = "shortCapacity",value = "最小短路容量(MVA)") + private Float shortCapacity; + + @ApiModelProperty(name = "devCapacity",value = "供电设备容量(MVA)") + private Float devCapacity; + + @ApiModelProperty(name = "dealCapacity",value = "用户协议容量(MVA)") + private Float dealCapacity; + + @ApiModelProperty(name = "powerFlag",value = "电网标志(0-电网侧;1-非电网侧)") + private Integer powerFlag; + + /** + * 测量间隔(1-10分钟) + */ + @ApiModelProperty(name = "timeInterval",value = "测量间隔(1-10分钟)") + private Integer timeInterval; + + /** + * 监测点拥有者 + */ + @ApiModelProperty(name = "owner",value = "监测点拥有者") + private String owner; + + /** + * 拥有者职务 + */ + @ApiModelProperty(name = "ownerDuty",value = "拥有者职务") + private String ownerDuty; + + /** + * 拥有者联系方式 + */ + @ApiModelProperty(name = "ownerTel",value = "拥有者联系方式") + private String ownerTel; + + /** + * 接线图 + */ + @ApiModelProperty(name = "wiringDiagram",value = "接线图") + private String wiringDiagram; + @ApiModelProperty(name = "ptPhaseType",value = "监测点接线相别(0,单相,1,三相,默认三相)") + private Integer ptPhaseType; + + @ApiModelProperty(name = "投运日期") + private LocalDate loginTime; + + @ApiModelProperty(name = "最新数据时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @ApiModelProperty(name = "监测对象信息ID") + private String objId; + + @ApiModelProperty(name = "对象类型大类") + private String bigObjType; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDetailVO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDetailVO.java new file mode 100644 index 0000000..42f5796 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/LineDetailVO.java @@ -0,0 +1,109 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @author denghuajun + * @version 1.0.0 + * @date 2022年05月06日 15:38 + */ +@Data +public class LineDetailVO implements Serializable { + + @ApiModelProperty("供电公司名称") + private String gdName; + + @ApiModelProperty("变电站名称") + private String subName; + + @ApiModelProperty("终端名称") + private String devName; + + @ApiModelProperty("网络参数") + private String ip; + + @ApiModelProperty("监测点名称") + private String lineName; + + @ApiModelProperty("母线名称") + private String volName; + + /** + * (0:运行;1:检修;2:停运;3:调试;4:退运) + */ + @ApiModelProperty("监测点运行状态") + private Integer runFlag; + @Data + public static class Detail extends LineDetailVO implements Serializable{ + + @ApiModelProperty("区域id") + private String areaId; + + @ApiModelProperty("区域名称") + private String areaName; + + @ApiModelProperty("终端id") + private String devId; + + @ApiModelProperty("监测点Id") + private String lineId; + + @ApiModelProperty("测量间隔(1-10分钟)") + private Integer timeInterval; + + @ApiModelProperty("接线类型") + private Integer ptType; + + @ApiModelProperty("电压等级") + private String voltageLevel; + + @ApiModelProperty("数据更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timeID; + + @ApiModelProperty("终端等级") + private String lineGrade; + + @ApiModelProperty("通讯状态(0:中断;1:正常)") + private Integer comFlag; + + @ApiModelProperty("PT一次变比") + private Double PT1; + + @ApiModelProperty("PT二次变比") + private Double PT2; + + @ApiModelProperty("CT一次变比") + private Double CT1; + + @ApiModelProperty("CT二次变比") + private Double CT2; + + @ApiModelProperty("套餐流量") + private Float flowMeal; + + @ApiModelProperty("已用流量") + private Float statisValue; + + @ApiModelProperty("已用流量占比") + private Float flowProportion; + } + + @Data + public static class noDataLineInfo extends LineDetailVO implements Serializable{ + + @ApiModelProperty("监测点Id") + private String lineId; + + @ApiModelProperty("终端id") + private String devId; + + @ApiModelProperty("最新数据时间") + private LocalDateTime updateTime; + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/TerminalShowVO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/TerminalShowVO.java new file mode 100644 index 0000000..8810ddc --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/TerminalShowVO.java @@ -0,0 +1,23 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.vo; + + +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerBaseInfo; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: cdf + * @CreateTime: 2025-09-05 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class TerminalShowVO extends LedgerBaseInfo { + + private String stationVoltageLevel; + + private Double lng; + + private Double lat; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/TerminalTree.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/TerminalTree.java new file mode 100644 index 0000000..200fdff --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/TerminalTree.java @@ -0,0 +1,80 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * pqs + * 终端树实体 + * @author cdf + * @date 2021/7/19 + */ +@ApiModel +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TerminalTree implements Serializable { + @ApiModelProperty(name = "index",value = "序号") + private Integer index; + + private String id; + @ApiModelProperty(name = "parentId",value = "父id") + private String pid; + @ApiModelProperty(name = "level",value = "等级") + private Integer level; + @ApiModelProperty(name = "name",value = "名称") + private String name; + @ApiModelProperty(name = "sort",value = "排序") + private Integer sort; + @ApiModelProperty(name = "comFlag",value = "设备状态") + private Integer comFlag; + + @ApiModelProperty(name = "children",value = "子节点") + private List children = new ArrayList<>(); + + private String pids; + + /** + * 终端厂家 + */ + private String manufacturer; + + /** + * 电压等级Id,字典表 + */ + private String scale; + + /** + * 干扰源类型,字典表 + */ + private String loadType; + + /** + * 接线方式 + */ + private Integer ptType; + + /** + * 电网标志(0-电网侧;1-非电网侧) + */ + private Integer powerFlag; + + /** + * 电网侧变电站 + */ + private String powerSubstationName; + + /** + * 电网侧变电站 + */ + private String objName; + + private String objId; +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/UserLedgerVO.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/UserLedgerVO.java new file mode 100644 index 0000000..a9cd426 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/pojo/vo/UserLedgerVO.java @@ -0,0 +1,24 @@ +package com.njcn.product.terminal.mysqlTerminal.pojo.vo; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: cdf + * @CreateTime: 2025-03-24 + * @Description: 用户台账 + */ +@Data +public class UserLedgerVO implements Serializable { + private static final long serialVersionUID = 1L; + + private String id; + + private String projectName; + + private String stationId; + + private String city; + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/CommGeneralService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/CommGeneralService.java new file mode 100644 index 0000000..811f41c --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/CommGeneralService.java @@ -0,0 +1,518 @@ +package com.njcn.product.terminal.mysqlTerminal.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.njcn.common.pojo.dto.SimpleDTO; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.common.ServerEnum; +import com.njcn.common.utils.EnumUtils; +import com.njcn.product.cnuser.user.pojo.dto.DeptDTO; +import com.njcn.product.system.dict.mapper.DictDataMapper; +import com.njcn.product.system.dict.pojo.enums.DicDataTypeEnum; +import com.njcn.product.system.dict.pojo.po.DictData; +import com.njcn.product.terminal.mysqlTerminal.mapper.DeptLineMapper; +import com.njcn.product.cnuser.user.mapper.DeptMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.DeviceType; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.GeneralDeviceDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.enums.LineBaseEnum; +import com.njcn.product.terminal.mysqlTerminal.pojo.enums.PowerFlagEnum; +import com.njcn.product.terminal.mysqlTerminal.pojo.enums.StatisticsEnum; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.DeviceInfoParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.LargeScreenCountParam; +import com.njcn.product.cnuser.user.pojo.po.Dept; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.DeptLine; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Line; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * @Author: cdf + * @CreateTime: 2025-06-25 + * @Description: + */ +@Service +@RequiredArgsConstructor +public class CommGeneralService { + + private final DeptMapper deptMapper; + + private final DeptLineMapper deptLineMapper; + + private final DeptLineService deptLineService; + + private final DictDataMapper dictDataMapper; + + private final TerminalBaseService terminalBaseService; + + + /** + * 根据部门id获取部门所拥有的监测点 + * @param deptId + * @return + */ + public List getRunLineIdsByDept(String deptId){ + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.like(Dept::getPids,deptId).eq(Dept::getState, DataStateEnum.ENABLE.getCode()); + List deptAndChildren = deptMapper.selectList(lambdaQueryWrapper); + List deptIds = deptAndChildren.stream().map(Dept::getId).distinct().collect(Collectors.toList()); + deptIds.add(deptId); + + List deptLineList = deptLineMapper.getLineIdByDeptIds(deptIds, Stream.of(0).collect(Collectors.toList())); + return deptLineList; + } + + /** + * 根据部门id获取部门所拥有的监测点 + * @param param + * @return + */ + public List getRunLineIdsByDept(LargeScreenCountParam param){ + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.like(Dept::getPids,param.getDeptId()).eq(Dept::getState, DataStateEnum.ENABLE.getCode()); + List deptAndChildren = deptMapper.selectList(lambdaQueryWrapper); + List deptIds = deptAndChildren.stream().map(Dept::getId).distinct().collect(Collectors.toList()); + deptIds.add(param.getDeptId()); + + List deptLineList = deptLineMapper.getLineIdByDeptIds(deptIds, Stream.of(0).collect(Collectors.toList())); + return deptLineList; + } + + + /** + * 根据部门id获取部门所拥有的监测点 + * @param deptId + * @return + */ + public List getAllLineIdsByDept(String deptId){ + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.like(Dept::getPids,deptId).eq(Dept::getState, DataStateEnum.ENABLE.getCode()); + List deptAndChildren = deptMapper.selectList(lambdaQueryWrapper); + List deptIds = deptAndChildren.stream().map(Dept::getId).distinct().collect(Collectors.toList()); + deptIds.add(deptId); + + List deptLineList = deptLineMapper.getLineIdByDeptIds(deptIds, Stream.of(0,1,2).collect(Collectors.toList())); + return deptLineList; + } + + + /** + * 根据部门id、远程服务名、远程客户端类型,以部门的方式 + * + * @param deviceInfoParam 终端查询条件 + * @param runFlag 终端状态 + * @param devModel 终端模型 + * @return 部门分类终端信息 + */ + public List getDeviceInfo(DeviceInfoParam deviceInfoParam, + List runFlag, + List devModel) { + //定义待返回终端信息 + List deviceInfos = new ArrayList<>(); + //初始化终端查询条件 + DeviceType deviceType = new DeviceType(); + if (CollectionUtil.isEmpty(devModel)) { + /** + * 终端模型(0:虚拟设备;1:实际设备;2:离线设备;)默认是实际设备 + */ + deviceType.setDevModel(null); + } else { + deviceType.setDevModel(devModel); + } + if (CollectionUtil.isEmpty(runFlag)) { + /** + * 终端状态(0:投运;1:热备用;2:停运) + */ + deviceType.setRunFlag(null); + } else { + deviceType.setRunFlag(runFlag); + } + if(ObjectUtil.isNotNull(deviceInfoParam.getComFlagStatus())){ + deviceType.setComFlag(Arrays.asList(deviceInfoParam.getComFlagStatus())); + } + filterDataType(deviceType, deviceInfoParam.getServerName()); + + // 初始化部门筛选条件 + List deptType = Stream.of(0,1,2,3).collect(Collectors.toList()); + // 获取包括当前部门的后代所有部门信息 + List deptInfos = deptMapper.getDeptDescendantIndexes(deviceInfoParam.getDeptIndex(), deptType); + // 过滤非直接后代部门,集合直接子部门 + List directDeptInfos = deptInfos.stream() + .filter(deptDTO -> deptDTO.getPid().equals(deviceInfoParam.getDeptIndex())).sorted(Comparator.comparing(DeptDTO::getSort)) + .collect(Collectors.toList()); + if (CollectionUtil.isEmpty(directDeptInfos)) { + // 没有直接子部门(树的最底层),获取当前部门所有信息 + List dept = deptInfos.stream() + .filter(deptDTO -> deptDTO.getId().equals(deviceInfoParam.getDeptIndex())) + .collect(Collectors.toList()); + deviceInfos.add(getGeneralDeviceInfo( + dept.get(0), + deviceType, + Collections.singletonList(deviceInfoParam.getDeptIndex()), + deviceInfoParam)); + } else { + for (DeptDTO directDeptDTO : directDeptInfos) { + //筛选pids包含该id的所有部门 直接子部门下属所有部门 + List descendantDeptDTO = deptInfos.stream() + .filter(d -> d.getPids().contains(directDeptDTO.getId())) + .collect(Collectors.toList()); + //形成需要查询监测点的部门索引 + List indexes = descendantDeptDTO.stream() + .map(DeptDTO::getId) + .distinct() + .collect(Collectors.toList()); + indexes.add(directDeptDTO.getId()); + GeneralDeviceDTO generalDeviceInfo = getGeneralDeviceInfo(directDeptDTO, deviceType, indexes, deviceInfoParam); + deviceInfos.add(generalDeviceInfo); + } + } + + + //判断统计类型 + if (deviceInfoParam.getStatisticalType() == null) { + deviceInfoParam.setStatisticalType(new SimpleDTO()); + } + StatisticsEnum statisticsEnum = StatisticsEnum.getStatisticsEnumByCode(deviceInfoParam.getStatisticalType().getCode()); + switch (statisticsEnum) { + case VOLTAGE_LEVEL: + return filterDataByScale(deviceInfos, deviceInfoParam.getScale()); + case LOAD_TYPE: + return filterDataByLoadType(deviceInfos, deviceInfoParam.getLoadType()); + case MANUFACTURER: + return filterDataByManufacturer(deviceInfos, deviceInfoParam.getManufacturer()); + case POWER_FLAG: + return filterDataByPowerFlag(deviceInfos, deviceInfoParam.getManufacturer()); + default: + return deviceInfos; + } + } + + + /** + * 筛选数据类型 + */ + private void filterDataType(DeviceType deviceType, String serverName) { + ServerEnum serverEnum = EnumUtils.getServerEnumByName(serverName); + List dataType = new ArrayList<>(); + dataType.add(2); + switch (serverEnum) { + case EVENT: + dataType.add(0); + break; + case HARMONIC: + dataType.add(1); + break; + default: + dataType.add(0); + dataType.add(1); + break; + } + /** + * 数据类型(0:暂态系统;1:稳态系统;2:两个系统) + */ + deviceType.setDataType(dataType); + } + + + + /** + * 根据部门id集合获取监测点信息 + * + * @param directDeptDTO 入参deptIndex的直接子部门 + * @param deviceType + * @param ids 直接子部门以及后代部门id集合 + * @param deviceInfoParam + * @return + */ + private GeneralDeviceDTO getGeneralDeviceInfo(DeptDTO directDeptDTO, + DeviceType deviceType, + List ids, + DeviceInfoParam deviceInfoParam) { + GeneralDeviceDTO generalDeviceDTO = new GeneralDeviceDTO(); + generalDeviceDTO.setIndex(directDeptDTO.getId()); + // type:部门类型 0-非自定义;1-web自定义;2-App自定义;3-web测试 + if (directDeptDTO.getType() == 0) { + generalDeviceDTO.setName(directDeptDTO.getArea()); + } else { + generalDeviceDTO.setName(directDeptDTO.getName()); + } + // 根据部门ids集合查询是否绑定监测点 部门和监测点关联关系中间表:pq_dept_line 可以一对多 + List deptLines = deptLineService.selectDeptBindLines(ids); + // 返回空数据 + if (CollectionUtil.isEmpty(deptLines)) { + return generalDeviceDTO; + } + // 提取该部门及其子部门所有监测点id + List lineIds = deptLines.stream().map(DeptLine::getLineId).collect(Collectors.toList()); + // 获取line详细数据 :根据监测点id,获取所有监测点 联查 pq_line、pq_line_detail + List lines = terminalBaseService.getLineByCondition(lineIds, deviceInfoParam); + // 返回空数据 + if (CollectionUtil.isEmpty(lines)) { + return generalDeviceDTO; + } + + //1.筛选出母线id,理论上监测点的pids中第六个id为母线id 联查: pq_line t1 ,pq_voltage t2 + List voltageIds=lines.stream().map(Line::getPid).collect(Collectors.toList()); + //再根据电压等级筛选合法母线信息 + List voltages = terminalBaseService.getVoltageByCondition(voltageIds, deviceInfoParam.getScale()); + + //2.筛选出终端id,理论上监测点的pids中第五个id为终端id + List devIds=voltages.stream().map(Line::getPid).collect(Collectors.toList()); + // 再根据终端条件筛选合法终端信息 联查:pq_line t1,pq_device t2 + List devices = terminalBaseService.getDeviceByCondition(devIds, deviceType, deviceInfoParam.getManufacturer()); + + //3.筛选出变电站id,理论上监测点的pids中第四个id为变电站id 联查: pq_line t1 ,pq_substation t2 + List subIds=devices.stream().map(Line::getPid).collect(Collectors.toList()); + List sub = terminalBaseService.getSubByCondition(subIds, new ArrayList<>()); + + //筛选最终的数据 + dealDeviceData(generalDeviceDTO, lines, devices, voltages, sub); + return generalDeviceDTO; + } + + + private List filterDataByScale(List deviceInfos, List scales) { + List generalDeviceDTOS = new ArrayList<>(); + List subIds = new ArrayList<>(), lineIds = new ArrayList<>(); + for (GeneralDeviceDTO generalDeviceDTO : deviceInfos) { + subIds.addAll(generalDeviceDTO.getSubIndexes()); + lineIds.addAll(generalDeviceDTO.getLineIndexes()); + } + //如果电压等级集合为空,则查询所有的电压等级 + if (CollectionUtil.isEmpty(scales)) { + List scaleDictData = dictDataMapper.getDicDataByTypeCode(DicDataTypeEnum.DEV_VOLTAGE_STAND.getCode()); + scales = scaleDictData.stream().map(dictData -> { + SimpleDTO simpleDTO = new SimpleDTO(); + BeanUtil.copyProperties(dictData, simpleDTO); + return simpleDTO; + }).collect(Collectors.toList()); + } + //监测点为空,则返回空的分类数据 + if (CollectionUtil.isEmpty(lineIds)) { + return assembleCommonData(scales); + } + List lines = terminalBaseService.getLineById(lineIds); + for (SimpleDTO simpleDTO : scales) { + List voltageScaleIds = terminalBaseService.getSubIdByScale(subIds, simpleDTO.getId()); + generalDeviceDTOS.add(assembleDataByLine(simpleDTO, lines, voltageScaleIds, LineBaseEnum.SUB_LEVEL.getCode())); + } + return generalDeviceDTOS; + } + + + /** + * 处理各节点索引集合 + * + * @param generalDeviceDTO 终端信息综合体 + * @param lines 监测点信息 + * @param devices 终端信息 + */ + private void dealDeviceData(GeneralDeviceDTO generalDeviceDTO, List lines, List devices) { + List gdIndexes = new ArrayList<>(), subIndexes = new ArrayList<>(), deviceIndexes = new ArrayList<>(), voltageIndexes = new ArrayList<>(), lineIndexes = new ArrayList<>(); + //筛选出供电公司、变电站、终端索引集合 + for (Line device : devices) { + String[] ids = device.getPids().split(","); + gdIndexes.add(ids[2]); + subIndexes.add(ids[3]); + deviceIndexes.add(device.getId()); + } + //筛选出母线、监测点集合 + for (Line line : lines) { + String[] ids = line.getPids().split(","); + if (deviceIndexes.contains(ids[4])) { + lineIndexes.add(line.getId()); + voltageIndexes.add(ids[5]); + } + } + //排重,入参到终端综合体 + generalDeviceDTO.setGdIndexes(gdIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setSubIndexes(subIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setDeviceIndexes(deviceIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setVoltageIndexes(voltageIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setLineIndexes(lineIndexes.stream().distinct().collect(Collectors.toList())); + + } + + /** + * 取多条件筛选后的交集索引,填充到部门统计中 + * + * @param generalDeviceDTO 部门信息 + * @param lines 筛选后的监测点信息 + * @param devices 筛选后的终端信息 + * @param voltages 筛选后的母线信息 + */ + private void dealDeviceData(GeneralDeviceDTO generalDeviceDTO, List lines, List devices, List voltages, List sub) { + List gdIndexes = new ArrayList<>(), subIndexes = new ArrayList<>(), deviceIndexes = new ArrayList<>(), voltageIndexes = new ArrayList<>(), lineIndexes = new ArrayList<>(); + List devIds = devices.stream().map(Line::getId).distinct().collect(Collectors.toList()); + List volIds = voltages.stream().map(Line::getId).distinct().collect(Collectors.toList()); + List subIds = sub.stream().map(Line::getId).distinct().collect(Collectors.toList()); + for (Line line : lines) { + String[] idsArray = line.getPids().split(","); + //监测点同时满足条件筛选后的终端、母线信息,才是最终的结果 + if (devIds.contains(idsArray[LineBaseEnum.DEVICE_LEVEL.getCode()]) && + volIds.contains(idsArray[LineBaseEnum.SUB_V_LEVEL.getCode()])&& + subIds.contains(idsArray[LineBaseEnum.SUB_LEVEL.getCode()]) + ) { + gdIndexes.add(idsArray[LineBaseEnum.GD_LEVEL.getCode()]); + subIndexes.add(idsArray[LineBaseEnum.SUB_LEVEL.getCode()]); + deviceIndexes.add(idsArray[LineBaseEnum.DEVICE_LEVEL.getCode()]); + voltageIndexes.add(idsArray[LineBaseEnum.SUB_V_LEVEL.getCode()]); + lineIndexes.add(line.getId()); + } + } + //排重,入参到终端综合体 + generalDeviceDTO.setGdIndexes(gdIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setSubIndexes(subIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setDeviceIndexes(deviceIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setVoltageIndexes(voltageIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setLineIndexes(lineIndexes.stream().distinct().collect(Collectors.toList())); + } + + + + private List filterDataByLoadType(List deviceInfos, List loadType) { + List generalDeviceDTOS = new ArrayList<>(); + List lineIds = new ArrayList<>(); + for (GeneralDeviceDTO generalDeviceDTO : deviceInfos) { + lineIds.addAll(generalDeviceDTO.getLineIndexes()); + } + //如果干扰源集合为空,则查询所有的电压等级 + if (CollectionUtil.isEmpty(loadType)) { + List scaleDictData = dictDataMapper.getDicDataByTypeCode(DicDataTypeEnum.INTERFERENCE_SOURCE_TYPE.getCode()); + loadType = scaleDictData.stream().map(dictData -> { + SimpleDTO simpleDTO = new SimpleDTO(); + BeanUtil.copyProperties(dictData, simpleDTO); + return simpleDTO; + }).collect(Collectors.toList()); + } + //监测点为空,则返回空的分类数据 + if (CollectionUtil.isEmpty(lineIds)) { + return assembleCommonData(loadType); + } + List lines = terminalBaseService.getLineById(lineIds); + for (SimpleDTO simpleDTO : loadType) { + List lineLoadTypeIds = terminalBaseService.getLineIdByLoadType(lineIds, simpleDTO.getId()); + generalDeviceDTOS.add(assembleDataByLine(simpleDTO, lines, lineLoadTypeIds, LineBaseEnum.LINE_LEVEL.getCode())); + } + return generalDeviceDTOS; + } + + private List filterDataByManufacturer(List deviceInfos, List manufacturer) { + List generalDeviceDTOS = new ArrayList<>(); + List deviceIds = new ArrayList<>(), lineIds = new ArrayList<>(); + for (GeneralDeviceDTO generalDeviceDTO : deviceInfos) { + deviceIds.addAll(generalDeviceDTO.getDeviceIndexes()); + lineIds.addAll(generalDeviceDTO.getLineIndexes()); + } + //如果终端厂家集合为空,则查询所有的电压等级 + if (CollectionUtil.isEmpty(manufacturer)) { + List scaleDictData = dictDataMapper.getDicDataByTypeCode(DicDataTypeEnum.DEV_MANUFACTURER.getCode()); + manufacturer = scaleDictData.stream().map(dictData -> { + SimpleDTO simpleDTO = new SimpleDTO(); + BeanUtil.copyProperties(dictData, simpleDTO); + return simpleDTO; + }).collect(Collectors.toList()); + } + //监测点为空,则返回空的分类数据 + if (CollectionUtil.isEmpty(lineIds)) { + return assembleCommonData(manufacturer); + } + List lines = terminalBaseService.getLineById(lineIds); + for (SimpleDTO simpleDTO : manufacturer) { + List voltageScaleIds = terminalBaseService.getDeviceIdByManufacturer(deviceIds, simpleDTO.getId()); + generalDeviceDTOS.add(assembleDataByLine(simpleDTO, lines, voltageScaleIds, LineBaseEnum.DEVICE_LEVEL.getCode())); + } + return generalDeviceDTOS; + } + + private List filterDataByPowerFlag(List deviceInfos, List manufacturer) { + List generalDeviceDTOS = new ArrayList<>(); + List deviceIds = deviceInfos.stream().flatMap(x->x.getLineIndexes().stream()).collect(Collectors.toList()); + List lineIds = deviceInfos.stream().flatMap(x->x.getLineIndexes().stream()).collect(Collectors.toList()); + //监测点为空,则返回空的分类数据 + if (CollectionUtil.isEmpty(lineIds)) { + return assembleCommonData(manufacturer); + } + SimpleDTO dto; + List lines = terminalBaseService.getLineById(lineIds); + for (int i = 0; i < 6; i++) { + List powerFlagIds = terminalBaseService.getDeviceIdByPowerFlag(deviceIds, i); + dto=new SimpleDTO(); + PowerFlagEnum enumByCode = PowerFlagEnum.getPowerFlagEnumByCode(i); + dto.setId(enumByCode.getCode().toString()); + dto.setName(enumByCode.getMessage()); + generalDeviceDTOS.add(assembleDataByLine(dto, lines, powerFlagIds, LineBaseEnum.LINE_LEVEL.getCode())); + } + + return generalDeviceDTOS; + } + + + /** + * 当该部门不存在监测点时,返回空的分类数据 + * + * @param simpleDTOS 分类类别 + * @return . + */ + private List assembleCommonData(List simpleDTOS) { + return simpleDTOS.stream().map(this::assembleData).collect(Collectors.toList()); + } + + /** + * 当该部门不存在监测点时,返回空的分类数据 + * + * @param simpleDTO 基础数据 + * @return . + */ + private GeneralDeviceDTO assembleData(SimpleDTO simpleDTO) { + GeneralDeviceDTO generalDeviceDTO = new GeneralDeviceDTO(); + generalDeviceDTO.setName(simpleDTO.getName()); + generalDeviceDTO.setIndex(simpleDTO.getId()); + return generalDeviceDTO; + } + + + /** + * 筛选对应等级的id + * + * @param simpleDTO 分类信息 + * @param lines 所有监测点 + * @param keyIds 待筛选的id + * @param level 待筛选的层级 + */ + private GeneralDeviceDTO assembleDataByLine(SimpleDTO simpleDTO, List lines, List keyIds, Integer level) { + GeneralDeviceDTO generalDeviceDTO = assembleData(simpleDTO); + if (CollectionUtil.isNotEmpty(keyIds)) { + List tempLines = lines.stream().filter(line -> { + String[] idsArray = line.getPids().split(","); + if (level.equals(LineBaseEnum.LINE_LEVEL.getCode())) { + return keyIds.contains(line.getId()); + } else { + return keyIds.contains(idsArray[level]); + } + }).collect(Collectors.toList()); + List gdIndexes = new ArrayList<>(), subIndexes = new ArrayList<>(), deviceIndexes = new ArrayList<>(), voltageIndexes = new ArrayList<>(), lineIndexes = new ArrayList<>(); + for (Line line : tempLines) { + String[] idsArray = line.getPids().split(","); + gdIndexes.add(idsArray[LineBaseEnum.GD_LEVEL.getCode()]); + subIndexes.add(idsArray[LineBaseEnum.SUB_LEVEL.getCode()]); + deviceIndexes.add(idsArray[LineBaseEnum.DEVICE_LEVEL.getCode()]); + voltageIndexes.add(idsArray[LineBaseEnum.SUB_V_LEVEL.getCode()]); + lineIndexes.add(line.getId()); + } + //排重,入参到终端综合体 + generalDeviceDTO.setGdIndexes(gdIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setSubIndexes(subIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setDeviceIndexes(deviceIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setVoltageIndexes(voltageIndexes.stream().distinct().collect(Collectors.toList())); + generalDeviceDTO.setLineIndexes(lineIndexes.stream().distinct().collect(Collectors.toList())); + } + return generalDeviceDTO; + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/CommTerminalService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/CommTerminalService.java new file mode 100644 index 0000000..a51bf1b --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/CommTerminalService.java @@ -0,0 +1,33 @@ +package com.njcn.product.terminal.mysqlTerminal.service; + + + +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.DeptGetChildrenMoreDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LineDevGetDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.DeptGetLineParam; + +import java.util.List; +import java.util.Map; + +/** + * pqs + * + * @author cdf + * @date 2023/5/10 + */ + +public interface CommTerminalService { + + + + /** + * 根据单位获取单位监测点 + * @author cdf + * @date 2023/5/10 + */ + List deptGetLine(DeptGetLineParam deptGetLineParam); + + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/DeptLineService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/DeptLineService.java new file mode 100644 index 0000000..b06c96a --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/DeptLineService.java @@ -0,0 +1,38 @@ +package com.njcn.product.terminal.mysqlTerminal.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LineDevGetDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.DeptLine; + + +import java.util.List; +import java.util.Map; + +/** + * @author denghuajun + * @date 2022/1/12 17:30 + * + */ +public interface DeptLineService extends IService { + + + /** + * 根据部门ids集合查询是否绑定监测点 + * @param ids 部门ids + * @return 查询结果 + */ + List selectDeptBindLines(List ids); + + + /** + * 获取根据单位分组的监测点集合信息 + * param 系统类型 0暂态 1稳态 2 双类型 + * param type 台账类型 1监测点 2母线 3 装置 + * @author cdf + * @date 2023/5/10 + */ + Map> lineDevGet(List devType, Integer type, Integer lineRunFlag); + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/LineService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/LineService.java new file mode 100644 index 0000000..ba88b15 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/LineService.java @@ -0,0 +1,39 @@ +package com.njcn.product.terminal.mysqlTerminal.service; + +import com.baomidou.mybatisplus.extension.service.IService; + +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerTreeDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.DeviceInfoParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Line; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.LineDetailDataVO; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.TerminalTree; + +import java.util.List; + + +/** + * 监测点类 + * @author denghuajun + * @date 2022/2/23 + * + */ +public interface LineService extends IService { + + List getTree(); + + /** + * 5层树排除设备 母线监测点合并 + * + * @author cdf + * @date 2022/1/13 + */ + List getTerminalTreeForFive(DeviceInfoParam deviceInfoParam); + + /** + * 获取监测点详情 + * @param id 监测点id + * @return 结果 + */ + LineDetailDataVO getLineDetailData(String id); + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/TerminalBaseService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/TerminalBaseService.java new file mode 100644 index 0000000..51b32ca --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/TerminalBaseService.java @@ -0,0 +1,130 @@ +package com.njcn.product.terminal.mysqlTerminal.service; + +import com.njcn.common.pojo.dto.SimpleDTO; + +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.DeviceType; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.DeviceInfoParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Line; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * pqs + * + * @author cdf + * @date 2022/1/4 + */ +public interface TerminalBaseService { + + + + /** + * 根据监测点id,获取所有监测点 + * + * @param lineIds 监测点id + * @return 监测点数据 + */ + List getLineById(List lineIds); + + /** + * 根据监测点id,获取所有监测点 + * + * @param lineIds 监测点id + * @param deviceInfoParam 监测点查询条件 + * @return 监测点数据 + */ + List getLineByCondition(List lineIds, DeviceInfoParam deviceInfoParam); + + /** + * 根据终端id,获取所有对应终端 + * + * @param devIds 终端id + * @param deviceType 终端条件筛选 + * @return 终端数据 + */ + List getDeviceById(List devIds, DeviceType deviceType); + + + /** + * 功能描述: 根据id查询变电站信息 + * + * @param list + * @return java.util.List + * @author xy + * @date 2022/2/21 18:47 + */ + List getSubstationById(List list); + + + /** + * 查询终端信息 + * + * @param devIds 终端索引 + * @param deviceType 终端筛选条件 + * @param manufacturer 终端厂家 + */ + List getDeviceByCondition(List devIds, DeviceType deviceType, List manufacturer); + + /** + * 查询母线信息 + * + * @param voltageIds 母线索引 + * @param scale 电压等级 + */ + List getVoltageByCondition(List voltageIds, List scale); + + /** + * 查询变电站信息 + * + * @param subIds 变电站索引 + * @param scale 电压等级 + */ + List getSubByCondition(List subIds, List scale); + + /** + * 根据指定电压等级查询母线id + * + * @param voltageIds 母线id + * @param scale 电压等级 + */ + /* List getVoltageIdByScale(List voltageIds, String scale); +*/ + /** + * 根据指定电压等级查询母线id + * @param subIds + * @param scale + * @return: java.util.List + * @Author: wr + * @Date: 2024/10/12 15:58 + */ + List getSubIdByScale(List subIds, String scale); + + /** + * 根据干扰源获取对应的监测点id + * + * @param lineIds 监测点id + * @param loadType 干扰源类型 + */ + List getLineIdByLoadType(List lineIds, String loadType); + + /** + * 根据终端厂家获取对应的终端id + * + * @param deviceIds 终端id + * @param manufacturer 终端厂家 + */ + List getDeviceIdByManufacturer(List deviceIds, String manufacturer); + /** + * 根据监测点性质获取监测信息 + * + * @param lineIds 监测点id + * @param manufacturer 监测点性质 + */ + List getDeviceIdByPowerFlag(List lineIds, Integer manufacturer); + + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/UserReportPOService.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/UserReportPOService.java new file mode 100644 index 0000000..9175e87 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/UserReportPOService.java @@ -0,0 +1,25 @@ +package com.njcn.product.terminal.mysqlTerminal.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.UserReportParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.UserReportPO; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.UserLedgerVO; + +import java.util.List; + +/** + * Description: + * Date: 2024/4/25 10:07【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface UserReportPOService extends IService { + + + + List selectUserList(UserReportParam userReportParam); + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/CommTerminalServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/CommTerminalServiceImpl.java new file mode 100644 index 0000000..bae8cce --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/CommTerminalServiceImpl.java @@ -0,0 +1,130 @@ +package com.njcn.product.terminal.mysqlTerminal.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.njcn.common.pojo.enums.common.ServerEnum; +import com.njcn.common.utils.EnumUtils; + +import com.njcn.product.cnuser.user.pojo.dto.DeptDTO; +import com.njcn.product.cnuser.user.mapper.DeptMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.DeptGetBase; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.DeptGetChildrenMoreDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LineDevGetDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.DeptGetLineParam; +import com.njcn.product.terminal.mysqlTerminal.service.CommTerminalService; +import com.njcn.product.terminal.mysqlTerminal.service.DeptLineService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * pqs + * + * @author cdf + * @date 2023/5/10 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class CommTerminalServiceImpl implements CommTerminalService { + + //redis前缀 + private final String commTerminal = "commTerminal#"; + + private final DeptLineService deptLineService; + private final DeptMapper deptMapper; + + + @Override + public List deptGetLine(DeptGetLineParam deptGetLineParam) { + List result = new ArrayList<>(); + List temDept = getDeptChildrenByParent(deptGetLineParam); + Map deptMap = temDept.stream().collect(Collectors.toMap(DeptGetBase::getUnitId, DeptGetBase::getUnitName)); + Map> map = deptLineService.lineDevGet(filterDataTypeNew(deptGetLineParam.getServerName()), + 1,deptGetLineParam.getLineRunFlag()); + temDept.forEach(item -> { + DeptGetChildrenMoreDTO deptGetChildrenMoreDTO = new DeptGetChildrenMoreDTO(); + deptGetChildrenMoreDTO.setUnitId(item.getUnitId()); + deptGetChildrenMoreDTO.setUnitName(item.getUnitName()); + deptGetChildrenMoreDTO.setUnitChildrenList(item.getUnitChildrenList()); + deptGetChildrenMoreDTO.setDeptLevel(item.getDeptLevel()); + List deptIds = item.getUnitChildrenList(); + if (CollectionUtil.isNotEmpty(deptIds)) { + List lineList = new ArrayList<>(); + deptIds.forEach(i -> { + if (map.containsKey(i)) { + map.get(i).forEach(x->{ + if(deptMap.containsKey(x.getUnitId())){ + x.setUnitName(deptMap.get(x.getUnitId())); + } + }); + lineList.addAll(map.get(i)); + } + }); + + //去重 + ArrayList collect = lineList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>( + Comparator.comparing(LineDevGetDTO::getPointId) + )), ArrayList::new)); + + deptGetChildrenMoreDTO.setLineBaseList(collect); + } + result.add(deptGetChildrenMoreDTO); + }); + return result; + } + + /** + * 基础获取单位信息 + * @author cdf + * @date 2023/5/10 + */ + + public List getDeptChildrenByParent(DeptGetLineParam deptGetLineParam) { + /*List redisResult = (List) redisUtil.getObjectByKey(commTerminal + deptGetLineParam.getDeptId()); + if (CollectionUtil.isNotEmpty(redisResult)) { + return redisResult; + }*/ + List result = new ArrayList<>(); + List deptDTOList = deptMapper.getDeptDescendantIndexes(deptGetLineParam.getDeptId(), Stream.of(0, 1).collect(Collectors.toList())); + deptDTOList.forEach(it -> { + DeptGetBase deptGetBase = new DeptGetBase(); + deptGetBase.setUnitId(it.getId()); + deptGetBase.setUnitName(it.getName()); + deptGetBase.setDeptLevel(getDeptLevel(it.getPids())); + List deptChildren = deptDTOList.stream().filter(deptDTO -> deptDTO.getPids().contains(it.getId())).map(DeptDTO::getId).collect(Collectors.toList()); + deptChildren.add(it.getId()); + deptGetBase.setUnitChildrenList(deptChildren); + result.add(deptGetBase); + }); + return result; + } + + + private Integer getDeptLevel(String pids) { + List list = Arrays.stream(pids.split(",")).map(String::trim).collect(Collectors.toList()); + return list.size(); + } + + private List filterDataTypeNew(String serverName) { + List devType = new ArrayList<>(); + devType.add(2); + ServerEnum serverEnum = EnumUtils.getServerEnumByName(serverName); + switch (serverEnum) { + case EVENT: + devType.add(0); + break; + case HARMONIC: + devType.add(1); + break; + default: + devType.add(0); + devType.add(1); + break; + } + return devType; + } +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/DeptLineServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/DeptLineServiceImpl.java new file mode 100644 index 0000000..eab7dfd --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/DeptLineServiceImpl.java @@ -0,0 +1,48 @@ +package com.njcn.product.terminal.mysqlTerminal.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.terminal.mysqlTerminal.mapper.DeptLineMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LineDevGetDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.DeptLine; +import com.njcn.product.terminal.mysqlTerminal.service.DeptLineService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author denghuajun + * @date 2022/1/12 17:32 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DeptLineServiceImpl extends ServiceImpl implements DeptLineService { + + private final DeptLineMapper deptLineMapper; + + + @Override + public List selectDeptBindLines(List ids) { + return this.lambdaQuery().in(DeptLine::getId, ids).list(); + } + + @Override + public Map> lineDevGet(List devDataType, Integer type, Integer lineRunFlag) { + List deptLines = deptLineMapper.lineDevGet(devDataType, type, lineRunFlag); + return deptLines.stream().collect(Collectors.groupingBy(LineDevGetDTO::getUnitId)); + } + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/LineServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/LineServiceImpl.java new file mode 100644 index 0000000..ce1f1f1 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/LineServiceImpl.java @@ -0,0 +1,336 @@ +package com.njcn.product.terminal.mysqlTerminal.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.utils.PubUtils; +import com.njcn.product.system.dict.mapper.DictDataMapper; +import com.njcn.product.system.other.mapper.AreaMapper; +import com.njcn.product.terminal.mysqlTerminal.mapper.*; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.GeneralDeviceDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.LedgerTreeDTO; +import com.njcn.product.terminal.mysqlTerminal.pojo.enums.LineBaseEnum; +import com.njcn.product.terminal.mysqlTerminal.pojo.enums.StatisticsEnum; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.DeviceInfoParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.UserReportParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Device; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Line; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.LineDetail; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.LineDataVO; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.LineDetailDataVO; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.TerminalTree; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.UserLedgerVO; +import com.njcn.product.terminal.mysqlTerminal.service.CommGeneralService; +import com.njcn.product.terminal.mysqlTerminal.service.LineService; +import com.njcn.product.terminal.mysqlTerminal.service.UserReportPOService; +import com.njcn.product.terminal.utils.TerminalUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * 监测点类 + * + * @author denghuajun + * @date 2022/2/23 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class LineServiceImpl extends ServiceImpl implements LineService { + + private final DeviceMapper deviceMapper; + + private final TreeMapper treeMapper; + private final CommGeneralService commGeneralService; + private final UserReportPOService userReportPOService; + private final DictDataMapper dictDataMapper; + private final LineDetailMapper lineDetailMapper; + private final AreaMapper areaMapper; + private final VoltageMapper voltageMapper; + + @Override + public List getTree() { + + List deviceList = deviceMapper.selectList(new LambdaQueryWrapper().eq(Device::getRunFlag,0).eq(Device::getDevModel,1)); + List devList = this.baseMapper.selectList(new LambdaQueryWrapper().in(Line::getId,deviceList.stream().map(Device::getId).collect(Collectors.toList()))); + + List ledgerList = this.baseMapper.selectList(new LambdaQueryWrapper().in(Line::getLevel, Stream.of(LineBaseEnum.GD_LEVEL.getCode(),LineBaseEnum.SUB_LEVEL.getCode(),LineBaseEnum.SUB_V_LEVEL.getCode(),LineBaseEnum.LINE_LEVEL.getCode()).collect(Collectors.toList()))); + List monitorList = ledgerList.stream().filter(it->Objects.equals(it.getLevel(),LineBaseEnum.LINE_LEVEL.getCode())).collect(Collectors.toList()); + List busBarList = ledgerList.stream().filter(it->Objects.equals(it.getLevel(),LineBaseEnum.SUB_V_LEVEL.getCode())).collect(Collectors.toList()); + Map busBarMap = busBarList.stream().collect(Collectors.toMap(Line::getId, Function.identity())); + List stationList = ledgerList.stream().filter(it->Objects.equals(it.getLevel(),LineBaseEnum.SUB_LEVEL.getCode())).collect(Collectors.toList()); + List gdList = ledgerList.stream().filter(it->Objects.equals(it.getLevel(),LineBaseEnum.GD_LEVEL.getCode())).collect(Collectors.toList()); + + + + List lineDtoList = monitorList.stream().map(it->{ + LedgerTreeDTO oracleLedgerTreeDTO = new LedgerTreeDTO(); + oracleLedgerTreeDTO.setId(it.getId()); + String busName = busBarMap.get(it.getPid()).getName(); + oracleLedgerTreeDTO.setName(busName+"_"+it.getName()); + oracleLedgerTreeDTO.setPid(it.getPids().split(StrUtil.COMMA)[LineBaseEnum.DEVICE_LEVEL.getCode()]); + oracleLedgerTreeDTO.setLevel(4); + return oracleLedgerTreeDTO; + }).collect(Collectors.toList()); + + List devDtoList = devList.stream().map(it->{ + LedgerTreeDTO oracleLedgerTreeDTO = new LedgerTreeDTO(); + oracleLedgerTreeDTO.setId(it.getId()); + oracleLedgerTreeDTO.setName(it.getName()); + oracleLedgerTreeDTO.setPid(it.getPid()); + oracleLedgerTreeDTO.setLevel(3); + oracleLedgerTreeDTO.setChildren(lineDtoList.stream().filter(line-> Objects.equals(it.getId(),line.getPid())).collect(Collectors.toList())); + return oracleLedgerTreeDTO; + }).collect(Collectors.toList()); + List stationDtoList = stationList.stream().map(it->{ + LedgerTreeDTO oracleLedgerTreeDTO = new LedgerTreeDTO(); + oracleLedgerTreeDTO.setId(it.getId()); + oracleLedgerTreeDTO.setName(it.getName()); + oracleLedgerTreeDTO.setPid(it.getPid()); + oracleLedgerTreeDTO.setLevel(2); + oracleLedgerTreeDTO.setChildren(devDtoList.stream().filter(dev->Objects.equals(it.getId(),dev.getPid())).collect(Collectors.toList())); + return oracleLedgerTreeDTO; + }).collect(Collectors.toList()); + List gdDtoList = gdList.stream().map(it->{ + LedgerTreeDTO oracleLedgerTreeDTO = new LedgerTreeDTO(); + oracleLedgerTreeDTO.setId(it.getId()); + oracleLedgerTreeDTO.setName(it.getName()); + oracleLedgerTreeDTO.setPid("0"); + oracleLedgerTreeDTO.setLevel(1); + oracleLedgerTreeDTO.setChildren(stationDtoList.stream().filter(sub->Objects.equals(it.getId(),sub.getPid())).collect(Collectors.toList())); + return oracleLedgerTreeDTO; + }).collect(Collectors.toList()); + return gdDtoList; + } + + + + + /** + * 5层树排除设备 母线监测点合并 + * + * @author cdf + * @date 2022/1/13 + */ + @Override + public List getTerminalTreeForFive(DeviceInfoParam deviceInfoParam) { + //deviceInfoParam.setDeptIndex(RequestUtil.getDeptIndex()); + // 获取所有数据 + List generalDeviceDTOList = commGeneralService.getDeviceInfo(deviceInfoParam, Stream.of(0).collect(Collectors.toList()), Stream.of(1).collect(Collectors.toList())); + // 判断所有数据集合状态 + if (CollectionUtil.isNotEmpty(generalDeviceDTOList)) { + // 创建集合 + List taiZhang = new ArrayList<>(); + // 获取用户 + UserReportParam userReportParam = new UserReportParam(); + List userReportPOList = userReportPOService.selectUserList(userReportParam); + userReportPOList = userReportPOList.stream().filter(it -> StrUtil.isNotBlank(it.getStationId())).collect(Collectors.toList()); + Map userMap = userReportPOList.stream().collect(Collectors.toMap(UserLedgerVO::getId, Function.identity())); + + // 遍历集合 + for (GeneralDeviceDTO generalDeviceDTO : generalDeviceDTOList) { + // 创建实体类 + TerminalTree terminalTree = new TerminalTree(); + // 判断监测点索引集合状态 + if (CollectionUtils.isEmpty(generalDeviceDTO.getLineIndexes())) { + continue; + } + // 通过供电公司索引查询省会 + List proList = treeMapper.getProvinceList(generalDeviceDTO.getGdIndexes()); + // 通过供电公司索引查询供电公司信息 + List gdList = treeMapper.getGdList(generalDeviceDTO.getGdIndexes()); + // 通过供电站索引查询供电站信息 + List subList = treeMapper.getSubList(generalDeviceDTO.getSubIndexes()); + // 通过监测点索引查询监测点信息 + List lineList = treeMapper.getLineList(generalDeviceDTO.getLineIndexes()); + + List userLineList = lineList.stream().filter(it->StrUtil.isNotBlank(it.getObjId())).collect(Collectors.toList()); + List otherLineList = lineList.stream().filter(it->StrUtil.isBlank(it.getObjId())).collect(Collectors.toList()); + + Map> temMap = new HashMap<>(); + if(CollUtil.isNotEmpty(userLineList)) { + Map> objMap = userLineList.stream().collect(Collectors.groupingBy(TerminalTree::getObjId)); + List temList = new ArrayList<>(); + objMap.forEach((objId, monitorList) -> { + UserLedgerVO userLedgerVO = userMap.get(objId); + TerminalTree tree = new TerminalTree(); + tree.setLevel(LineBaseEnum.USER_LEVEL.getCode()); + tree.setPid(userLedgerVO.getStationId()); + tree.setId(userLedgerVO.getId()); + tree.setChildren(monitorList); + int devSize = (int) monitorList.stream().map(x -> { + // 获取父id字符串,通过 逗号 分割 成一个数组 + String[] pid = x.getPids().split(StrUtil.COMMA); + return pid[LineBaseEnum.DEVICE_LEVEL.getCode()]; + }).distinct().count(); + tree.setName(userLedgerVO.getProjectName()); + //特殊处理,用户层级下面的装置数量临时存到pids字段。 + tree.setPids(String.valueOf(devSize)); + temList.add(tree); + }); + temMap = temList.stream().collect(Collectors.groupingBy(TerminalTree::getPid)); + } + + + + //处理变电站 + dealChildrenData(subList, otherLineList, temMap,true); + + //监测点前面加序号,后面不需要删除下面两行就行 + //Integer[] arr = {1}; + //subList.forEach(item->item.getChildren().forEach(it->it.setName((arr[0]++ +"_"+it.getName())))); + //处理供电公司 + dealChildrenData(gdList, subList, null,false); + + if (deviceInfoParam.getStatisticalType().getCode().equalsIgnoreCase(StatisticsEnum.POWER_NETWORK.getCode())) { + terminalTree.setChildren(gdList); + } else { + //还需要额外处理省会 + dealChildrenData(proList, gdList, null,false); + terminalTree.setChildren(proList); + } + terminalTree.setId(generalDeviceDTO.getIndex()); + terminalTree.setName(generalDeviceDTO.getName()); + terminalTree.setLevel(0); + taiZhang.add(terminalTree); + } + return taiZhang; + } else { + return new ArrayList<>(); + } + } + + + @Override + public LineDetailDataVO getLineDetailData(String id) { + if (StringUtils.isEmpty(id)) { + return new LineDetailDataVO(); + } else { + //根据id查询当前信息的pids + List pids = Arrays.asList(this.baseMapper.selectById(id).getPids().split(",")); + List list = new ArrayList(pids); + list.add(id); + List lineDataVOList = this.baseMapper.getLineDetail(list); + LineDetailDataVO lineDetailDataVO = new LineDetailDataVO(); + String areaId = "", devId = "", voId = ""; + for (LineDataVO lineDataVO : lineDataVOList) { + switch (lineDataVO.getLevel()) { + case 1: + areaId = lineDataVO.getName(); + break; + case 2: + lineDetailDataVO.setGdName(lineDataVO.getName()); + break; + case 3: + lineDetailDataVO.setBdName(lineDataVO.getName()); + break; + case 4: + devId = lineDataVO.getId(); + lineDetailDataVO.setDevName(lineDataVO.getName()); + break; + case 5: + voId = lineDataVO.getId(); + break; + case 6: + lineDetailDataVO.setLineName(lineDataVO.getName()); + break; + default: + break; + } + } + lineDetailDataVO.setAreaName(areaMapper.selectById(areaId).getName()); + lineDetailDataVO.setScale(dictDataMapper.selectById(voltageMapper.selectById(voId).getScale()).getName()); + LineDetail lineDetail = lineDetailMapper.selectById(id); + Device device = deviceMapper.selectById(devId); + lineDetailDataVO.setManufacturer(dictDataMapper.selectById(device.getManufacturer()).getName()); + lineDetailDataVO.setComFlag(TerminalUtils.comFlag(device.getComFlag())); + lineDetailDataVO.setRunFlag(TerminalUtils.lineRunFlag(lineDetail.getRunFlag())); + lineDetailDataVO.setIp(device.getIp()); + lineDetailDataVO.setLoginTime(device.getLoginTime()); + lineDetailDataVO.setDevId(device.getId()); + lineDetailDataVO.setBusinessType(dictDataMapper.selectById(lineDetail.getBusinessType()).getName()); + lineDetailDataVO.setLoadType(dictDataMapper.selectById(lineDetail.getLoadType()).getName()); + lineDetailDataVO.setObjName(lineDetail.getObjName()); + lineDetailDataVO.setId(lineDetail.getNum()); + lineDetailDataVO.setPtType(TerminalUtils.ptType(lineDetail.getPtType())); + lineDetailDataVO.setPt(lineDetail.getPt1() + "/" + lineDetail.getPt2()); + lineDetailDataVO.setCt(lineDetail.getCt1() + "/" + lineDetail.getCt2()); + lineDetailDataVO.setDealCapacity(lineDetail.getDealCapacity()); + lineDetailDataVO.setDevCapacity(lineDetail.getDevCapacity()); + lineDetailDataVO.setShortCapacity(lineDetail.getShortCapacity()); + lineDetailDataVO.setStandardCapacity(lineDetail.getStandardCapacity()); + lineDetailDataVO.setTimeInterval(lineDetail.getTimeInterval()); + lineDetailDataVO.setOwner(lineDetail.getOwner()); + lineDetailDataVO.setOwnerDuty(lineDetail.getOwnerDuty()); + lineDetailDataVO.setOwnerTel(lineDetail.getOwnerTel()); + lineDetailDataVO.setWiringDiagram(lineDetail.getWiringDiagram()); + lineDetailDataVO.setPtPhaseType(lineDetail.getPtPhaseType()); + lineDetailDataVO.setUpdateTime(device.getUpdateTime()); + return lineDetailDataVO; + } + + } + + /** + * 处理变电站 + * + * @param targetData + * @param childrenData + * @param isLine + */ + private void dealChildrenData(List targetData, List childrenData,Map> userLineMap, boolean isLine) { + // 创建一个map集合,用于封装对象 + Map> groupLine; + if (isLine) { + // 通过stream流分组 + groupLine = childrenData.stream().collect(Collectors.groupingBy(terminalTree -> { + // 获取父id字符串,通过 逗号 分割 成一个数组 + String[] pid = terminalTree.getPids().split(StrUtil.COMMA); + return pid[LineBaseEnum.SUB_LEVEL.getCode()]; + })); + } else { + groupLine = childrenData.stream().collect(Collectors.groupingBy(TerminalTree::getPid)); + } + //变电站 + targetData.forEach(terminalTree -> { + List terminalTrees = new ArrayList<>(); + if(groupLine.containsKey(terminalTree.getId())) { + terminalTrees.addAll(groupLine.get(terminalTree.getId()).stream().sorted(Comparator.comparing(TerminalTree::getSort)).collect(Collectors.toList())); + } + if (isLine) { + //变电站集合 + int size = (int) terminalTrees.stream().map(x -> { + // 获取父id字符串,通过 逗号 分割 成一个数组 + String[] pid = x.getPids().split(StrUtil.COMMA); + return pid[LineBaseEnum.DEVICE_LEVEL.getCode()]; + }).distinct().count(); + + int devSize = 0; + if(userLineMap.containsKey(terminalTree.getId())){ + List userList = userLineMap.get(terminalTree.getId()); + devSize= (int) userList.stream().mapToDouble(it->Integer.parseInt(it.getPids())).sum(); + terminalTrees.addAll(userList); + } + int sumDev = size+devSize; + terminalTree.setName(terminalTree.getName() + "(" +sumDev+ "台装置)"); + terminalTree.setChildren(terminalTrees); + } else { + terminalTree.setChildren(terminalTrees); + } + }); + } + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/TerminalBaseServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/TerminalBaseServiceImpl.java new file mode 100644 index 0000000..07cc70c --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/TerminalBaseServiceImpl.java @@ -0,0 +1,196 @@ +package com.njcn.product.terminal.mysqlTerminal.service.impl; + +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.dto.SimpleDTO; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.LogUtil; +import com.njcn.common.utils.PubUtils; + +import com.njcn.oss.constant.OssPath; +import com.njcn.oss.utils.FileStorageUtil; + +import com.njcn.product.terminal.mysqlTerminal.mapper.LineMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.dto.DeviceType; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.DeviceInfoParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.Line; + +import com.njcn.product.terminal.mysqlTerminal.service.TerminalBaseService; +import com.njcn.web.utils.RequestUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.net.HttpURLConnection; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * pqs + * + * @author cdf + * @date 2022/1/4 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TerminalBaseServiceImpl extends ServiceImpl implements TerminalBaseService { + + private final LineMapper lineMapper; + + + + + + + @Override + public List getSubstationById(List list) { + return this.lambdaQuery().in(Line::getId, list).list(); + } + + @Override + public List getLineById(List lineIds) { + return Collections.emptyList(); + } + + @Override + public List getLineByCondition(List ids, DeviceInfoParam deviceInfoParam) { + return this.baseMapper.getLineByCondition(ids, deviceInfoParam); + } + + @Override + public List getDeviceById(List devIds, DeviceType deviceType) { + return Collections.emptyList(); + } + + @Override + public List getDeviceByCondition(List devIds, DeviceType deviceType, List manufacturer) { + return this.baseMapper.getDeviceByCondition(devIds, deviceType, manufacturer); + } + + @Override + public List getVoltageByCondition(List voltageIds, List scale) { + return this.baseMapper.getVoltageByCondition(voltageIds, scale); + } + + @Override + public List getSubByCondition(List subIds, List scale) { + return this.baseMapper.getSubByCondition(subIds, scale); + } +/* + + @Override + public List getVoltageIdByScale(List voltageIds, String scale) { + return this.baseMapper.getVoltageIdByScale(voltageIds, scale); + } +*/ + + @Override + public List getSubIdByScale(List subIds, String scale) { + return this.baseMapper.getSubIdByScale(subIds, scale); + } + + @Override + public List getLineIdByLoadType(List lineIds, String loadType) { + return this.baseMapper.getLineIdByLoadType(lineIds, loadType); + } + + @Override + public List getDeviceIdByManufacturer(List deviceIds, String manufacturer) { + return this.baseMapper.getDeviceIdByManufacturer(deviceIds, manufacturer); + } + + @Override + public List getDeviceIdByPowerFlag(List lineIds, Integer manufacturer) { + return this.baseMapper.getDeviceIdByPowerFlag(lineIds, manufacturer); + } + + + + + + + + + /** + * 根据条件查询出台账信息 + * + * @param name 名称 + * @param pid 父id + * @param level 层级 + * @param state 状态 0 删除 1 正常 + * @return 终端信息 + */ + private Line queryLine(LambdaQueryWrapper lineLambdaQueryWrapper, String name, String pid, Integer level, Integer state) { + lineLambdaQueryWrapper.clear(); + lineLambdaQueryWrapper.eq(Line::getName, name) + .eq(Line::getPid, pid) + .eq(Line::getLevel, level) + .eq(Line::getState, state); + return this.baseMapper.selectOne(lineLambdaQueryWrapper); + } + + /** + * 组装台账信息,稍后入库 + * + * @param name 名称 + * @param level 等级 + * @param pid 父ID + * @param pids 上层所有ID + */ + private Line assembleLine(String name, Integer level, String pid, List pids) { + Line line = new Line(); + line.setName(name); + line.setLevel(level); + line.setPid(pid); + line.setPids(String.join(",", pids)); + line.setSort(0); + line.setState(DataStateEnum.ENABLE.getCode()); + return line; + } + + private Line assembleLine(String name, Integer level, String pid, String pids, Integer sort) { + Line line = new Line(); + line.setName(name); + line.setLevel(level); + line.setPid(pid); + line.setPids(pids); + line.setSort(sort); + line.setState(DataStateEnum.ENABLE.getCode()); + return line; + } + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/UserReportPOServiceImpl.java b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/UserReportPOServiceImpl.java new file mode 100644 index 0000000..c13813d --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/mysqlTerminal/service/impl/UserReportPOServiceImpl.java @@ -0,0 +1,54 @@ +package com.njcn.product.terminal.mysqlTerminal.service.impl; + + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.product.terminal.mysqlTerminal.mapper.UserReportPOMapper; +import com.njcn.product.terminal.mysqlTerminal.pojo.param.UserReportParam; +import com.njcn.product.terminal.mysqlTerminal.pojo.po.UserReportPO; +import com.njcn.product.terminal.mysqlTerminal.pojo.vo.UserLedgerVO; +import com.njcn.product.terminal.mysqlTerminal.service.UserReportPOService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Description: + * Date: 2024/4/25 10:07【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +@RequiredArgsConstructor +public class UserReportPOServiceImpl extends ServiceImpl implements UserReportPOService { + + + + + + @Override + public List selectUserList(UserReportParam userReportParam) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + + if(StrUtil.isNotBlank(userReportParam.getCity())){ + lambdaQueryWrapper.in(UserReportPO::getCity,Stream.of(userReportParam.getCity()).collect(Collectors.toList())); + } + if(StrUtil.isNotBlank(userReportParam.getStationId())){ + lambdaQueryWrapper.eq(UserReportPO::getStationId,userReportParam.getStationId()); + } + + lambdaQueryWrapper.eq(UserReportPO::getState,DataStateEnum.ENABLE.getCode()); + + List list = this.list(lambdaQueryWrapper); + return BeanUtil.copyToList(list,UserLedgerVO.class); + } + + + +} diff --git a/cn-terminal/src/main/java/com/njcn/product/terminal/utils/TerminalUtils.java b/cn-terminal/src/main/java/com/njcn/product/terminal/utils/TerminalUtils.java new file mode 100644 index 0000000..e2ac660 --- /dev/null +++ b/cn-terminal/src/main/java/com/njcn/product/terminal/utils/TerminalUtils.java @@ -0,0 +1,79 @@ +package com.njcn.product.terminal.utils; + +/** + * @Author: cdf + * @CreateTime: 2025-09-09 + * @Description: + */ +public class TerminalUtils { + + public static String comFlag(Integer comFlag) { + switch (comFlag) { + case 0: + return "中断"; + case 1: + return "正常"; + default: + return ""; + } + } + + public static String runFlag(Integer runFlag) { + switch (runFlag) { + case 0: + return "投运"; + case 1: + return "热备用"; + case 2: + return "停运"; + default: + return ""; + } + } + + //监测点运行状态(0:投运;1:检修;2:停运;3:调试;4:退运) + public static String lineRunFlag(Integer runFlag) { + switch (runFlag) { + case 0: + return "投运"; + case 1: + return "检修"; + case 2: + return "停运"; + case 3: + return "调试"; + case 4: + return "退运"; + default: + return ""; + } + } + + public static Integer getRunFlag(String runFlag) { + switch (runFlag) { + case "投运": + return 0; + case "热备用": + return 1; + case "停运": + return 2; + default: + return -1; + } + } + + + public static String ptType(Integer ptType) { + switch (ptType) { + case 0: + return "星型接线"; + case 1: + return "三角型接线"; + case 2: + return "开口三角型接线"; + default: + return ""; + } + } + +} diff --git a/cn-user/.gitignore b/cn-user/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/cn-user/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/cn-user/pom.xml b/cn-user/pom.xml new file mode 100644 index 0000000..a943723 --- /dev/null +++ b/cn-user/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + com.njcn.product + CN_Product + 1.0.0 + + + cn-user + 1.0.0 + cn-user + cn-user + + + + + + + com.njcn + njcn-common + 0.0.1 + + + + com.njcn + mybatis-plus + 0.0.1 + + + + com.njcn + spingboot2.3.12 + 2.3.12 + + + + + + + + + + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/AuthController.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/AuthController.java new file mode 100644 index 0000000..6d33c2f --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/AuthController.java @@ -0,0 +1,146 @@ +//package com.njcn.product.cnuser.user.controller; +// +//import cn.hutool.core.date.DateUnit; +//import cn.hutool.core.util.ObjectUtil; +//import cn.hutool.core.util.StrUtil; +//import com.alibaba.fastjson.JSON; +//import com.njcn.common.bean.CustomCacheUtil; +//import com.njcn.common.pojo.annotation.OperateInfo; +//import com.njcn.common.pojo.constant.OperateType; +//import com.njcn.common.pojo.constant.SecurityConstants; +//import com.njcn.common.pojo.enums.common.LogEnum; +//import com.njcn.common.pojo.enums.response.CommonResponseEnum; +//import com.njcn.common.pojo.exception.BusinessException; +//import com.njcn.common.pojo.response.HttpResult; +//import com.njcn.common.utils.JwtUtil; +//import com.njcn.common.utils.LogUtil; +//import com.njcn.common.utils.RSAUtil; +//import com.njcn.product.cnuser.user.pojo.constant.UserValidMessage; +//import com.njcn.product.cnuser.user.pojo.param.SysUserParam; +//import com.njcn.product.cnuser.user.pojo.po.SysUser; +//import com.njcn.product.cnuser.user.pojo.po.Token; +//import com.njcn.product.cnuser.user.service.ISysUserService; +//import com.njcn.product.cnuser.user.pojo.enums.UserResponseEnum; +// +//import com.njcn.web.controller.BaseController; +//import com.njcn.web.utils.HttpResultUtil; +//import com.njcn.web.utils.RequestUtil; +//import io.swagger.annotations.Api; +//import io.swagger.annotations.ApiOperation; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.web.bind.annotation.*; +// +//import javax.servlet.http.HttpServletRequest; +//import java.security.KeyPair; +//import java.util.Base64; +//import java.util.HashMap; +//import java.util.Map; +// +// +//@Slf4j +//@RestController +//@Api(tags = "登录/注销") +//@RequestMapping("/admin") +//@RequiredArgsConstructor +//public class AuthController extends BaseController { +// +// private final ISysUserService sysUserService; +// private final CustomCacheUtil customCacheUtil; +// private KeyPair keyPair; +// +// +// @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.AUTHENTICATE) +// @PostMapping("/login") +// @ApiOperation("登录") +// public HttpResult login(@RequestBody SysUserParam.LoginParam param, HttpServletRequest request) { +// String methodDescribe = getMethodDescribe("login"); +// LogUtil.njcnDebug(log, "{},登录参数为:{}", methodDescribe, param); +// byte[] decode = Base64.getDecoder().decode(param.getUsername()); +// String username = new String(decode); +// String password = null; +// +// try { +// password = RSAUtil.decrypt(param.getPassword(), keyPair.getPrivate()); +// } catch (Exception e) { +// throw new BusinessException(UserResponseEnum.RSA_DECRYT_ERROR); +// } +// // 因不确定是否能登陆成功先将登陆名保存到request,一遍记录谁执行了登录操作 +// request.setAttribute(SecurityConstants.AUTHENTICATE_USERNAME, username); +// SysUser user = sysUserService.getUserByLoginNameAndPassword(username, password); +// if (ObjectUtil.isNull(user)) { +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, UserValidMessage.LOGIN_FAILED); +// } else { +// String accessToken = JwtUtil.getAccessToken(user.getId(),username); +// String refreshToken = JwtUtil.getRefreshToken(accessToken); +// Token token = new Token(); +// token.setAccessToken(accessToken); +// token.setRefreshToken(refreshToken); +// +// Map map = new HashMap<>(); +// map.put("name", user.getName()); +// map.put("id", user.getId()); +// map.put("loginName",user.getLoginName()); +// +// token.setUserInfo(map); +// +// customCacheUtil.putWithExpireTime(accessToken, JSON.toJSONString(user), DateUnit.DAY.getMillis() * Integer.MAX_VALUE); +// sysUserService.updateLoginTime(user.getId()); +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, token, methodDescribe); +// } +// } +// +// @OperateInfo(info = LogEnum.SYSTEM_SERIOUS, operateType = OperateType.LOGOUT) +// @ApiOperation("注销登录") +// @PostMapping("/logout") +// public HttpResult logout() { +// String methodDescribe = getMethodDescribe("logout"); +// LogUtil.njcnDebug(log, "{},注销登录", methodDescribe); +// String accessToken = RequestUtil.getAccessToken(); +// if (StrUtil.isNotBlank(accessToken)) { +// customCacheUtil.remove(accessToken); +// +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); +// } +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); +// } +// +// @OperateInfo(info = LogEnum.SYSTEM_COMMON) +// @ApiOperation("刷新accessToken") +// @GetMapping("/refreshToken") +// public HttpResult refreshToken(HttpServletRequest request) { +// String methodDescribe = getMethodDescribe("refreshToken"); +// LogUtil.njcnDebug(log, "{},刷新token", methodDescribe); +// String accessToken = RequestUtil.getAccessToken(); +// +// Token token = new Token(); +// if (StrUtil.isNotBlank(accessToken)) { +// Map map = JwtUtil.parseToken(accessToken); +// String userId = (String) map.get(SecurityConstants.USER_ID); +// SysUser user = sysUserService.getById(userId); +// String accessTokenNew = JwtUtil.getAccessToken(userId, user.getLoginName()); +// request.setAttribute(SecurityConstants.AUTHENTICATE_USERNAME, user.getLoginName()); +//// String refreshTokenNew = JwtUtil.getRefreshToken(accessTokenNew); +// +// token.setAccessToken(accessTokenNew); +// token.setRefreshToken(accessToken); +// +// customCacheUtil.putWithExpireTime(accessTokenNew, JSON.toJSONString(user), DateUnit.DAY.getMillis() * Integer.MAX_VALUE); +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, token, methodDescribe); +// } else { +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); +// } +// } +// +// @OperateInfo(info = LogEnum.SYSTEM_COMMON) +// @ApiOperation("获取RSA公钥") +// @GetMapping("/getPublicKey") +// public HttpResult publicKey(@RequestParam("username") String username, HttpServletRequest request) throws Exception { +// String methodDescribe = getMethodDescribe("publicKey"); +// LogUtil.njcnDebug(log, "{},获取RSA公钥", methodDescribe); +// // 因不确定是否能登陆成功先将登陆名保存到request,一遍记录谁执行了登录操作 +// request.setAttribute(SecurityConstants.AUTHENTICATE_USERNAME, username); +// keyPair = RSAUtil.generateKeyPair(); +// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, RSAUtil.publicKeyToString(keyPair.getPublic()), methodDescribe); +// } +//} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/DeptController.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/DeptController.java new file mode 100644 index 0000000..533c786 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/DeptController.java @@ -0,0 +1,70 @@ +package com.njcn.product.cnuser.user.controller; + + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.tree.Tree; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.LogUtil; +import com.njcn.product.cnuser.user.pojo.vo.DeptAllTreeVO; +import com.njcn.product.cnuser.user.service.IDeptService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.*; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import springfox.documentation.annotations.ApiIgnore; + +import java.util.List; +import java.util.Objects; + +/** + *

+ * 前端控制器(部门信息) + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Validated +@Slf4j +@RestController +@RequestMapping("/dept") +@Api(tags = "部门管理") +@AllArgsConstructor +public class DeptController extends BaseController { + + private final IDeptService deptService; + + + + + /** + * 根据登录用户获取部门树 + */ + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/loginDeptTree") + @ApiOperation("根据登录用户获取部门树") + public HttpResult loginDeptTree(@RequestParam("deptIndex")String deptIndex) { + String methodDescribe = getMethodDescribe("loginDeptTree"); + List result = deptService.loginDeptTree(deptIndex); + if (!result.isEmpty()) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } else { + + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.NO_DATA, null, methodDescribe); + } + } + + + +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysFunctionController.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysFunctionController.java new file mode 100644 index 0000000..a9a9540 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysFunctionController.java @@ -0,0 +1,168 @@ +package com.njcn.product.cnuser.user.controller; + +import cn.hutool.core.util.StrUtil; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.JwtUtil; +import com.njcn.common.utils.LogUtil; +import com.njcn.product.cnuser.user.pojo.param.SysFunctionParam; +import com.njcn.product.cnuser.user.pojo.param.SysRoleParam; +import com.njcn.product.cnuser.user.pojo.po.SysFunction; +import com.njcn.product.cnuser.user.pojo.vo.MenuVO; +import com.njcn.product.cnuser.user.service.ISysFunctionService; +import com.njcn.product.cnuser.user.service.ISysRoleFunctionService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Strings; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; + + +/** + * @author caozehui + * @date 2024-11-15 + */ +@Slf4j +@Api(tags = "菜单(资源)管理") +@RestController +@RequestMapping("/sysFunction") +@RequiredArgsConstructor +public class SysFunctionController extends BaseController { + private final ISysFunctionService sysFunctionService; + private final ISysRoleFunctionService sysRoleFunctionService; + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getTree") + @ApiOperation("按照名称模糊查询菜单树") + @ApiImplicitParam(name = "keyword", value = "查询参数", required = true) + public HttpResult> getFunctionTreeByKeyword(@RequestParam @Validated String keyword) { + String methodDescribe = getMethodDescribe("getFunctionTreeByKeyword"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, keyword); + List result = sysFunctionService.getFunctionTreeByKeyword(keyword); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/functionTreeNoButton") + @ApiOperation("菜单树-不包括按钮") + public HttpResult> getFunctionTreeNoButton() { + String methodDescribe = getMethodDescribe("getFunctionTreeNoButton"); + List list = sysFunctionService.getFunctionTree(false); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD) + @PostMapping("/add") + @ApiOperation("新增菜单") + @ApiImplicitParam(name = "functionParam", value = "菜单数据", required = true) + public HttpResult add(@RequestBody @Validated SysFunctionParam functionParam) { + String methodDescribe = getMethodDescribe("add"); + LogUtil.njcnDebug(log, "{},菜单数据为:{}", methodDescribe, functionParam); + boolean result = sysFunctionService.addFunction(functionParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE) + @PostMapping("/update") + @ApiOperation("修改菜单") + @ApiImplicitParam(name = "functionParam", value = "菜单数据", required = true) + public HttpResult update(@RequestBody @Validated SysFunctionParam.UpdateParam functionParam) { + String methodDescribe = getMethodDescribe("update"); + LogUtil.njcnDebug(log, "{},更新的菜单信息为:{}", methodDescribe, functionParam); + boolean result = sysFunctionService.updateFunction(functionParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DELETE) + @PostMapping("/delete") + @ApiOperation("删除菜单") + @ApiImplicitParam(name = "id", value = "菜单id", required = true) + public HttpResult delete(@RequestParam String id) { + String methodDescribe = getMethodDescribe("delete"); + LogUtil.njcnDebug(log, "{},删除的菜单id为:{}", methodDescribe, id); + boolean result = sysFunctionService.deleteFunction(id); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getMenu") + @ApiOperation("获取菜单") + public HttpResult> getMenu(HttpServletRequest request) { + String methodDescribe = getMethodDescribe("getMenu"); + String tokenStr = request.getHeader(SecurityConstants.AUTHORIZATION_KEY); + if (StrUtil.isNotBlank(tokenStr)) { + tokenStr = tokenStr.replace(SecurityConstants.AUTHORIZATION_PREFIX, Strings.EMPTY); + String userId = (String) (JwtUtil.parseToken(tokenStr).get("userId")); + List list = sysFunctionService.getMenuByUserId(userId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe); + } + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getButton") + @ApiOperation("获取按钮") + public HttpResult>> getButton(HttpServletRequest request) { + String methodDescribe = getMethodDescribe("getButton"); + String tokenStr = request.getHeader(SecurityConstants.AUTHORIZATION_KEY); + if (StrUtil.isNotBlank(tokenStr)) { + tokenStr = tokenStr.replace(SecurityConstants.AUTHORIZATION_PREFIX, Strings.EMPTY); + String userId = (String) JwtUtil.parseToken(tokenStr).get("userId"); + Map> map = sysFunctionService.getButtonByUserId(userId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, map, methodDescribe); + } + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/getFunctionsByRoleId") + @ApiOperation("获取角色id绑定的菜单(资源)") + @ApiImplicitParam(name = "id", value = "角色id", required = true) + public HttpResult> getFunctionsByRoleId(@RequestParam @Validated String id) { + String methodDescribe = getMethodDescribe("getFunctionsByRoleId"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, id); + List sysFunctions = sysRoleFunctionService.listFunctionByRoleId(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, sysFunctions, methodDescribe); + } + + @OperateInfo(operateType = OperateType.UPDATE, info = LogEnum.SYSTEM_MEDIUM) + @PostMapping("/assignFunctionByRoleId") + @ApiOperation("角色分配菜单") + @ApiImplicitParam(name = "roleFunctionComponent", value = "角色信息", required = true) + public HttpResult assignFunctionByRoleId(@RequestBody @Validated SysRoleParam.RoleBindFunction param) { + String methodDescribe = getMethodDescribe("assignFunctionByRoleId"); + LogUtil.njcnDebug(log, "{},传入的角色id和资源id集合为:{}", methodDescribe, param); + boolean result = sysRoleFunctionService.updateRoleFunction(param.getRoleId(), param.getFunctionIds()); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysRoleController.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysRoleController.java new file mode 100644 index 0000000..bca2609 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysRoleController.java @@ -0,0 +1,104 @@ +package com.njcn.product.cnuser.user.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.LogUtil; +import com.njcn.product.cnuser.user.pojo.param.SysRoleParam; +import com.njcn.product.cnuser.user.pojo.po.SysRole; +import com.njcn.product.cnuser.user.service.ISysRoleService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @author caozehui + * @date 2024-11-11 + */ +@Slf4j +@Api(tags = "角色管理") +@RestController +@RequestMapping("/sysRole") +@RequiredArgsConstructor +public class SysRoleController extends BaseController { + private final ISysRoleService sysRoleService; + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/list") + @ApiOperation("分页查询角色") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult> list(@RequestBody @Validated SysRoleParam.QueryParam queryParam) { + String methodDescribe = getMethodDescribe("list"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam); + Page result = sysRoleService.listRole(queryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD) + @PostMapping("/add") + @ApiOperation("新增角色信息") + @ApiImplicitParam(name = "roleParam", value = "角色信息", required = true) + public HttpResult add(@RequestBody @Validated SysRoleParam sysRoleParam) { + String methodDescribe = getMethodDescribe("add"); + LogUtil.njcnDebug(log, "{},角色信息数据为:{}", methodDescribe, sysRoleParam); + boolean result = sysRoleService.addRole(sysRoleParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE) + @PostMapping("/update") + @ApiOperation("修改角色信息") + @ApiImplicitParam(name = "updateParam", value = "角色信息", required = true) + public HttpResult update(@RequestBody @Validated SysRoleParam.UpdateParam updateParam) { + String methodDescribe = getMethodDescribe("update"); + LogUtil.njcnDebug(log, "{},角色信息数据为:{}", methodDescribe, updateParam); + boolean result = sysRoleService.updateRole(updateParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DELETE) + @PostMapping("/delete") + @ApiOperation("删除角色信息") + @ApiImplicitParam(name = "ids", value = "角色id集合", required = true) + public HttpResult delete(@RequestBody List ids) { + String methodDescribe = getMethodDescribe("delete"); + LogUtil.njcnDebug(log, "{},角色信息数据为:{}", methodDescribe, ids); + boolean result = sysRoleService.deleteRole(ids); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/simpleList") + @ApiOperation("查询所有角色作为下拉框") + public HttpResult> simpleList() { + String methodDescribe = getMethodDescribe("simpleList"); + List result = sysRoleService.simpleList(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysUserController.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysUserController.java new file mode 100644 index 0000000..e1ea0cc --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/controller/SysUserController.java @@ -0,0 +1,127 @@ +package com.njcn.product.cnuser.user.controller; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.LogUtil; +import com.njcn.product.cnuser.user.pojo.param.SysUserParam; +import com.njcn.product.cnuser.user.pojo.po.SysUser; +import com.njcn.product.cnuser.user.service.ISysUserService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @author caozehui + * @since 2024-11-08 + */ +@Slf4j +@Api(tags = "用户管理") +@RestController +@RequestMapping("/sysUser") +@RequiredArgsConstructor +public class SysUserController extends BaseController { + + private final ISysUserService sysUserService; + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @PostMapping("/list") + @ApiOperation("分页查询用户列表") + @ApiImplicitParam(name = "queryParam", value = "查询参数", required = true) + public HttpResult> list(@RequestBody @Validated SysUserParam.SysUserQueryParam queryParam) { + String methodDescribe = getMethodDescribe("list"); + LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam); + Page result = sysUserService.listUser(queryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD) + @PostMapping("/add") + @ApiOperation("新增用户") + @ApiImplicitParam(name = "addUserParam", value = "新增用户", required = true) + public HttpResult add(@RequestBody @Validated SysUserParam.SysUserAddParam addUserParam) { + String methodDescribe = getMethodDescribe("add"); + LogUtil.njcnDebug(log, "{},用户数据为:{}", methodDescribe, addUserParam); + boolean result = sysUserService.addUser(addUserParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE) + @PostMapping("/update") + @ApiOperation("修改用户") + @ApiImplicitParam(name = "updateUserParam", value = "修改用户", required = true) + public HttpResult update(@RequestBody @Validated SysUserParam.SysUserUpdateParam updateUserParam) { + String methodDescribe = getMethodDescribe("update"); + LogUtil.njcnDebug(log, "{},用户数据为:{}", methodDescribe, updateUserParam); + boolean result = sysUserService.updateUser(updateUserParam); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DELETE) + @PostMapping("/delete") + @ApiOperation("批量删除用户") + @ApiImplicitParam(name = "ids", value = "用户id", required = true) + public HttpResult delete(@RequestBody List ids) { + String methodDescribe = getMethodDescribe("delete"); + LogUtil.njcnDebug(log, "{},用户id为:{}", methodDescribe, String.join(StrUtil.COMMA, ids)); + boolean result = sysUserService.deleteUser(ids); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE) + @PostMapping("/updatePassword") + @ApiOperation("修改密码") + @ApiImplicitParams({ + @ApiImplicitParam(name = "id", value = "用户id", required = true), + @ApiImplicitParam(name = "oldPassword", value = "旧密码", required = true), + @ApiImplicitParam(name = "newPassword", value = "新密码", required = true) + }) + public HttpResult updatePassword(@RequestBody @Validated SysUserParam.SysUserUpdatePasswordParam param) { + String methodDescribe = getMethodDescribe("updatePassword"); + LogUtil.njcnDebug(log, "{},用户id:{},用户旧密码:{},新密码:{}", methodDescribe, param.getId(), param.getOldPassword(), param.getNewPassword()); + boolean result = sysUserService.updatePassword(param); + if (result) { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe); + } + } + + @OperateInfo(info = LogEnum.SYSTEM_COMMON) + @GetMapping("/getAll") + @ApiOperation("获取所有用户") + public HttpResult> getAll() { + String methodDescribe = getMethodDescribe("getAll"); + LogUtil.njcnDebug(log, "{},查询所有用户", methodDescribe); + List result = sysUserService.lambdaQuery().eq(SysUser::getState, DataStateEnum.ENABLE.getCode()).list(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/DeptMapper.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/DeptMapper.java new file mode 100644 index 0000000..07a39d8 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/DeptMapper.java @@ -0,0 +1,36 @@ +package com.njcn.product.cnuser.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.njcn.product.cnuser.user.pojo.dto.DeptDTO; +import com.njcn.product.cnuser.user.pojo.po.Dept; +import com.njcn.product.cnuser.user.pojo.vo.DeptAllTreeVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface DeptMapper extends BaseMapper { + + /** + * 根据条件获取后代部门索引 + * @param id 部门id + * @param type 指定部门类型 + * @return 后代部门索引 + */ + List getDeptDescendantIndexes(@Param("id")String id, @Param("type")List type); + + /** + * + * @return 部门树 + */ + List getAllDeptTree(@Param("id")String id, @Param("type")List type); + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysFunctionMapper.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysFunctionMapper.java new file mode 100644 index 0000000..3868d09 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysFunctionMapper.java @@ -0,0 +1,30 @@ +package com.njcn.product.cnuser.user.mapper; + +import com.github.yulichang.base.MPJBaseMapper; +import com.njcn.product.cnuser.user.pojo.po.SysFunction; +import com.njcn.product.cnuser.user.pojo.vo.MenuVO; + + +import java.util.List; + +/** + * @author caozehui + * @date 2024-11-12 + */ +public interface SysFunctionMapper extends MPJBaseMapper { + + /** + * 根据用户id获取菜单列表 + * @param userId 用户id + * @return 菜单列表 + */ + List getMenuByUserId(String userId); + + /* + * 根据用户id获取按钮列表 + * @param userId 用户id + * @return 按钮列表 + */ + List getButtonByUserId(String userId); +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysRoleFunctionMapper.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysRoleFunctionMapper.java new file mode 100644 index 0000000..c48f674 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysRoleFunctionMapper.java @@ -0,0 +1,24 @@ +package com.njcn.product.cnuser.user.mapper; + +import com.github.yulichang.base.MPJBaseMapper; +import com.njcn.product.cnuser.user.pojo.po.SysFunction; +import com.njcn.product.cnuser.user.pojo.po.SysRoleFunction; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @author caozehui + * @date 2024-11-12 + */ +public interface SysRoleFunctionMapper extends MPJBaseMapper { + + /** + * 根据角色id获取角色拥有的菜单(资源)列表 + * + * @param roleId 角色id + * @return 角色拥有的菜单(资源)列表 + */ + List getFunctionListByRoleId(@Param("roleId") String roleId); +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysRoleMapper.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysRoleMapper.java new file mode 100644 index 0000000..1eb8c43 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysRoleMapper.java @@ -0,0 +1,13 @@ +package com.njcn.product.cnuser.user.mapper; + +import com.github.yulichang.base.MPJBaseMapper; +import com.njcn.product.cnuser.user.pojo.po.SysRole; + +/** + * @author caozehui + * @date 2024-11-11 + */ +public interface SysRoleMapper extends MPJBaseMapper { + +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysUserMapper.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysUserMapper.java new file mode 100644 index 0000000..a7bdc10 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysUserMapper.java @@ -0,0 +1,13 @@ +package com.njcn.product.cnuser.user.mapper; + +import com.github.yulichang.base.MPJBaseMapper; +import com.njcn.product.cnuser.user.pojo.po.SysUser; + +/** + * @author caozehui + * @since 2024-11-08 + */ +public interface SysUserMapper extends MPJBaseMapper { + +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysUserRoleMapper.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..a396798 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/SysUserRoleMapper.java @@ -0,0 +1,23 @@ +package com.njcn.product.cnuser.user.mapper; + +import com.github.yulichang.base.MPJBaseMapper; +import com.njcn.product.cnuser.user.pojo.po.SysRole; +import com.njcn.product.cnuser.user.pojo.po.SysUserRole; + + +import java.util.List; + +/** + * @author caozehui + * @date 2024-11-12 + */ +public interface SysUserRoleMapper extends MPJBaseMapper { + /** + * 根据用户id获取角色详情 + * + * @param userId 用户id + * @return 角色结果集 + */ + List getRoleListByUserId(String userId); +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysFunctionMapper.xml b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysFunctionMapper.xml new file mode 100644 index 0000000..dc69943 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysFunctionMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysRoleFunctionMapper.xml b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysRoleFunctionMapper.xml new file mode 100644 index 0000000..8558246 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysRoleFunctionMapper.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysRoleMapper.xml b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysRoleMapper.xml new file mode 100644 index 0000000..4b3edc2 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysRoleMapper.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysUserMapper.xml b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysUserMapper.xml new file mode 100644 index 0000000..bba56ac --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysUserMapper.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysUserRoleMapper.xml b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysUserRoleMapper.xml new file mode 100644 index 0000000..cf2176f --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/mapper/mapping/SysUserRoleMapper.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/FunctionConst.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/FunctionConst.java new file mode 100644 index 0000000..4ba74c6 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/FunctionConst.java @@ -0,0 +1,33 @@ +package com.njcn.product.cnuser.user.pojo.constant; + +/** + * @author caozehui + * @data 2024/11/12 + */ +public interface FunctionConst { + /** + * 资源类型:0-菜单 + */ + int TYPE_MENU =0; + + /** + * 资源类型:1-按钮 + */ + int TYPE_BUTTON =1; + + /** + * 资源类型:2-公共资源 + */ + int TYPE_PUBLIC =2; + + /** + * 资源类型:3-服务间调用资源 + */ + int TYPE_SERVICE_INVOKE_FUNCTION =3; + + /** + * 顶级父节点ID + */ + String FATHER_PID = "0"; + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/RoleConst.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/RoleConst.java new file mode 100644 index 0000000..4522cd2 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/RoleConst.java @@ -0,0 +1,28 @@ +package com.njcn.product.cnuser.user.pojo.constant; + +/** + * @author caozehui + * @data 2024/11/11 + */ +public interface RoleConst { + /** + * 角色类型:0-超级管理员 + */ + int TYPE_SUPER_ADMINISTRATOR = 0; + + /** + * 角色类型:1-管理员 + */ + int TYPE_ADMINISTRATOR = 1; + + /** + * 角色类型:2-用户 + */ + int TYPE_USER = 2; + + /** + * 角色类型:3-APP角色 + */ + int TYPE_APP = 3; + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/UserConst.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/UserConst.java new file mode 100644 index 0000000..c593567 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/UserConst.java @@ -0,0 +1,16 @@ +package com.njcn.product.cnuser.user.pojo.constant; + +/** + * @author caozehui + * @data 2024/11/11 + */ +public interface UserConst { + Integer STATE_DELETE = 0; + Integer STATE_ENABLE = 1; + Integer STATE_LOCKED = 2; + Integer STATE_WAITING_FOR_APPROVAL = 3; + Integer STATE_SLEEPING = 4; + Integer STATE_PASSWORD_EXPIRED = 5; + + String SUPER_ADMIN = "root"; +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/UserValidMessage.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/UserValidMessage.java new file mode 100644 index 0000000..31877ac --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/constant/UserValidMessage.java @@ -0,0 +1,49 @@ +package com.njcn.product.cnuser.user.pojo.constant; + +/** + * @author caozehui + * @data 2024/11/8 + */ +public interface UserValidMessage { + + String ID_NOT_BLANK = "id不能为空,请检查id参数"; + + String ID_FORMAT_ERROR = "id格式错误,请检查id参数"; + + String DEPT_ID_FORMAT_ERROR = "部门id格式错误,请检查deptId参数"; + + String NAME_NOT_BLANK = "名称不能为空,请检查name参数"; + + String NAME_FORMAT_ERROR = "名称格式错误,请检查name参数"; + + String CODE_NOT_BLANK = "编码不能为空,请检查code参数"; + + String LOGIN_NAME_NOT_BLANK = "登录名不能为空,请检查loginName参数"; + + String LOGIN_NAME_FORMAT_ERROR = "登录名格式错误,需以字母开头,长度为3-16位的字母或数字"; + + String PASSWORD_NOT_BLANK = "密码不能为空,请检查password参数"; + + String PASSWORD_FORMAT_ERROR = "密码格式错误,需要包含特殊字符字母数字8-16位"; + + String PHONE_FORMAT_ERROR = "电话号码格式错误,请检查phone参数"; + + String EMAIL_FORMAT_ERROR = "邮箱格式错误,请检查email参数"; + + String OLD_PASSWORD_NOT_BLANK = "旧密码不能为空,请检查oldPassword参数"; + + String NEW_PASSWORD_NOT_BLANK = "新密码不能为空,请检查newPassword参数"; + + String PID_NOT_BLANK = "父节点id不能为空,请检查pid参数"; + + String SORT_NOT_NULL = "排序不能为空,请检查sort参数"; + + String TYPE_NOT_BLANK = "类型不能为空,请检查type参数"; + + String PARAM_FORMAT_ERROR = "参数值非法"; + + String LOGIN_FAILED = "登录失败,用户名或密码错误"; + + String FUNCTION_NAME_FORMAT_ERROR = "菜单名称格式错误,只能包含字母、数字、中文、下划线、中划线、空格,长度为1-32个字符"; + String FUNCTION_CODE_FORMAT_ERROR = "菜单编码格式错误,只能包含字母、数字、下划线、中划线、空格,长度为1-32个字符"; +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/dto/DeptDTO.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/dto/DeptDTO.java new file mode 100644 index 0000000..003369a --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/dto/DeptDTO.java @@ -0,0 +1,46 @@ +package com.njcn.product.cnuser.user.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2022年02月11日 14:08 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DeptDTO implements Serializable { + + private String id; + + private String pid; + + private String pids; + + private String name; + + private String code; + + /** + * 专项分析类型区分 + */ + private Integer specialType; + + private String area; + + private String remark; + + private Integer sort; + + /** + * 部门类型 0-非自定义;1-web自定义;2-App自定义;3-web测试 + */ + private Integer type; + + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/dto/UserDTO.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/dto/UserDTO.java new file mode 100644 index 0000000..fc94173 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/dto/UserDTO.java @@ -0,0 +1,48 @@ +package com.njcn.product.cnuser.user.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年05月08日 15:12 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserDTO { + + private String userIndex; + + private String username; + + private String nickname; + + private String password; + + /** + * 角色集合 + */ + private List roleName; + + /** + * sm4加密秘钥 + */ + private String secretKey; + + /** + * sm4中间过程校验 + */ + private String standBy; + + private String deptIndex; + + private Integer type; + + private String headSculpture; + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/enums/UserResponseEnum.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/enums/UserResponseEnum.java new file mode 100644 index 0000000..6d691ae --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/enums/UserResponseEnum.java @@ -0,0 +1,37 @@ +package com.njcn.product.cnuser.user.pojo.enums; + +import lombok.Getter; + +/** + * @author caozehui + * @data 2024/11/9 + */ +@Getter +public enum UserResponseEnum { + LOGIN_NAME_REPEAT("A010001", "登录名重复,请检查loginName参数"), + REGISTER_PHONE_FAIL("A010002", "该号码已被注册"), + USER_NAME_REPEAT("A010003", "用户名重复,请检查name参数"), + REGISTER_EMAIL_FAIL("A010004", "该邮箱已被注册"), + NAME_OR_CODE_REPEAT("A010005", "名称或编码已存在"), + EXISTS_SAME_MENU_CHILDREN("A010006", "该层级下已存在相同名称或相同编码或相同路径或相同组件地址的菜单"), + EXISTS_CHILDREN_NOT_UPDATE("A010008", "该菜单下存在子节点,无法将菜单修改为按钮"), + EXISTS_CHILDREN_NOT_DELETE("A010007", "该节点下存在子节点,无法删除"), + SUPER_ADMINSTRATOR_ROLE_CANNOT_UPDATE("A010009", "禁止修改超级管理员角色"), + SUPER_ADMINSTRATOR_ROLE_CANNOT_DELETE("A010009", "禁止删除超级管理员角色"), + SUPER_ADMIN_CANNOT_DELETE("A010010", "禁止删除超级管理员用户"), + COMPONENT_NOT_BLANK("A010011", "组件地址不能为空"), + FUNCTION_PATH_FORMAT_ERROR("A010012", "菜单路由地址格式错误,只能包含字母、数字、下划线、中划线、空格、斜线、反斜线,长度为1-32个字符"), + FUNCTION_COMPONENT_FORMAT_ERROR("A010013","菜单组件地址格式错误,只能包含字母、数字、下划线、中划线、空格、斜线、反斜线,长度为1-32个字符" ), + SUPER_ADMIN_REPEAT("A010013","超级管理员已存在,请勿重复添加" ), + RSA_DECRYT_ERROR("A010014","RSA解密失败" ), + PASSWORD_SAME("A010015", "新密码不能与旧密码相同"), + OLD_PASSWORD_ERROR("A010016", "旧密码错误"), ; + + private String code; + private String message; + + UserResponseEnum(String code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysFunctionParam.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysFunctionParam.java new file mode 100644 index 0000000..9149efa --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysFunctionParam.java @@ -0,0 +1,74 @@ +package com.njcn.product.cnuser.user.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.product.cnuser.user.pojo.constant.UserValidMessage; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +/** + * @author caozehui + * @data 2024/11/12 + */ +@Data +public class SysFunctionParam { + @ApiModelProperty("父节点") + @NotBlank(message = UserValidMessage.PID_NOT_BLANK) + private String pid; + + @ApiModelProperty("名称") + @NotBlank(message = UserValidMessage.NAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.FUNCTION_NAME_REGEX, message = UserValidMessage.FUNCTION_NAME_FORMAT_ERROR) + private String name; + + @ApiModelProperty("编码") + @NotBlank(message = UserValidMessage.CODE_NOT_BLANK) + @Pattern(regexp = PatternRegex.FUNCTION_CODE_REGEX, message = UserValidMessage.FUNCTION_CODE_FORMAT_ERROR) + private String code; + + @ApiModelProperty("路径") + private String path; + + @ApiModelProperty("组件地址") + private String component; + + @ApiModelProperty("图标") + private String icon; + + @ApiModelProperty("排序") + @NotNull(message = UserValidMessage.SORT_NOT_NULL) + @Range(min = 0, max = 999, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer sort; + + @ApiModelProperty("资源类型") + @NotNull(message = UserValidMessage.TYPE_NOT_BLANK) + @Range(min = 0, max = 3, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer type; + + @ApiModelProperty("描述") + private String remark; + + @Data + @EqualsAndHashCode(callSuper = true) + public static class QueryParam extends BaseParam { + @ApiModelProperty("名称") + @Pattern(regexp = PatternRegex.FUNCTION_NAME_REGEX, message = UserValidMessage.FUNCTION_NAME_FORMAT_ERROR) + private String name; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class UpdateParam extends SysFunctionParam { + + @ApiModelProperty("id") + @NotBlank(message = UserValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = UserValidMessage.ID_FORMAT_ERROR) + private String id; + } +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysRoleParam.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysRoleParam.java new file mode 100644 index 0000000..d55a9cd --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysRoleParam.java @@ -0,0 +1,83 @@ +package com.njcn.product.cnuser.user.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.product.cnuser.user.pojo.constant.UserValidMessage; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.validator.constraints.Range; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.util.List; + +/** + * @author caozehui + * @data 2024/11/11 + */ +@Data +public class SysRoleParam { + + + @ApiModelProperty("名称") + @NotBlank(message = UserValidMessage.NAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.DEPT_NAME_REGEX, message = UserValidMessage.NAME_FORMAT_ERROR) + private String name; + + @ApiModelProperty("编码") + @NotNull(message = UserValidMessage.CODE_NOT_BLANK) + private String code; + + /** + * 角色类型 0:超级管理员;1:管理员;2:普通用户 + */ + @ApiModelProperty("类型") + @Range(min = 0, max = 2, message = UserValidMessage.PARAM_FORMAT_ERROR) + private Integer type; + + @ApiModelProperty("描述") + private String remark; + + /** + * 更新操作实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class UpdateParam extends SysRoleParam { + + @ApiModelProperty("id") + @NotBlank(message = UserValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = UserValidMessage.ID_FORMAT_ERROR) + private String id; + + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class QueryParam extends BaseParam { + @ApiModelProperty("名称") + private String name; + + @ApiModelProperty("编码") + private String code; + + @ApiModelProperty("类型") + private Integer type; + } + + /** + * 角色绑定菜单(资源)参数 + */ + @Data + public static class RoleBindFunction { + @ApiModelProperty("角色id") + @NotBlank(message = UserValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = UserValidMessage.ID_FORMAT_ERROR) + private String roleId; + + @ApiModelProperty("菜单ids") + private List functionIds; + } +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysUserParam.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysUserParam.java new file mode 100644 index 0000000..e98c8dc --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/param/SysUserParam.java @@ -0,0 +1,104 @@ +package com.njcn.product.cnuser.user.pojo.param; + +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.product.cnuser.user.pojo.constant.UserValidMessage; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import java.util.List; + +/** + * @author caozehui + * @data 2024/11/8 + */ +@Data +public class SysUserParam { + + @ApiModelProperty("用户名(别名)") + @NotBlank(message = UserValidMessage.NAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.USERNAME_REGEX, message = UserValidMessage.NAME_FORMAT_ERROR) + private String name; + + @ApiModelProperty("部门Id") + private String deptId; + + @ApiModelProperty("电话号码") + @Pattern(regexp = PatternRegex.PHONE_REGEX_OR_NULL, message = UserValidMessage.PHONE_FORMAT_ERROR) + private String phone; + + @ApiModelProperty("邮箱") + @Pattern(regexp = PatternRegex.EMAIL_REGEX_OR_NULL, message = UserValidMessage.EMAIL_FORMAT_ERROR) + private String email; + + @ApiModelProperty("角色ids") + private List roleIds; + + @Data + @EqualsAndHashCode(callSuper = true) + public static class SysUserAddParam extends SysUserParam { + + @ApiModelProperty("登录名") + @NotBlank(message = UserValidMessage.LOGIN_NAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.LOGIN_NAME_REGEX, message = UserValidMessage.LOGIN_NAME_FORMAT_ERROR) + private String loginName; + + @ApiModelProperty("密码") + @NotBlank(message = UserValidMessage.PASSWORD_NOT_BLANK) + @Pattern(regexp = PatternRegex.PASSWORD_REGEX, message = UserValidMessage.PASSWORD_FORMAT_ERROR) + private String password; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class SysUserUpdateParam extends SysUserParam { + + @ApiModelProperty("用户表Id") + @NotBlank(message = UserValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = UserValidMessage.ID_FORMAT_ERROR) + private String id; + + } + + @Data + public static class SysUserUpdatePasswordParam { + @ApiModelProperty("用户Id") + @NotBlank(message = UserValidMessage.ID_NOT_BLANK) + @Pattern(regexp = PatternRegex.SYSTEM_ID, message = UserValidMessage.ID_FORMAT_ERROR) + private String id; + + @ApiModelProperty("旧密码") + @NotBlank(message = UserValidMessage.OLD_PASSWORD_NOT_BLANK) + @Pattern(regexp = PatternRegex.PASSWORD_REGEX, message = UserValidMessage.PASSWORD_FORMAT_ERROR) + private String oldPassword; + + @ApiModelProperty("新密码") + @NotBlank(message = UserValidMessage.NEW_PASSWORD_NOT_BLANK) + @Pattern(regexp = PatternRegex.PASSWORD_REGEX, message = UserValidMessage.PASSWORD_FORMAT_ERROR) + private String newPassword; + } + + @Data + @EqualsAndHashCode(callSuper = true) + public static class SysUserQueryParam extends BaseParam { + @ApiModelProperty("用户名(别名)") + private String name; + + } + + @Data + public static class LoginParam { + @ApiModelProperty("登录名") + @NotBlank(message = UserValidMessage.LOGIN_NAME_NOT_BLANK) + @Pattern(regexp = PatternRegex.LOGIN_NAME_REGEX, message = UserValidMessage.LOGIN_NAME_FORMAT_ERROR) + private String username; + + @ApiModelProperty("密码") + @NotBlank(message = UserValidMessage.PASSWORD_NOT_BLANK) + @Pattern(regexp = PatternRegex.PASSWORD_REGEX, message = UserValidMessage.PASSWORD_FORMAT_ERROR) + private String password; + } +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/Dept.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/Dept.java new file mode 100644 index 0000000..d19a542 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/Dept.java @@ -0,0 +1,75 @@ +package com.njcn.product.cnuser.user.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * + * @author hongawen + * @since 2021-12-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_dept") +public class Dept extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * 部门表Id + */ + private String id; + + /** + * 父节点Id(0为根节点) + */ + private String pid; + + /** + * 上层所有节点Id + */ + private String pids; + + /** + * 部门名称 + */ + private String name; + + /** + * 部门编号 + */ + private String code; + + /** + * 专项分析类型区分 + */ + private Integer specialType; + + /** + * (sys_Area)行政区域Id,自定义部门无需填写部门 + */ + private String area; + + /** + * 部门类型 0-非自定义;1-web自定义;2-App自定义;3-web测试 + */ + private Integer type; + + /** + * 排序 + */ + private Integer sort; + + /** + * 部门描述 + */ + private String remark; + + /** + * 部门状态 0-删除;1-正常;默认正常 + */ + private Integer state; + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysFunction.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysFunction.java new file mode 100644 index 0000000..f6ad2a6 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysFunction.java @@ -0,0 +1,88 @@ +package com.njcn.product.cnuser.user.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * @author caozehui + * @date 2024-11-15 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_function") +public class SysFunction extends BaseEntity implements Serializable { + private static final long serialVersionUID = -30909841321495323L; + + /** + * 资源表Id + */ + private String id; + + /** + * 节点(0为根节点) + */ + private String pid; + + /** + * 上层所有节点 + */ + private String pids; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 路径 + */ + private String path; + + /** + * 组件地址 + */ + private String component; + + /** + * 图标(没有图标默认为:“Null”) + */ + private String icon; + + /** + * 排序 + */ + private Integer sort; + + /** + * 资源类型:0-菜单、1-按钮、2-公共资源、3-服务间调用资源 + */ + private Integer type; + + /** + * 权限资源描述 + */ + private String remark; + + /** + * 权限资源状态:0-删除 1-正常 + */ + private Integer state; + + /** + * 子节点 + */ + @TableField(exist = false) + private List children; +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysRole.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysRole.java new file mode 100644 index 0000000..b16a58f --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysRole.java @@ -0,0 +1,50 @@ +package com.njcn.product.cnuser.user.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + * @author caozehui + * @date 2024-11-11 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_role") +public class SysRole extends BaseEntity implements Serializable { + private static final long serialVersionUID = 183697621480953314L; + + /** + * 角色表Id + */ + private String id; + + /** + * 角色名称 + */ + private String name; + + /** + * 编码,有需要用做匹配时候用(关联字典表id) + */ + private String code; + + /** + * 类型:0-超级管理员;1-管理员角色;2-普通角色,默认普通角色 + */ + private Integer type; + + /** + * 描述 + */ + private String remark; + + /** + * 状态:0-删除;1-正常;默认正常 + */ + private Integer state; +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysRoleFunction.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysRoleFunction.java new file mode 100644 index 0000000..4c7541f --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysRoleFunction.java @@ -0,0 +1,27 @@ +package com.njcn.product.cnuser.user.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author caozehui + * @date 2024-11-15 + */ +@Data +@TableName("sys_role_function") +public class SysRoleFunction implements Serializable { + private static final long serialVersionUID = -32044506851166587L; + /** + * 角色表Id + */ + private String roleId; + + /** + * 资源表Id + */ + private String functionId; + +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysUser.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysUser.java new file mode 100644 index 0000000..beb8890 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysUser.java @@ -0,0 +1,97 @@ +package com.njcn.product.cnuser.user.pojo.po; + +import java.time.LocalDateTime; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * @author caozehui + * @since 2024-11-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_user") +public class SysUser extends BaseEntity implements Serializable { + + private static final long serialVersionUID = -54771740356521149L; + + /** + * 用户表Id + */ + private String id; + + /** + * 用户名(别名) + */ + private String name; + + /** + * 登录名 + */ + private String loginName; + + /** + * 密码 + */ + private String password; + + /** + * 部门Id + */ + private String deptId; + + /** + * 电话号码 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 最后一次登录时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonSerialize(using = LocalDateTimeSerializer.class) + private LocalDateTime loginTime; + + /** + * 密码错误次数 + */ + private Integer loginErrorTimes; + + /** + * 用户密码错误锁定时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonSerialize(using = LocalDateTimeSerializer.class) + private LocalDateTime lockTime; + + /** + * 用户状态 0-删除;1-正常;2-锁定;3-待审核;4-休眠;5-密码过期 + */ + private Integer state; + + @TableField(exist = false) + private List roleIds; + + @TableField(exist = false) + private List roleNames; +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysUserRole.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysUserRole.java new file mode 100644 index 0000000..a315a8e --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/SysUserRole.java @@ -0,0 +1,27 @@ +package com.njcn.product.cnuser.user.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author caozehui + * @date 2024-11-12 + */ +@Data +@TableName("sys_user_role") +public class SysUserRole implements Serializable { + private static final long serialVersionUID = 725290952766199948L; + /** + * 用户Id + */ + private String userId; + + /** + * 角色Id + */ + private String roleId; + +} + diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/Token.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/Token.java new file mode 100644 index 0000000..e7335b4 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/po/Token.java @@ -0,0 +1,16 @@ +package com.njcn.product.cnuser.user.pojo.po; + +import lombok.Data; + +import java.util.Map; + +@Data +public class Token { + + private String accessToken; + + private String refreshToken; + + private Map userInfo; + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/DeptAllTreeVO.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/DeptAllTreeVO.java new file mode 100644 index 0000000..51f6eec --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/DeptAllTreeVO.java @@ -0,0 +1,32 @@ +package com.njcn.product.cnuser.user.pojo.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * @author denghuajun + * @version 1.0.0 + * @date 2022年04月15日 11:28 + */ +@Data +public class DeptAllTreeVO { + + @ApiModelProperty("id") + private String id; + + @ApiModelProperty("名称") + private String name; + + @ApiModelProperty("父节点id") + private String pid; + + @ApiModelProperty("父节点集合") + private String pids; + + private Integer sort; + + @ApiModelProperty("子节点详细信息") + private List children; +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/MenuVO.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/MenuVO.java new file mode 100644 index 0000000..a1cf148 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/MenuVO.java @@ -0,0 +1,13 @@ +package com.njcn.product.cnuser.user.pojo.vo; + +import com.njcn.product.cnuser.user.pojo.po.SysFunction; +import com.njcn.product.cnuser.user.pojo.vo.MetaVO; +import lombok.Data; + +@Data +public class MenuVO extends SysFunction { + + private String redirect; + + private MetaVO meta; +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/MetaVO.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/MetaVO.java new file mode 100644 index 0000000..11d6666 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/pojo/vo/MetaVO.java @@ -0,0 +1,49 @@ +package com.njcn.product.cnuser.user.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class MetaVO { + + /** + * 菜单和面包屑对应的图标 + */ + private String icon; + + /** + * 路由标题 (用作 document.title || 菜单的名称) + */ + private String title; + + /** + * 路由外链时填写的访问地址 + */ + @JsonProperty("isLink") + private String isLink; + + /** + * 是否在菜单中隐藏 (通常列表详情页需要隐藏) + */ + @JsonProperty("isHide") + private boolean isHide; + + /** + * 菜单是否全屏 (示例:数据大屏页面) + */ + @JsonProperty("isFull") + private boolean isFull; + + /** + * 菜单是否固定在标签页中 (首页通常是固定项) + */ + @JsonProperty("isAffix") + private boolean isAffix; + + /** + * 当前路由是否缓存 + */ + @JsonProperty("isKeepAlive") + private boolean isKeepAlive; + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/IDeptService.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/IDeptService.java new file mode 100644 index 0000000..fe1cf4f --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/IDeptService.java @@ -0,0 +1,29 @@ +package com.njcn.product.cnuser.user.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.cnuser.user.pojo.po.Dept; +import com.njcn.product.cnuser.user.pojo.vo.DeptAllTreeVO; + + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +public interface IDeptService extends IService { + + + /** + * 根据登录用户获取区域树 + * @return 结果 + */ + List loginDeptTree(String deptIndex); + + + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysFunctionService.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysFunctionService.java new file mode 100644 index 0000000..872e210 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysFunctionService.java @@ -0,0 +1,71 @@ +package com.njcn.product.cnuser.user.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.cnuser.user.pojo.param.SysFunctionParam; +import com.njcn.product.cnuser.user.pojo.po.SysFunction; +import com.njcn.product.cnuser.user.pojo.vo.MenuVO; + + +import java.util.List; +import java.util.Map; + +/** + * @author caozehui + * @date 2024-11-12 + */ +public interface ISysFunctionService extends IService { + + /** + * 根据关键字模糊查询菜单(资源)树 + * + * @param keyword 关键字 + * @return 菜单(资源)树 + */ + List getFunctionTreeByKeyword(String keyword); + + /** + * 添加菜单(资源) + * + * @param functionParam 资源参数 + * @return 是否添加成功 + */ + boolean addFunction(SysFunctionParam functionParam); + + /** + * 修改菜单(资源) + * + * @param functionParam 资源参数 + * @return 是否更新成功 + */ + boolean updateFunction(SysFunctionParam.UpdateParam functionParam); + + /** + * 删除菜单(资源) + * + * @param id 资源id + */ + boolean deleteFunction(String id); + + /** + * 获取树形结构的菜单(资源 + * + * @param isContainButton 是否包含按钮 + * @return 树形结构的资源 + */ + List getFunctionTree(boolean isContainButton); + + /** + * 根据用户id获取菜单 + * + * @return 路由菜单 + */ + List getMenuByUserId(String userId); + + /** + * 根据用户id获取按钮 + * @param userId 用户id + * @return 按钮 + */ + Map> getButtonByUserId(String userId); + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysRoleFunctionService.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysRoleFunctionService.java new file mode 100644 index 0000000..5b082fc --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysRoleFunctionService.java @@ -0,0 +1,47 @@ +package com.njcn.product.cnuser.user.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.cnuser.user.pojo.po.SysFunction; +import com.njcn.product.cnuser.user.pojo.po.SysRoleFunction; + +import java.util.List; + +/** + * @author caozehui + * @date 2024-11-12 + */ +public interface ISysRoleFunctionService extends IService { + + /** + * 获取角色id绑定的菜单(资源) + * + * @param roleId 角色id + * @return 菜单(资源)列表 + */ + List listFunctionByRoleId(String roleId); + + /** + * 更新角色菜单(资源)关联数据 + * + * @param roleId 角色id + * @param functionIds 菜单(资源)ids + * @return 成功返回true,失败返回false + */ + boolean updateRoleFunction(String roleId, List functionIds); + + /** + * 根据角色ids删除角色资源关联数据 + * + * @param roleIds + * @return 成功返回true,失败返回false + */ + boolean deleteRoleFunctionByRoleIds(List roleIds); + + /** + * 根据菜单(资源)ids删除角色资源关联数据 + * + * @param functionIds 菜单(资源)ids + * @return 成功返回true,失败返回false + */ + boolean deleteRoleFunctionByFunctionIds(List functionIds); +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysRoleService.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysRoleService.java new file mode 100644 index 0000000..a95abfe --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysRoleService.java @@ -0,0 +1,53 @@ +package com.njcn.product.cnuser.user.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.cnuser.user.pojo.param.SysRoleParam; +import com.njcn.product.cnuser.user.pojo.po.SysRole; + +import java.util.List; + +/** + * @author caozehui + * @date 2024-11-11 + */ +public interface ISysRoleService extends IService { + + /** + * 分页查询角色列表 + * + * @param queryParam 查询参数 + */ + Page listRole(SysRoleParam.QueryParam queryParam); + + /** + * 新增角色 + * + * @param sysRoleParam 角色参数 + * @return 是否成功 + */ + boolean addRole(SysRoleParam sysRoleParam); + + /** + * 更新角色 + * + * @param updateParam 更新参数 + * @return 是否成功 + */ + boolean updateRole(SysRoleParam.UpdateParam updateParam); + + /** + * 删除角色 + * + * @param ids 角色id列表 + * @return 是否成功 + */ + boolean deleteRole(List ids); + + /** + * 查询所有角色作为下拉框 + * + * @return 角色列表 + */ + List simpleList(); +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysUserRoleService.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysUserRoleService.java new file mode 100644 index 0000000..b5eb954 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysUserRoleService.java @@ -0,0 +1,56 @@ +package com.njcn.product.cnuser.user.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.cnuser.user.pojo.po.SysRole; +import com.njcn.product.cnuser.user.pojo.po.SysUserRole; + +import java.util.List; + +/** + * @author caozehui + * @date 2024-11-12 + */ +public interface ISysUserRoleService extends IService { + + /** + * 根据用户id获取角色 + * + * @param userId 用户id + * @return 角色信息 + */ + List listRoleByUserId(String userId); + + /** + * 新增用户角色关联数据 + * + * @param userId 用户id + * @param roleIds 角色id + * @return 成功返回true,失败返回false + */ + boolean addUserRole(String userId, List roleIds); + + /** + * 修改用户角色关联数据 + * + * @param userId 用户id + * @param roleIds 角色id + * @return 成功返回true,失败返回false + */ + boolean updateUserRole(String userId, List roleIds); + + /** + * 根据用户id删除用户角色关联数据 + * + * @param userIds 用户ids + * @return 成功返回true,失败返回false + */ + boolean deleteUserRoleByUserIds(List userIds); + + /** + * 根据角色id删除用户角色关联数据 + * + * @param roleIds 角色ids + * @return 成功返回true,失败返回false + */ + boolean deleteUserRoleByRoleIds(List roleIds); +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysUserService.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysUserService.java new file mode 100644 index 0000000..66f1d22 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/ISysUserService.java @@ -0,0 +1,106 @@ +package com.njcn.product.cnuser.user.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.cnuser.user.pojo.param.SysUserParam; +import com.njcn.product.cnuser.user.pojo.po.SysUser; + +import java.util.List; + +/** + * @author caozehui + * @since 2024-11-08 + */ +public interface ISysUserService extends IService { + + /** + * 分页查询用户列表 + * + * @param queryParam 分页查询参数 + * @return 分页查询结果 + */ + Page listUser(SysUserParam.SysUserQueryParam queryParam); + + /** + * 根据登录名查询用户 + * + * @param loginName + * @return 用户对象,如果没有查询到则返回null + */ + SysUser getUserByLoginName(String loginName); + + /** + * 根据手机号查询用户 + * + * @param phone 手机号 + * @param isExcludeSelf 是否排除自己 + * @param id 排除自己时需要传入自己的ID + * @return 用户对象,如果没有查询到则返回null + */ + SysUser getUserByPhone(String phone, boolean isExcludeSelf, String id); + + /** + * 根据用户名(别名)查询用户 + * + * @param name 用户名(别名) + * @param isExcludeSelf 是否排除自己 + * @param id 排除自己时需要传入自己的ID + * @return 用户对象,如果没有查询到则返回null + */ + SysUser getUserByName(String name, boolean isExcludeSelf, String id); + + /** + * 根据邮箱查询用户 + * @param email 邮箱 + * @param isExcludeSelf 是否排除自己 + * @param id 排除自己时需要传入自己的ID + * @return 用户对象,如果没有查询到则返回null + */ + SysUser getUserByEmail(String email, boolean isExcludeSelf, String id); + + /** + * 新增用户 + * + * @param addUserParam 新增用户参数 + * @return 结果,true表示新增成功,false表示新增失败 + */ + boolean addUser(SysUserParam.SysUserAddParam addUserParam); + + /** + * 更新用户 + * + * @param updateUserParam 更新用户参数 + * @return 结果,true表示更新成功,false表示更新失败 + */ + boolean updateUser(SysUserParam.SysUserUpdateParam updateUserParam); + + /** + * 修改密码 + * @return 结果,true表示修改成功,false表示修改失败 + */ + boolean updatePassword(SysUserParam.SysUserUpdatePasswordParam param); + + /** + * 批量删除用户 + * + * @param ids 用户ID列表 + * @return 结果,true表示删除成功,false表示删除失败 + */ + boolean deleteUser(List ids); + + /** + * 根据登录名和密码查询用户 + * + * @param loginName 登录名 + * @param password 密码 + * @return 用户对象,如果没有查询到则返回null + */ + SysUser getUserByLoginNameAndPassword(String loginName, String password); + + /** + * 更新用户登录时间为当前时间 + * + * @param userId + */ + boolean updateLoginTime(String userId); +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/DeptServiceImpl.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/DeptServiceImpl.java new file mode 100644 index 0000000..615cae1 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/DeptServiceImpl.java @@ -0,0 +1,75 @@ +package com.njcn.product.cnuser.user.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNode; +import cn.hutool.core.lang.tree.TreeUtil; +import cn.hutool.core.text.StrPool; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; + +import com.njcn.product.cnuser.user.mapper.DeptMapper; +import com.njcn.product.cnuser.user.pojo.po.Dept; +import com.njcn.product.cnuser.user.pojo.vo.DeptAllTreeVO; +import com.njcn.product.cnuser.user.service.IDeptService; +import com.njcn.web.factory.PageFactory; +import com.njcn.web.utils.RequestUtil; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + *

+ * 服务实现类 + *

+ * + * @author hongawen + * @since 2021-12-13 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DeptServiceImpl extends ServiceImpl implements IDeptService { + + + + + + + @Override + public List loginDeptTree(String deptIndex) { + List deptList = this.baseMapper.getAllDeptTree(deptIndex, Stream.of(0).collect(Collectors.toList())); + return deptList.stream() + .filter(deptVO -> deptVO.getId().equals(deptIndex)) + .peek(deptFirst -> deptFirst.setChildren(getChildrens(deptFirst, deptList))) + .collect(Collectors.toList()); + } + + /** + * 递归查找所有部门的下级部门 + */ + private List getChildrens(DeptAllTreeVO deptFirst, List allDept) { + return allDept.stream().filter(dept -> dept.getPid().equals(deptFirst.getId())) + .peek(deptVo -> { + deptVo.setChildren(getChildrens(deptVo, allDept)); + }).sorted(Comparator.comparing(DeptAllTreeVO::getSort)).collect(Collectors.toList()); + } + + +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysFunctionServiceImpl.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysFunctionServiceImpl.java new file mode 100644 index 0000000..324bda8 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysFunctionServiceImpl.java @@ -0,0 +1,234 @@ +package com.njcn.product.cnuser.user.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.constant.PatternRegex; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.cnuser.user.pojo.constant.FunctionConst; +import com.njcn.product.cnuser.user.pojo.enums.UserResponseEnum; +import com.njcn.product.cnuser.user.mapper.SysFunctionMapper; +import com.njcn.product.cnuser.user.pojo.param.SysFunctionParam; +import com.njcn.product.cnuser.user.pojo.po.SysFunction; +import com.njcn.product.cnuser.user.pojo.vo.MenuVO; +import com.njcn.product.cnuser.user.pojo.vo.MetaVO; +import com.njcn.product.cnuser.user.service.ISysFunctionService; +import com.njcn.product.cnuser.user.service.ISysRoleFunctionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * @author caozehui + * @date 2024-11-12 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SysFunctionServiceImpl extends ServiceImpl implements ISysFunctionService { + + private final ISysRoleFunctionService sysRoleFunctionService; + + @Override + public List getFunctionTreeByKeyword(String keyword) { + List functionTree = this.getFunctionTree(true); + filterTreeByName(functionTree, keyword); + return functionTree; + } + + @Override + @Transactional + public boolean addFunction(SysFunctionParam functionParam) { + functionParam.setName(functionParam.getName().trim()); + functionParam.setPath(functionParam.getPath().trim()); + functionParam.setComponent(functionParam.getComponent().trim()); + checkFunctionParam(functionParam, false); + SysFunction function = new SysFunction(); + BeanUtil.copyProperties(functionParam, function); + function.setState(DataStateEnum.ENABLE.getCode()); + if (Objects.equals(functionParam.getPid(), FunctionConst.FATHER_PID)) { + function.setPids(FunctionConst.FATHER_PID); + } else { + SysFunction fatherFunction = this.lambdaQuery().eq(SysFunction::getId, functionParam.getPid()).one(); + if (Objects.equals(fatherFunction.getPid(), FunctionConst.FATHER_PID)) { + function.setPids(functionParam.getPid()); + } else { + String pidS = fatherFunction.getPids(); + function.setPids(pidS + "," + functionParam.getPid()); + } + } + return this.save(function); + } + + @Override + @Transactional + public boolean updateFunction(SysFunctionParam.UpdateParam param) { + param.setName(param.getName().trim()); + boolean result = false; + param.setPath(param.getPath().trim()); + param.setComponent(param.getComponent().trim()); + checkFunctionParam(param, true); + SysFunction oldFunction = this.lambdaQuery().eq(SysFunction::getId, param.getId()).eq(SysFunction::getState, DataStateEnum.ENABLE.getCode()).one(); + List childrenList = this.lambdaQuery().eq(SysFunction::getPid, param.getId()).eq(SysFunction::getState, DataStateEnum.ENABLE.getCode()).list(); + if (oldFunction.getType().equals(FunctionConst.TYPE_MENU) && param.getType().equals(FunctionConst.TYPE_BUTTON) && !CollectionUtils.isEmpty(childrenList)) { + throw new BusinessException(UserResponseEnum.EXISTS_CHILDREN_NOT_UPDATE); + } else { + SysFunction function = new SysFunction(); + BeanUtil.copyProperties(param, function); + result = this.updateById(function); + } + return result; + } + + @Override + @Transactional + public boolean deleteFunction(String id) { + boolean result1 = false; + sysRoleFunctionService.deleteRoleFunctionByFunctionIds(Collections.singletonList(id)); + List childrenList = this.lambdaQuery().eq(SysFunction::getState, DataStateEnum.ENABLE.getCode()).eq(SysFunction::getPid, id).list(); + if (CollectionUtils.isEmpty(childrenList)) { + result1 = this.lambdaUpdate().set(SysFunction::getState, DataStateEnum.DELETED.getCode()).in(SysFunction::getId, id).update(); + } else { + throw new BusinessException(UserResponseEnum.EXISTS_CHILDREN_NOT_DELETE); + } + return result1; + } + + @Override + public List getFunctionTree(boolean isContainButton) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysFunction::getState, DataStateEnum.ENABLE.getCode()); + if (isContainButton) { + wrapper.in(SysFunction::getType, FunctionConst.TYPE_MENU, FunctionConst.TYPE_BUTTON); + } else { + wrapper.in(SysFunction::getType, FunctionConst.TYPE_MENU); + } + List allFunctions = this.list(wrapper); + return allFunctions.stream().filter(fun -> Objects.equals(FunctionConst.FATHER_PID, fun.getPid())).peek(funS -> funS.setChildren(getChildrenList(funS, allFunctions))).sorted(Comparator.comparingInt(SysFunction::getSort)).collect(Collectors.toList()); + } + + @Override + public List getMenuByUserId(String userId) { + List menu = this.baseMapper.getMenuByUserId(userId); + menu.stream().forEach(m -> { + MetaVO meta = new MetaVO(); + meta.setIcon(m.getIcon()); + meta.setTitle(m.getName()); + meta.setIsLink(""); + meta.setHide(false); + meta.setFull(false); + meta.setAffix(false); + if("home".equals(m.getCode())){ + meta.setAffix(true); + } + meta.setKeepAlive(true); + m.setMeta(meta); + m.setName(m.getCode()); + }); + return menu.stream().filter(fun -> Objects.equals(FunctionConst.FATHER_PID, fun.getPid())).peek(funS -> { + List childrenList = getChildrenList(funS, menu); + if (ObjectUtil.isNull(childrenList) || childrenList.size() == 0) { + funS.setRedirect(null); + } else { + funS.setRedirect(funS.getComponent()); + funS.setComponent(null); + } + funS.setChildren(childrenList); + }).sorted(Comparator.comparingInt(MenuVO::getSort)).collect(Collectors.toList()); + } + + @Override + public Map> getButtonByUserId(String userId) { + List sysFunctions = this.baseMapper.getButtonByUserId(userId); + + Map> buttonMap = new HashMap<>(); + sysFunctions.stream().collect(Collectors.groupingBy(SysFunction::getPid)).forEach((k, v) -> { + SysFunction fatherFunction = this.getById(k); + if (ObjectUtil.isNotNull(fatherFunction)) { + buttonMap.put(fatherFunction.getCode(), v.stream().map(SysFunction::getCode).collect(Collectors.toList())); + } + }); + return buttonMap; + } + + private List getChildrenList(T currMenu, List categories) { + return categories.stream().filter(o -> Objects.equals(o.getPid(), currMenu.getId())).peek(o -> o.setChildren(getChildrenList(o, categories))).sorted(Comparator.comparingInt(SysFunction::getSort)).collect(Collectors.toList()); + } + + /** + * 校验参数, + * 1.检查是否存在相同名称的菜单 + * 名称 && 路径做唯一判断 + */ + private void checkFunctionParam(SysFunctionParam functionParam, boolean isExcludeSelf) { + if (functionParam.getType().equals(FunctionConst.TYPE_MENU)) { + if (StrUtil.isBlank(functionParam.getComponent())) { + throw new BusinessException(UserResponseEnum.COMPONENT_NOT_BLANK); + } + if (StrUtil.isBlank(functionParam.getPath()) || !Pattern.matches(PatternRegex.FUNCTION_PATH_REGEX, functionParam.getPath())) { + throw new BusinessException(UserResponseEnum.FUNCTION_PATH_FORMAT_ERROR); + } + if(StrUtil.isBlank(functionParam.getComponent()) || !Pattern.matches(PatternRegex.FUNCTION_COMPONENT_REGEX, functionParam.getComponent())){ + throw new BusinessException(UserResponseEnum.FUNCTION_COMPONENT_FORMAT_ERROR); + } + } + LambdaQueryWrapper functionLambdaQueryWrapper = new LambdaQueryWrapper<>(); + // 同一个pid下,名称、编码、路径、组件地址不能重复 + functionLambdaQueryWrapper + .eq(SysFunction::getPid, functionParam.getPid()) + .eq(SysFunction::getState, DataStateEnum.ENABLE.getCode()) + .and(obj -> obj.eq(SysFunction::getName, functionParam.getName()).or() + .eq(SysFunction::getCode, functionParam.getCode()).or() + .eq(StrUtil.isNotBlank(functionParam.getPath()), SysFunction::getPath, functionParam.getPath()).or() + .eq(StrUtil.isNotBlank(functionParam.getComponent()), SysFunction::getComponent, functionParam.getComponent()) + ); + //更新的时候,需排除当前记录 + if (isExcludeSelf) { + if (functionParam instanceof SysFunctionParam.UpdateParam) { + functionLambdaQueryWrapper.ne(SysFunction::getId, ((SysFunctionParam.UpdateParam) functionParam).getId()); + } + } + int countByAccount = this.count(functionLambdaQueryWrapper); + //大于等于1个则表示重复 + if (countByAccount >= 1) { + throw new BusinessException(UserResponseEnum.EXISTS_SAME_MENU_CHILDREN); + } + } + + private List filterTreeByName(List tree, String keyword) { + if (CollectionUtils.isEmpty(tree) || !StrUtil.isNotBlank(keyword)) { + return tree; + } + filter(tree, keyword); + return tree; + } + + private void filter(List list, String keyword) { + for (int i = list.size() - 1; i >= 0; i--) { + SysFunction function = list.get(i); + List children = function.getChildren(); + if (!function.getName().contains(keyword)) { + if (!CollectionUtils.isEmpty(children)) { + filter(children, keyword); + } + if (CollectionUtils.isEmpty(function.getChildren())) { + list.remove(i); + } + } +// else { +// if (!CollectionUtils.isEmpty(children)) { +// filter(children, keyword); +// } +// } + } + } +} \ No newline at end of file diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysRoleFunctionServiceImpl.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysRoleFunctionServiceImpl.java new file mode 100644 index 0000000..89aecea --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysRoleFunctionServiceImpl.java @@ -0,0 +1,68 @@ +package com.njcn.product.cnuser.user.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.cnuser.user.mapper.SysRoleFunctionMapper; +import com.njcn.product.cnuser.user.pojo.po.SysFunction; +import com.njcn.product.cnuser.user.pojo.po.SysRoleFunction; +import com.njcn.product.cnuser.user.service.ISysRoleFunctionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author caozehui + * @date 2024-11-12 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SysRoleFunctionServiceImpl extends ServiceImpl implements ISysRoleFunctionService { + + @Override + public List listFunctionByRoleId(String roleId) { + return this.baseMapper.getFunctionListByRoleId(roleId); + } + + @Override + @Transactional + public boolean updateRoleFunction(String roleId, List functionIds) { + //删除原有关系 + this.deleteRoleFunctionByRoleIds(Collections.singletonList(roleId)); + //新增关系 + List roleFunctions = new ArrayList<>(); + functionIds.forEach(functionId -> { + SysRoleFunction roleFunction = new SysRoleFunction(); + roleFunction.setRoleId(roleId); + roleFunction.setFunctionId(functionId); + roleFunctions.add(roleFunction); + }); + if (CollectionUtil.isEmpty(roleFunctions)) { + return true; + } else { + return this.saveBatch(roleFunctions); + } + } + + @Override + @Transactional + public boolean deleteRoleFunctionByRoleIds(List roleIds) { + LambdaQueryWrapper lambdaQuery = new LambdaQueryWrapper<>(); + lambdaQuery.in(SysRoleFunction::getRoleId, roleIds); + return this.remove(lambdaQuery); + } + + @Override + @Transactional + public boolean deleteRoleFunctionByFunctionIds(List functionIds) { + LambdaQueryWrapper lambdaQuery = new LambdaQueryWrapper<>(); + lambdaQuery.in(SysRoleFunction::getFunctionId, functionIds); + return this.remove(lambdaQuery); + } +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysRoleServiceImpl.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..4784bb1 --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,128 @@ +package com.njcn.product.cnuser.user.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.cnuser.user.mapper.SysRoleMapper; +import com.njcn.product.cnuser.user.pojo.constant.RoleConst; +import com.njcn.product.cnuser.user.pojo.enums.UserResponseEnum; +import com.njcn.product.cnuser.user.pojo.param.SysRoleParam; +import com.njcn.product.cnuser.user.pojo.po.SysRole; +import com.njcn.product.cnuser.user.service.ISysRoleFunctionService; +import com.njcn.product.cnuser.user.service.ISysRoleService; +import com.njcn.product.cnuser.user.service.ISysUserRoleService; +import com.njcn.web.factory.PageFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * @author caozehui + * @date 2024-11-11 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SysRoleServiceImpl extends ServiceImpl implements ISysRoleService { + + private final ISysUserRoleService sysUserRoleService; + private final ISysRoleFunctionService sysRoleFunctionService; + + @Override + public Page listRole(SysRoleParam.QueryParam queryParam) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(queryParam)) { + queryWrapper.like(StrUtil.isNotBlank(queryParam.getName()), "sys_role.name", queryParam.getName()).eq(StrUtil.isNotBlank(queryParam.getCode()), "sys_role.code", queryParam.getCode()).eq(ObjectUtil.isNotNull(queryParam.getType()), "sys_role.type", queryParam.getType()); + } +// if (queryParam.getType().equals(0)) { +// queryWrapper.in("sys_role.type", queryParam.getType(), 1); +// } else if (queryParam.getType().equals(1)) { +// queryWrapper.eq("sys_role.type", 2); +// } + queryWrapper.eq("sys_role.state", DataStateEnum.ENABLE.getCode()).orderByDesc("sys_role.Update_Time"); + return this.baseMapper.selectPage(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), queryWrapper); + } + + @Override + @Transactional + public boolean addRole(SysRoleParam sysRoleParam) { + sysRoleParam.setName(sysRoleParam.getName().trim()); + checkRepeat(sysRoleParam, false); + SysRole role = new SysRole(); + BeanUtil.copyProperties(sysRoleParam, role); + //默认为正常状态 + role.setState(DataStateEnum.ENABLE.getCode()); + return this.save(role); + } + + @Override + @Transactional + public boolean updateRole(SysRoleParam.UpdateParam updateParam) { + updateParam.setName(updateParam.getName().trim()); + checkRepeat(updateParam, true); + //不能修改超级管理员角色 + Integer count = this.lambdaQuery() + .in(SysRole::getType, RoleConst.TYPE_SUPER_ADMINISTRATOR) + .eq(SysRole::getId, updateParam.getId()).eq(SysRole::getState, DataStateEnum.ENABLE.getCode()).count(); + if (count > 0) { + throw new BusinessException(UserResponseEnum.SUPER_ADMINSTRATOR_ROLE_CANNOT_UPDATE); + } + SysRole role = new SysRole(); + BeanUtil.copyProperties(updateParam, role); + return this.updateById(role); + } + + @Override + @Transactional + public boolean deleteRole(List ids) { + //不能删除超级管理员角色 + Integer count = this.lambdaQuery() + .in(SysRole::getType, RoleConst.TYPE_SUPER_ADMINISTRATOR) + .in(SysRole::getId, ids).eq(SysRole::getState, DataStateEnum.ENABLE.getCode()).count(); + if (count > 0) { + throw new BusinessException(UserResponseEnum.SUPER_ADMINSTRATOR_ROLE_CANNOT_DELETE); + } + // 删除角色和用户的绑定 + sysUserRoleService.deleteUserRoleByRoleIds(ids); + //删除角色和资源的绑定 + sysRoleFunctionService.deleteRoleFunctionByRoleIds(ids); + return this.lambdaUpdate().set(SysRole::getState, DataStateEnum.DELETED.getCode()).in(SysRole::getId, ids).update(); + } + + @Override + public List simpleList() { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.select(SysRole::getId, SysRole::getName).eq(SysRole::getState, DataStateEnum.ENABLE.getCode()); + return this.baseMapper.selectList(lambdaQueryWrapper); + } + + /** + * 校验参数,检查是否存在相同名称或编码的角色 + */ + private void checkRepeat(SysRoleParam roleParam, boolean isExcludeSelf) { + LambdaQueryWrapper roleLambdaQueryWrapper = new LambdaQueryWrapper<>(); + roleLambdaQueryWrapper + .eq(SysRole::getState, DataStateEnum.ENABLE.getCode()) + .and(w -> w.eq(SysRole::getName, roleParam.getName()).or().eq(SysRole::getCode, roleParam.getCode())); + //更新的时候,需排除当前记录 + if (isExcludeSelf) { + if (roleParam instanceof SysRoleParam.UpdateParam) { + roleLambdaQueryWrapper.ne(SysRole::getId, ((SysRoleParam.UpdateParam) roleParam).getId()); + } + } + int countByAccount = this.count(roleLambdaQueryWrapper); + //大于等于1个则表示重复 + if (countByAccount >= 1) { + throw new BusinessException(UserResponseEnum.NAME_OR_CODE_REPEAT); + } + } +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysUserRoleServiceImpl.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysUserRoleServiceImpl.java new file mode 100644 index 0000000..07ac67e --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysUserRoleServiceImpl.java @@ -0,0 +1,79 @@ +package com.njcn.product.cnuser.user.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.cnuser.user.mapper.SysUserRoleMapper; +import com.njcn.product.cnuser.user.pojo.po.SysRole; +import com.njcn.product.cnuser.user.pojo.po.SysUserRole; +import com.njcn.product.cnuser.user.service.ISysUserRoleService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author caozehui + * @date 2024-11-12 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SysUserRoleServiceImpl extends ServiceImpl implements ISysUserRoleService { + + @Override + public List listRoleByUserId(String userId) { + return this.baseMapper.getRoleListByUserId(userId); + } + + @Override + @Transactional + public boolean addUserRole(String userId, List roleIds) { + List userRoles = new ArrayList<>(); + if (!CollectionUtil.isEmpty(roleIds)) { + roleIds.forEach(id -> { + SysUserRole userRole = new SysUserRole(); + userRole.setUserId(userId); + userRole.setRoleId(id); + userRoles.add(userRole); + }); + } + return this.saveBatch(userRoles); + } + + @Override + @Transactional + public boolean updateUserRole(String userId, List roleIds) { + //删除原有关系 + this.deleteUserRoleByUserIds(Collections.singletonList(userId)); + //新增关系 + List userROles = new ArrayList<>(); + roleIds.forEach(role -> { + SysUserRole userRole = new SysUserRole(); + userRole.setUserId(userId); + userRole.setRoleId(role); + userROles.add(userRole); + }); + return this.saveBatch(userROles); + } + + @Override + @Transactional + public boolean deleteUserRoleByUserIds(List userIds) { + LambdaQueryWrapper lambdaQuery = new LambdaQueryWrapper<>(); + lambdaQuery.in(SysUserRole::getUserId, userIds); + return this.remove(lambdaQuery); + } + + @Override + @Transactional + public boolean deleteUserRoleByRoleIds(List roleIds) { + LambdaQueryWrapper lambdaQuery = new LambdaQueryWrapper<>(); + lambdaQuery.in(SysUserRole::getRoleId, roleIds); + return this.remove(lambdaQuery); + } +} diff --git a/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysUserServiceImpl.java b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..d1a6dca --- /dev/null +++ b/cn-user/src/main/java/com/njcn/product/cnuser/user/service/impl/SysUserServiceImpl.java @@ -0,0 +1,212 @@ +package com.njcn.product.cnuser.user.service.impl; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.utils.sm.Sm4Utils; +import com.njcn.db.mybatisplus.constant.DbConstant; +import com.njcn.product.cnuser.user.pojo.constant.RoleConst; +import com.njcn.product.cnuser.user.pojo.constant.UserConst; +import com.njcn.product.cnuser.user.pojo.enums.UserResponseEnum; +import com.njcn.product.cnuser.user.mapper.SysUserMapper; +import com.njcn.product.cnuser.user.pojo.po.SysRole; +import com.njcn.product.cnuser.user.pojo.po.SysUser; +import com.njcn.product.cnuser.user.service.ISysUserRoleService; +import com.njcn.product.cnuser.user.pojo.param.SysUserParam; +import com.njcn.product.cnuser.user.service.ISysUserService; +import com.njcn.web.factory.PageFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author caozehui + * @date 2024-11-08 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SysUserServiceImpl extends ServiceImpl implements ISysUserService { + + private final ISysUserRoleService sysUserRoleService; + + @Override + public Page listUser(SysUserParam.SysUserQueryParam queryParam) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(queryParam)) { + queryWrapper.like(StrUtil.isNotBlank(queryParam.getName()), "sys_user.name", queryParam.getName()) + .between(ObjectUtil.isAllNotEmpty(queryParam.getSearchBeginTime(), queryParam.getSearchEndTime()), "sys_user.Login_Time", queryParam.getSearchBeginTime(), queryParam.getSearchEndTime()); + //排序 + if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) { + queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy())); + } else { + queryWrapper.orderByDesc("sys_user.update_time"); + } + } else { + queryWrapper.orderByDesc("sys_user.update_time"); + } + queryWrapper.ne("sys_user.state", UserConst.STATE_DELETE); + Page page = this.baseMapper.selectPage(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), queryWrapper); + page.getRecords().forEach(sysUser -> { + List sysRoles = sysUserRoleService.listRoleByUserId(sysUser.getId()); + sysUser.setRoleIds(sysRoles.stream().map(SysRole::getId).collect(Collectors.toList())); + sysUser.setRoleNames(sysRoles.stream().map(SysRole::getName).collect(Collectors.toList())); + }); + return page; + } + + @Override + public SysUser getUserByLoginName(String loginName) { + return this.lambdaQuery().ne(SysUser::getState, UserConst.STATE_DELETE).eq(SysUser::getLoginName, loginName).one(); + } + + @Override + public SysUser getUserByPhone(String phone, boolean isExcludeSelf, String id) { + LambdaQueryWrapper lambdaQuery = new LambdaQueryWrapper<>(); + lambdaQuery.eq(SysUser::getPhone, phone).ne(SysUser::getState, UserConst.STATE_DELETE); + if (isExcludeSelf) { + lambdaQuery.ne(SysUser::getId, id); + } + return this.baseMapper.selectOne(lambdaQuery); + } + + @Override + public SysUser getUserByName(String name, boolean isExcludeSelf, String id) { + LambdaQueryWrapper lambdaQuery = new LambdaQueryWrapper<>(); + lambdaQuery.eq(SysUser::getName, name).ne(SysUser::getState, UserConst.STATE_DELETE); + if (isExcludeSelf) { + lambdaQuery.ne(SysUser::getId, id); + } + return this.baseMapper.selectOne(lambdaQuery); + } + + @Override + public SysUser getUserByEmail(String email, boolean isExcludeSelf, String id) { + LambdaQueryWrapper lambdaQuery = new LambdaQueryWrapper<>(); + lambdaQuery.eq(SysUser::getEmail, email).ne(SysUser::getState, UserConst.STATE_DELETE); + if (isExcludeSelf) { + lambdaQuery.ne(SysUser::getId, id); + } + return this.baseMapper.selectOne(lambdaQuery); + } + + @Override + @Transactional + public boolean addUser(SysUserParam.SysUserAddParam addUserParam) { + addUserParam.setName(addUserParam.getName().trim()); + addUserParam.setLoginName(addUserParam.getLoginName().trim()); + if (UserConst.SUPER_ADMIN.equals(addUserParam.getLoginName())) { + throw new BusinessException(UserResponseEnum.SUPER_ADMIN_REPEAT); + } + if (!Objects.isNull(getUserByLoginName(addUserParam.getLoginName()))) { + throw new BusinessException(UserResponseEnum.LOGIN_NAME_REPEAT); + } + checkRepeat(addUserParam, false, null); + SysUser sysUser = new SysUser(); + BeanUtils.copyProperties(addUserParam, sysUser); + String secretkey = Sm4Utils.globalSecretKey; + Sm4Utils sm4 = new Sm4Utils(secretkey); + sysUser.setPassword(sm4.encryptData_ECB(sysUser.getPassword())); + sysUser.setLoginTime(LocalDateTimeUtil.now()); + sysUser.setLoginErrorTimes(0); + sysUser.setState(UserConst.STATE_ENABLE); + boolean result = this.save(sysUser); + sysUserRoleService.addUserRole(sysUser.getId(), addUserParam.getRoleIds()); + return result; + } + + @Override + @Transactional + public boolean updateUser(SysUserParam.SysUserUpdateParam updateUserParam) { + updateUserParam.setName(updateUserParam.getName().trim()); + checkRepeat(updateUserParam, true, updateUserParam.getId()); + SysUser sysUser = new SysUser(); + BeanUtils.copyProperties(updateUserParam, sysUser); + sysUserRoleService.updateUserRole(sysUser.getId(), updateUserParam.getRoleIds()); + return this.updateById(sysUser); + } + + @Override + @Transactional + public boolean updatePassword(SysUserParam.SysUserUpdatePasswordParam param) { + if (param.getOldPassword().equals(param.getNewPassword())) { + throw new BusinessException(UserResponseEnum.PASSWORD_SAME); + } + SysUser user = lambdaQuery().ne(SysUser::getState, UserConst.STATE_DELETE).eq(SysUser::getId, param.getId()).one(); + if (ObjectUtil.isNotNull(user)) { + String secretkey = Sm4Utils.globalSecretKey; + Sm4Utils sm4 = new Sm4Utils(secretkey); + if (sm4.encryptData_ECB(param.getOldPassword()).equals(user.getPassword())) { + user.setPassword(sm4.encryptData_ECB(param.getNewPassword())); + return this.updateById(user); + }else { + throw new BusinessException(UserResponseEnum.OLD_PASSWORD_ERROR); + } + } + return false; + } + + @Override + @Transactional + public boolean deleteUser(List ids) { + for (String id : ids) { + List sysRoles = sysUserRoleService.listRoleByUserId(id); + for (SysRole sysRole : sysRoles) { + if (sysRole.getType().equals(RoleConst.TYPE_SUPER_ADMINISTRATOR)) { + throw new BusinessException(UserResponseEnum.SUPER_ADMIN_CANNOT_DELETE); // 超级管理员角色不能删除 + } + } + } + // 删除用户角色关联数据 + sysUserRoleService.deleteUserRoleByUserIds(ids); + return this.lambdaUpdate() + .set(SysUser::getState, UserConst.STATE_DELETE) + .in(SysUser::getId, ids) + .update(); + } + + @Override + public SysUser getUserByLoginNameAndPassword(String loginName, String password) { + String secretkey = Sm4Utils.globalSecretKey; + Sm4Utils sm4 = new Sm4Utils(secretkey); + return this.lambdaQuery().ne(SysUser::getState, UserConst.STATE_DELETE) + .eq(SysUser::getLoginName, loginName) + .eq(SysUser::getPassword, sm4.encryptData_ECB(password)).one(); + } + + @Override + public boolean updateLoginTime(String userId) { + return this.lambdaUpdate().eq(SysUser::getId, userId).set(SysUser::getLoginTime, LocalDateTimeUtil.now()).update(); + } + + /** + * 校验重复 + * + * @param sysUserParam 检查对象 + * @param isExcludeSelf 是否排除自己 + * @param id 排除自己id + */ + private void checkRepeat(SysUserParam sysUserParam, boolean isExcludeSelf, String id) { + if (!Objects.isNull(getUserByName(sysUserParam.getName(), isExcludeSelf, id))) { + throw new BusinessException(UserResponseEnum.USER_NAME_REPEAT); + } + if (StringUtils.isNotBlank(sysUserParam.getPhone()) && !Objects.isNull(getUserByPhone(sysUserParam.getPhone(), isExcludeSelf, id))) { + throw new BusinessException(UserResponseEnum.REGISTER_PHONE_FAIL); + } + if (StringUtils.isNotBlank(sysUserParam.getEmail()) && !Objects.isNull(getUserByEmail(sysUserParam.getEmail(), isExcludeSelf, id))) { + throw new BusinessException(UserResponseEnum.REGISTER_EMAIL_FAIL); + } + } +} diff --git a/cn-zutai/.gitignore b/cn-zutai/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/cn-zutai/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/cn-zutai/pom.xml b/cn-zutai/pom.xml new file mode 100644 index 0000000..fe358ed --- /dev/null +++ b/cn-zutai/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + com.njcn.product + CN_Product + 1.0.0 + + + cn-zutai + 1.0.0 + cn-zutai + cn-zutai + + + + com.njcn + njcn-common + 0.0.1 + + + + com.njcn + mybatis-plus + 0.0.1 + + + + com.njcn + spingboot2.3.12 + 2.3.12 + + + + + com.njcn + common-oss + 1.0.0 + + + com.njcn + common-web + + + + + com.google.code.gson + gson + + + com.njcn + pqs-influx + 1.0.0 + compile + + + + + diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/CsConfigurationController.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/CsConfigurationController.java new file mode 100644 index 0000000..0d15940 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/CsConfigurationController.java @@ -0,0 +1,110 @@ +package com.njcn.product.cnzutai.zutai.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; + +import com.njcn.minioss.bo.MinIoUploadResDTO; +import com.njcn.product.cnzutai.zutai.pojo.param.CsConfigurationParm; +import com.njcn.product.cnzutai.zutai.pojo.po.CsConfigurationPO; +import com.njcn.product.cnzutai.zutai.pojo.vo.CsConfigurationVO; +import com.njcn.product.cnzutai.zutai.service.CsConfigurationService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Objects; + +/** +* (cs_configuration)表控制层 +* +* @author xxxxx +*/ +@Slf4j +@RestController +@RequestMapping("/cs-harmonic-boot/csconfiguration") +@Api(tags = "组态项目") +@AllArgsConstructor +public class CsConfigurationController extends BaseController { + + private final CsConfigurationService csConfigurationService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/add") + @ApiOperation("新增组态项目") + @ApiImplicitParam(name = "csConfigurationParm", value = "新增组态项目参数", required = true) + public HttpResult add(@RequestBody @Validated CsConfigurationParm csConfigurationParm){ + String methodDescribe = getMethodDescribe("add"); + + boolean save = csConfigurationService.add(csConfigurationParm); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, save, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/audit") + @ApiOperation("修改组态项目") + @ApiImplicitParam(name = "auditParm", value = "修改组态项目参数", required = true) + public HttpResult audit(@RequestBody @Validated CsConfigurationParm.CsConfigurationAuditParam auditParm){ + String methodDescribe = getMethodDescribe("audit"); + + boolean save = csConfigurationService.audit (auditParm); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, save, methodDescribe); + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @GetMapping("/active") + @ApiOperation("激活组态项目") + public HttpResult active(@RequestParam("id")String id){ + String methodDescribe = getMethodDescribe("active"); + boolean active = csConfigurationService.active(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, active, methodDescribe); + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @GetMapping("/getActive") + @ApiOperation("获取激活组态项目") + public HttpResult getActive(){ + String methodDescribe = getMethodDescribe("active"); + CsConfigurationPO active = csConfigurationService.getActive(); + if(Objects.nonNull(active)){ + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, active, methodDescribe); + }else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queryPage") + @ApiOperation("组态项目分页查询") + @ApiImplicitParam(name = "csConfigurationQueryParam", value = "组态项目查询参数", required = true) + public HttpResult> queryPage(@RequestBody @Validated CsConfigurationParm.CsConfigurationQueryParam csConfigurationQueryParam ){ + String methodDescribe = getMethodDescribe("queryPage"); + + IPage page = csConfigurationService.queryPage (csConfigurationQueryParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, page, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPLOAD) + @PostMapping("/uploadImage") + @ApiOperation("上传底图") + @ApiImplicitParam(name = "file", value = "底图文件", required = true) + public HttpResult uploadImage(@RequestParam("file") MultipartFile issuesFile){ + String methodDescribe = getMethodDescribe("uploadImage"); + String filePath = csConfigurationService.uploadImage(issuesFile); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,new MinIoUploadResDTO(issuesFile.getOriginalFilename(),filePath), methodDescribe); + } + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/CsPagePOController.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/CsPagePOController.java new file mode 100644 index 0000000..85c785d --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/CsPagePOController.java @@ -0,0 +1,69 @@ +package com.njcn.product.cnzutai.zutai.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; + +import com.njcn.product.cnzutai.zutai.pojo.param.CsPageParm; +import com.njcn.product.cnzutai.zutai.pojo.vo.CsPageVO; +import com.njcn.product.cnzutai.zutai.service.CsPagePOService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** +* (cs_page)表控制层 +* +* @author xxxxx +*/ +@RestController +@RequestMapping("/cs-harmonic-boot/cspage") +@Api(tags = "组态项目页面") +@AllArgsConstructor +public class CsPagePOController extends BaseController { + private final CsPagePOService csPagePOService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/add") + @ApiOperation("新增组态页面") +// @ApiImplicitParam(name = "csPageParm", value = "新增组态项目参数", required = true) + public HttpResult add( @Validated CsPageParm csPageParm){ + String methodDescribe = getMethodDescribe("add"); + + boolean flag = csPagePOService.add (csPageParm); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, flag, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/audit") + @ApiOperation("修改组态页面") + public HttpResult audit( @Validated CsPageParm.CsPageParmAuditParam auditParm){ + String methodDescribe = getMethodDescribe("audit"); + + boolean save = csPagePOService.audit (auditParm); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, save, methodDescribe); + } + + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/queryPage") + @ApiOperation("组态页面分页查询") + @ApiImplicitParam(name = "csPageParam", value = "组态项目查询参数", required = true) + public HttpResult> queryPage(@RequestBody @Validated CsPageParm.CsPageParmQueryParam csPageParam ){ + String methodDescribe = getMethodDescribe("queryPage"); + + IPage page = csPagePOService.queryPage (csPageParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, page, methodDescribe); + } +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/ElementController.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/ElementController.java new file mode 100644 index 0000000..b87fe4a --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/ElementController.java @@ -0,0 +1,68 @@ +package com.njcn.product.cnzutai.zutai.controller; + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; + +import com.njcn.product.cnzutai.zutai.pojo.param.ElementParam; +import com.njcn.product.cnzutai.zutai.service.IElementService; +import com.njcn.product.cnzutai.zutai.pojo.po.CsElement; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2023/7/12 16:07 + */ +@RestController +@RequestMapping("cs-system-boot/csElement") +@Api(tags = "组态图元") +@AllArgsConstructor +public class ElementController extends BaseController { + + private final IElementService csElementService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/add") + @ApiOperation("新增图元") + public HttpResult add(@Validated ElementParam param){ + String methodDescribe = getMethodDescribe("add"); + CsElement csElement = csElementService.addElement(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, csElement, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/find") + @ApiOperation("查询图元") + public HttpResult> find(){ + String methodDescribe = getMethodDescribe("find"); + List list = csElementService.findElement(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/delete") + @ApiOperation("删除图元") + @ApiImplicitParam(name = "id", value = "图元Id", required = true) + public HttpResult deleteById(@RequestParam("id") String id){ + String methodDescribe = getMethodDescribe("deleteById"); + csElementService.deleteById(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/RealTimeDataController.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/RealTimeDataController.java new file mode 100644 index 0000000..f20b452 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/controller/RealTimeDataController.java @@ -0,0 +1,43 @@ +package com.njcn.product.cnzutai.zutai.controller; + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.HttpResultUtil; +import com.njcn.product.cnzutai.zutai.pojo.vo.CsRtDataVO; +import com.njcn.product.cnzutai.zutai.pojo.vo.RealTimeDataVo; +import com.njcn.product.cnzutai.zutai.service.ILineTargetService; +import com.njcn.web.controller.BaseController; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Description: + * Date: 2025/09/10 下午 7:46【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Slf4j +@RestController +@RequestMapping("/data") +@Api(tags = "装置数据") +@AllArgsConstructor +public class RealTimeDataController extends BaseController { + private final ILineTargetService lineTargetService; + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/realTimeData") + @ApiOperation("设备监控-》准实时数据") + public HttpResult> realTimeData(@RequestParam String pageId) { + String methodDescribe = getMethodDescribe("realTimeData"); + List lineData = lineTargetService.getLineData(pageId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, lineData, methodDescribe); + } +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsConfigurationMapper.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsConfigurationMapper.java new file mode 100644 index 0000000..baf8c36 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsConfigurationMapper.java @@ -0,0 +1,19 @@ +package com.njcn.product.cnzutai.zutai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import com.njcn.product.cnzutai.zutai.pojo.param.CsConfigurationParm; +import com.njcn.product.cnzutai.zutai.pojo.po.CsConfigurationPO; +import org.apache.ibatis.annotations.Param; + +/** + * Description: + * Date: 2023/5/31 10:53【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CsConfigurationMapper extends BaseMapper { + Page queryPage(Page temppage, @Param("temp") CsConfigurationParm.CsConfigurationQueryParam csConfigurationQueryParam); +} \ No newline at end of file diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsElementMapper.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsElementMapper.java new file mode 100644 index 0000000..fa0dd9b --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsElementMapper.java @@ -0,0 +1,16 @@ +package com.njcn.product.cnzutai.zutai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.cnzutai.zutai.pojo.po.CsElement; + +/** + *

+ * 组态图元库 Mapper 接口 + *

+ * + * @author xuyang + * @since 2023-06-14 + */ +public interface CsElementMapper extends BaseMapper { + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsPagePOMapper.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsPagePOMapper.java new file mode 100644 index 0000000..7da5f04 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/CsPagePOMapper.java @@ -0,0 +1,15 @@ +package com.njcn.product.cnzutai.zutai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.cnzutai.zutai.pojo.po.CsPagePO; + +/** + * + * Description: + * Date: 2023/5/31 14:31【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CsPagePOMapper extends BaseMapper { +} \ No newline at end of file diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/mapping/CsConfigurationMapper.xml b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/mapping/CsConfigurationMapper.xml new file mode 100644 index 0000000..72fcf12 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/mapping/CsConfigurationMapper.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + id, `name`, `status`, create_by, create_time, update_by, update_time + + + + \ No newline at end of file diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/mapping/CsPagePOMapper.xml b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/mapping/CsPagePOMapper.xml new file mode 100644 index 0000000..b000f4c --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/mapper/mapping/CsPagePOMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + id, pid, `name`, `path`, `status`, create_by, create_time, update_by, update_time + + \ No newline at end of file diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/AskRealTimeDataDTO.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/AskRealTimeDataDTO.java new file mode 100644 index 0000000..61e08f7 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/AskRealTimeDataDTO.java @@ -0,0 +1,19 @@ +package com.njcn.product.cnzutai.zutai.pojo.dto; + +import lombok.Data; + +import java.util.List; + +/** + * Description: + * Date: 2025/09/18 下午 1:59【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class AskRealTimeDataDTO { + private String pageId; + private List lineIdList; + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/RealTimeDataDTO.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/RealTimeDataDTO.java new file mode 100644 index 0000000..23f41d1 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/RealTimeDataDTO.java @@ -0,0 +1,25 @@ +package com.njcn.product.cnzutai.zutai.pojo.dto; + +import com.njcn.product.cnzutai.zutai.pojo.vo.RealTimeDataVo; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * Description: + * Date: 2025/09/18 下午 2:00【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RealTimeDataDTO { + private Integer code; + private String message; + private Integer type =1; +// private List realTimeDataVoList; +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/ZuTaiDTO.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/ZuTaiDTO.java new file mode 100644 index 0000000..865c4aa --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/dto/ZuTaiDTO.java @@ -0,0 +1,50 @@ +package com.njcn.product.cnzutai.zutai.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +import java.util.List; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2023/6/14 20:07 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ZuTaiDTO { + + private List json; + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class DiagramElement{ + + private String id; + private String title; + private String keyId; + private String type; + private boolean resize; + private boolean rotate; + private boolean lock; + private boolean active; + private boolean hide; + private String tag; + private boolean use_proportional_scaling; + private String lineId; + private List lineList; + private String lineName; + @JsonProperty("UID") + private List> uid; + @JsonProperty("UIDNames") + + private List uidNames; + private List unit; + + } + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/enums/CsSystemResponseEnum.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/enums/CsSystemResponseEnum.java new file mode 100644 index 0000000..32d42ef --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/enums/CsSystemResponseEnum.java @@ -0,0 +1,35 @@ +package com.njcn.product.cnzutai.zutai.pojo.enums; + +import lombok.Getter; + +/** + * @author xuyang + * @version 1.0.0 + * @date 2023年04月17日 10:50 + */ +@Getter +public enum CsSystemResponseEnum { + + /** + * A0301 ~ A0399 用于治理系统模块的枚举 + *

+ */ + DICT_REPEAT("A0301","字典数据重复!"), + SAME_DATA_ERROR("A0301","数据重复"), + + CS_SYSTEM_COMMON_ERROR("A00302","治理系统模块异常"), + BIND_TARGET_ERROR("A00601","指标参数绑定异常"), + + + ; + + private final String code; + + private final String message; + + CsSystemResponseEnum(String code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/CsConfigurationParm.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/CsConfigurationParm.java new file mode 100644 index 0000000..902c44c --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/CsConfigurationParm.java @@ -0,0 +1,64 @@ +package com.njcn.product.cnzutai.zutai.pojo.param; + +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * Description: + * Date: 2023/5/31 10:35【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class CsConfigurationParm { + + + /** + * 组态项目名称 + */ + @ApiModelProperty(value = "组态项目名称") + @NotBlank(message="组态项目名称不能为空") + private String name; + + private String remark; + + private List projectIds; + + + private Integer orderBy; + + private String fileContent; + + + + + @Data + @EqualsAndHashCode(callSuper = true) + public static class CsConfigurationAuditParam extends CsConfigurationParm { + @ApiModelProperty("Id") + @NotBlank(message = "id不为空") + private String id; + @ApiModelProperty(value = "状态") + private String status; + } + + /** + * 分页查询实体 + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class CsConfigurationQueryParam extends BaseParam { + private String id; + + } + + + + +} \ No newline at end of file diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/CsPageParm.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/CsPageParm.java new file mode 100644 index 0000000..d3a6ac6 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/CsPageParm.java @@ -0,0 +1,53 @@ +package com.njcn.product.cnzutai.zutai.pojo.param; + +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.constraints.NotBlank; + +/** + * + * Description: + * Date: 2023/5/31 14:31【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +public class CsPageParm { + + /** + * 组态项目id + */ + @ApiModelProperty(value="组态项目id") + private String pid; + + private MultipartFile multipartFile; + + /** + * 组态页面文件路径 + */ + @ApiModelProperty(value = "组态页面json文件") + private String jsonFile; + + @Data + @EqualsAndHashCode(callSuper = true) + public static class CsPageParmAuditParam extends CsPageParm { + @ApiModelProperty("Id") + @NotBlank(message = "id不为空") + private String id; + @ApiModelProperty(value = "状态") + private String status; + } + @Data + @EqualsAndHashCode(callSuper = true) + public static class CsPageParmQueryParam extends BaseParam { + @ApiModelProperty("pid") + private String pid; + } +} \ No newline at end of file diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/ElementParam.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/ElementParam.java new file mode 100644 index 0000000..8326594 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/param/ElementParam.java @@ -0,0 +1,45 @@ +package com.njcn.product.cnzutai.zutai.pojo.param; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2023/7/12 16:23 + */ +@Data +public class ElementParam { + + @ApiModelProperty(value = "组件分类") + @NotBlank(message="组件分类不能为空!") + private String elementType; + + @ApiModelProperty(value = "组件子类型") + @NotBlank(message="组件子类型不能为空!") + private String elementSonType; + + @ApiModelProperty(value = "组件编码") + private String elementCode; + + @ApiModelProperty(value = "组件名称") + private String elementName; + + @ApiModelProperty(value = "组件标识") + private String elementMark; + + @ApiModelProperty(value = "图元文件") + @NotNull(message="图元文件不能为空!") + private MultipartFile multipartFile; + + @ApiModelProperty(value = "图元类型") + @NotBlank(message="图元类型不能为空!") + private String elementForm; + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsConfigurationPO.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsConfigurationPO.java new file mode 100644 index 0000000..2b2f5b9 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsConfigurationPO.java @@ -0,0 +1,72 @@ +package com.njcn.product.cnzutai.zutai.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Description: + * Date: 2023/5/31 10:35【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +@TableName(value = "cs_configuration") +public class CsConfigurationPO extends BaseEntity { + /** + * id + */ + @TableId(value = "id", type = IdType.ASSIGN_UUID) + private String id; + + /** + * 组态项目名称 + */ + @TableField(value = "`name`") + private String name; + + @TableField(value = "image_path") + private String imagePath; + + @TableField(value = "remark") + private String remark; + @TableField(value = "project_ids") + private String projectIds; + + @TableField(value = "order_By") + private Integer orderBy; + + /** + * 是否激活 0.否 1.是 + */ + private Integer active; + + + /** + * 状态 0:删除 1:正常 + */ + @TableField(value = "`status`") + private String status; + + + + public static final String COL_ID = "id"; + + public static final String COL_NAME = "name"; + + public static final String COL_STATUS = "status"; + + public static final String COL_CREATE_BY = "create_by"; + + public static final String COL_CREATE_TIME = "create_time"; + + public static final String COL_UPDATE_BY = "update_by"; + + public static final String COL_UPDATE_TIME = "update_time"; +} \ No newline at end of file diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsElement.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsElement.java new file mode 100644 index 0000000..1608df1 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsElement.java @@ -0,0 +1,70 @@ +package com.njcn.product.cnzutai.zutai.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * 组态图元库 + *

+ * + * @author xuyang + * @since 2023-06-14 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("cs_element") +public class CsElement extends BaseEntity { + + private static final long serialVersionUID = 1L; + + + /** + * id + */ + private String id; + + /** + * 图元文件路径 + */ + private String path; + + /** + * 组件分类 + */ + private String elementType; + + /** + * 组件子类型 + */ + private String elementSonType; + + /** + * 组件编码 + */ + private String elementCode; + + /** + * 组件名称 + */ + private String elementName; + + /** + * 组件标识 + */ + private String elementMark; + + /** + * 状态 + */ + private Integer status; + + /** + * 图元类型 + */ + private String elementForm; + + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsPagePO.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsPagePO.java new file mode 100644 index 0000000..6bdfea9 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/po/CsPagePO.java @@ -0,0 +1,77 @@ +package com.njcn.product.cnzutai.zutai.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.db.mybatisplus.bo.BaseEntity; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2023/5/31 14:31【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +@TableName(value = "cs_page") +public class CsPagePO extends BaseEntity { + /** + * id + */ + @TableId(value = "id", type = IdType.ASSIGN_UUID) + private String id; + + /** + * 组态项目id + */ + @TableField(value = "pid") + private String pid; + + /** + * 组态页面名称 + */ + @TableField(value = "`name`") + private String name; + /*排序id + * */ + @TableField(value = "k_id") + private String kId; + + /** + * 组态页面文件路径 + */ + @TableField(value = "`path`") + private String path; + + /** + * 组态页面状态 + */ + @TableField(value = "`status`") + private String status; + + + + public static final String COL_ID = "id"; + + public static final String COL_PID = "pid"; + + public static final String COL_NAME = "name"; + public static final String COL_KID = "k_id"; + + public static final String COL_PATH = "path"; + + public static final String COL_STATUS = "status"; + + public static final String COL_CREATE_BY = "create_by"; + + public static final String COL_CREATE_TIME = "create_time"; + + public static final String COL_UPDATE_BY = "update_by"; + + public static final String COL_UPDATE_TIME = "update_time"; +} \ No newline at end of file diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsConfigurationVO.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsConfigurationVO.java new file mode 100644 index 0000000..cb92356 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsConfigurationVO.java @@ -0,0 +1,54 @@ +package com.njcn.product.cnzutai.zutai.pojo.vo; + + +import com.njcn.db.mybatisplus.bo.BaseEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * Description: + * Date: 2023/5/31 10:35【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data + +public class CsConfigurationVO extends BaseEntity { + /** + * id + */ + private String id; + + /** + * 组态项目名称 + */ + @ApiModelProperty(value = "组态项目名称") + private String name; + + private String fileContent; + + private Integer orderBy; + + private Integer active; + + + private List projectIds; + + + @ApiModelProperty(value = "操作人") + private String operater; + + @ApiModelProperty(value = "备注") + private String remark; + + /** + * 状态 0:删除 1:正常 + */ + @ApiModelProperty(value = "组态项目状态") + private String status; + + +} \ No newline at end of file diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsPageVO.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsPageVO.java new file mode 100644 index 0000000..d762b8c --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsPageVO.java @@ -0,0 +1,71 @@ +package com.njcn.product.cnzutai.zutai.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * + * Description: + * Date: 2023/5/31 14:31【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +public class CsPageVO { + /** + * id + */ + @ApiModelProperty(value="id") + private String id; + + /** + * 组态项目id + */ + @ApiModelProperty(value="组态项目id") + private String pid; + + @ApiModelProperty(value="前端使用") + @JsonProperty("kId") // 强制前端使用 kId + private String kId; + + @ApiModelProperty(value="组态项目名称") + private String configurationName; + + + /** + * 组态页面名称 + */ + @ApiModelProperty(value="组态页面名称") + private String name; + @ApiModelProperty(value = "操作人") + private String operater; + + /** + * 组态页面文件路径 + */ + @ApiModelProperty(value="组态页面文件路径") + private String path; + + private String createBy; + + @JsonFormat( + pattern = "yyyy-MM-dd HH:mm:ss" + ) + private LocalDateTime createTime; + + private String updateBy; + + @JsonFormat( + pattern = "yyyy-MM-dd HH:mm:ss" + ) + private LocalDateTime updateTime; + + +} \ No newline at end of file diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsRtDataVO.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsRtDataVO.java new file mode 100644 index 0000000..9398a16 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/CsRtDataVO.java @@ -0,0 +1,52 @@ +package com.njcn.product.cnzutai.zutai.pojo.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.time.Instant; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2023/6/25 13:31 + */ +@Data +public class CsRtDataVO { + + @ApiModelProperty("图元id") + private String id; + + @ApiModelProperty("最新数据时间") +// @JsonSerialize(using = InstantDateSerializer.class) + private Instant time; + + @ApiModelProperty("监测点id") + private String lineId; + + @ApiModelProperty("相别") + private String phaseType; + + @ApiModelProperty("数据类型") + private String valueType; + + @ApiModelProperty("实时数据值,3.1415926则显示暂无数据") + private Double value; + + @ApiModelProperty("指标显示名称") + private String statisticalName; + + @ApiModelProperty("指标单位") + private String unit; + + @ApiModelProperty("指标名称") + private String target; + + @ApiModelProperty("指标最大值") + private Double maxValue; + + @ApiModelProperty("指标最小值") + private Double minValue; + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/DataArrayTreeVO.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/DataArrayTreeVO.java new file mode 100644 index 0000000..b561420 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/DataArrayTreeVO.java @@ -0,0 +1,27 @@ +package com.njcn.product.cnzutai.zutai.pojo.vo; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2023/6/14 13:36 + */ +@Data +public class DataArrayTreeVO { + + private String id; + + private String name; + + private String showName; + + private List children = new ArrayList<>(); + + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/ElementsVO.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/ElementsVO.java new file mode 100644 index 0000000..028fdbb --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/ElementsVO.java @@ -0,0 +1,21 @@ +package com.njcn.product.cnzutai.zutai.pojo.vo; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2023/6/2 15:26 + */ +@Data +public class ElementsVO implements Serializable { + + private String id; + + private String json; + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/RealTimeDataVo.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/RealTimeDataVo.java new file mode 100644 index 0000000..10bf55f --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/pojo/vo/RealTimeDataVo.java @@ -0,0 +1,51 @@ +package com.njcn.product.cnzutai.zutai.pojo.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.time.Instant; + +/** + * @author xy + */ +@Data +public class RealTimeDataVo implements Serializable { + + @ApiModelProperty("数据时间") +// @JsonSerialize(using = InstantDateSerializer.class) + private Instant time; + + @ApiModelProperty("指标id") + private String id; + + @ApiModelProperty("指标名称") + private String name; + + @ApiModelProperty("指标别名") + private String otherName; + + @ApiModelProperty("相别") + private String phase; + + @ApiModelProperty("单位") + private String unit; + + @ApiModelProperty("排序") + private Integer sort; + + @ApiModelProperty("平均值") + private Double avgValue; + + @ApiModelProperty("A相值") + private Double valueA; + + @ApiModelProperty("B相值") + private Double valueB; + + @ApiModelProperty("C相值") + private Double valueC; + + @ApiModelProperty("无相值") + private Double valueM; +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/CsConfigurationService.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/CsConfigurationService.java new file mode 100644 index 0000000..f9077e9 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/CsConfigurationService.java @@ -0,0 +1,33 @@ +package com.njcn.product.cnzutai.zutai.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; + +import com.njcn.product.cnzutai.zutai.pojo.param.CsConfigurationParm; +import com.njcn.product.cnzutai.zutai.pojo.po.CsConfigurationPO; +import com.njcn.product.cnzutai.zutai.pojo.vo.CsConfigurationVO; +import org.springframework.web.multipart.MultipartFile; + +/** + * + * Description: + * Date: 2023/5/31 10:53【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CsConfigurationService extends IService{ + + + boolean add(CsConfigurationParm csConfigurationParm); + + boolean audit(CsConfigurationParm.CsConfigurationAuditParam auditParm); + + boolean active(String id); + + CsConfigurationPO getActive(); + + IPage queryPage(CsConfigurationParm.CsConfigurationQueryParam csConfigurationQueryParam); + + String uploadImage(MultipartFile issuesFile); +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/CsPagePOService.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/CsPagePOService.java new file mode 100644 index 0000000..c062cc4 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/CsPagePOService.java @@ -0,0 +1,34 @@ +package com.njcn.product.cnzutai.zutai.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.cnzutai.zutai.pojo.param.CsPageParm; +import com.njcn.product.cnzutai.zutai.pojo.po.CsPagePO; +import com.njcn.product.cnzutai.zutai.pojo.vo.CsPageVO; + + +/** + * + * Description: + * Date: 2023/5/31 14:31【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface CsPagePOService extends IService{ + + + boolean add(CsPageParm csPageParm); + + boolean audit(CsPageParm.CsPageParmAuditParam auditParm); + + IPage queryPage(CsPageParm.CsPageParmQueryParam csPageParam); + + /** + * 根据id获取组态页面数据 + * @param id + * @return + */ + CsPagePO queryById(String id); + + } diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/IElementService.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/IElementService.java new file mode 100644 index 0000000..c08ceef --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/IElementService.java @@ -0,0 +1,36 @@ +package com.njcn.product.cnzutai.zutai.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.cnzutai.zutai.pojo.param.ElementParam; +import com.njcn.product.cnzutai.zutai.pojo.po.CsElement; + + +import java.util.List; + +/** + *

+ * 组态图元库 服务类 + *

+ * + * @author xuyang + * @since 2023-06-14 + */ +public interface IElementService extends IService { + + /** + * 新增组态图元 + * @param param 图元参数 + */ + CsElement addElement(ElementParam param); + + /** + * 组态图元数据查询 + */ + List findElement(); + + /** + * 组态图元数据查询 + */ + void deleteById(String id); + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/ILineTargetService.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/ILineTargetService.java new file mode 100644 index 0000000..e0e1c8e --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/ILineTargetService.java @@ -0,0 +1,28 @@ +package com.njcn.product.cnzutai.zutai.service; + + + +import com.njcn.product.cnzutai.zutai.pojo.vo.CsRtDataVO; +import com.njcn.product.cnzutai.zutai.pojo.vo.DataArrayTreeVO; + +import java.util.List; + +/** + * @author xuyang + */ +public interface ILineTargetService { + + /** + * 根据监测点Id获取对应指标 + * @param lineId + * @return + */ + List getLineTarget(String lineId); + + /** + * 获取绑定指标的数据 + * @param id + */ + List getLineData(String id); + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsConfigurationServiceImpl.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsConfigurationServiceImpl.java new file mode 100644 index 0000000..205e5ee --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsConfigurationServiceImpl.java @@ -0,0 +1,225 @@ +package com.njcn.product.cnzutai.zutai.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.oss.constant.OssPath; +import com.njcn.oss.utils.FileStorageUtil; +import com.njcn.product.cnzutai.zutai.mapper.CsPagePOMapper; +import com.njcn.product.cnzutai.zutai.pojo.po.CsPagePO; +import com.njcn.product.cnzutai.zutai.service.CsConfigurationService; +import com.njcn.product.cnzutai.zutai.mapper.CsConfigurationMapper; +import com.njcn.product.cnzutai.zutai.pojo.param.CsConfigurationParm; +import com.njcn.product.cnzutai.zutai.pojo.po.CsConfigurationPO; +import com.njcn.product.cnzutai.zutai.pojo.vo.CsConfigurationVO; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +/** + * + * Description: + * Date: 2023/5/31 10:53【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +@RequiredArgsConstructor +public class CsConfigurationServiceImpl extends ServiceImpl implements CsConfigurationService { + + private final FileStorageUtil fileStorageUtil; + private final CsPagePOMapper csPagePOMapper; + + //private final UserFeignClient userFeignClient; + + //private final RoleEngineerDevFeignClient roleEngineerDevFeignClient; + + @Override + @Transactional(rollbackFor = {Exception.class}) + public boolean add(CsConfigurationParm csConfigurationParm) { + CsConfigurationPO csConfigurationPO = new CsConfigurationPO(); + + BeanUtils.copyProperties(csConfigurationParm,csConfigurationPO); + + List projectIds = csConfigurationParm.getProjectIds(); + if(CollectionUtils.isEmpty(projectIds)){ + throw new BusinessException("请选择项目"); + } + String projects = String.join(",", projectIds); + + csConfigurationPO.setProjectIds(projects); + //排序不填给个100往后排 + csConfigurationPO.setOrderBy(Objects.isNull(csConfigurationParm.getOrderBy())?100:csConfigurationParm.getOrderBy()); + csConfigurationPO.setImagePath(csConfigurationParm.getFileContent()); + csConfigurationPO.setActive(0); + csConfigurationPO.setStatus("1"); + boolean save = this.save(csConfigurationPO); + + String name = csConfigurationPO.getName(); + Integer count = this.lambdaQuery().eq(CsConfigurationPO::getName, name).eq(CsConfigurationPO::getStatus, "1").count(); + if(count>1){ + throw new BusinessException("存在相同的组态项目名称"); + } + return save; + } + + @Override + public boolean audit(CsConfigurationParm.CsConfigurationAuditParam auditParm) { + CsConfigurationPO tem = this.getById(auditParm.getId()); + if(Objects.isNull(tem)){ + throw new BusinessException(CommonResponseEnum.FAIL,"未查询到项目信息,无法操作!"); + } + + + CsConfigurationPO csConfigurationPO = new CsConfigurationPO(); + if(Objects.equals(auditParm.getStatus(),"0")){ + csConfigurationPO.setId(auditParm.getId()); + csConfigurationPO.setStatus("0"); + boolean b = this.updateById(csConfigurationPO); + + if(StrUtil.isNotBlank(tem.getImagePath())){ + fileStorageUtil.deleteFile(tem.getImagePath()); + } + + CsPagePO csPagePO = new CsPagePO(); + csPagePO.setStatus("0"); + csPagePOMapper.update(csPagePO,new LambdaUpdateWrapper().eq(CsPagePO::getPid,csConfigurationPO.getId())); + return b; + } + + BeanUtils.copyProperties(auditParm,csConfigurationPO); + List projectIds = auditParm.getProjectIds(); + if(!CollectionUtils.isEmpty(projectIds)){ + String projects = String.join(",", projectIds); + csConfigurationPO.setProjectIds(projects); + + } + if(!Objects.isNull(auditParm.getOrderBy())){ + csConfigurationPO.setOrderBy(auditParm.getOrderBy()==0?100:auditParm.getOrderBy()); + } + + if(!Objects.isNull(auditParm.getFileContent())){ + if(StrUtil.isNotBlank(tem.getImagePath())){ + fileStorageUtil.deleteFile(tem.getImagePath()); + } + + String s = fileStorageUtil.uploadStream(writeJsonStringToInputStream(auditParm.getFileContent()), OssPath.CONFIGURATIONPATH, OssPath.CONFIGURATIONNAME); + csConfigurationPO.setImagePath(s); + } + + boolean b = this.updateById(csConfigurationPO); + + return b; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean active(String id) { + CsConfigurationPO csConfigurationPO = this.lambdaQuery().eq(CsConfigurationPO::getId,id).one(); + if(Objects.isNull(csConfigurationPO)){ + throw new BusinessException("未查询到项目组态图"); + } + this.lambdaUpdate().set(CsConfigurationPO::getActive,0).update(); + this.lambdaUpdate().set(CsConfigurationPO::getActive,1).eq(CsConfigurationPO::getId, id).update(); + return true; + } + + @Override + public CsConfigurationPO getActive() { + CsConfigurationPO csConfigurationPO = this.lambdaQuery().eq(CsConfigurationPO::getActive,1).one(); + return csConfigurationPO; + } + + + @Override + public IPage queryPage(CsConfigurationParm.CsConfigurationQueryParam csConfigurationQueryParam) { + Page returnpage = new Page<> (csConfigurationQueryParam.getPageNum(), csConfigurationQueryParam.getPageSize ( )); + Page temppage = new Page<> (csConfigurationQueryParam.getPageNum(), csConfigurationQueryParam.getPageSize ( )); + + List data1 = new ArrayList<>(); //roleEngineerDevFeignClient.getRoleProject().getData(); + /* if(CollectionUtils.isEmpty(data1)){ + return returnpage; + }*/ + //+无线项目id + //data1.add(DataParam.WIRELESS_PROJECT_ID); + Page csConfigurationPOPage = this.getBaseMapper().queryPage(temppage,csConfigurationQueryParam); + + List collect1 = csConfigurationPOPage.getRecords().stream().map(CsConfigurationPO::getCreateBy).collect(Collectors.toList()); + Map collect2 = new HashMap<>(); + /* if(!CollectionUtils.isEmpty(collect1)){ + List data = userFeignClient.appuserByIdList(collect1).getData(); + collect2 = data.stream().collect(Collectors.toMap(User::getId, User::getName, (e1, e2) -> e1 + "," + e2)); + + } else { + collect2 = new HashMap<>(); + }*/ + + List collect = csConfigurationPOPage.getRecords().stream().map(page -> { + CsConfigurationVO csDevModelPageVO = new CsConfigurationVO(); + BeanUtils.copyProperties(page, csDevModelPageVO); + + if(StringUtils.isEmpty(page.getProjectIds())){ + csDevModelPageVO.setProjectIds(new ArrayList<>()); + }else { + csDevModelPageVO.setProjectIds( Arrays.asList(page.getProjectIds().split(","))); + + } + if(Objects.isNull(page.getImagePath())){ + csDevModelPageVO.setFileContent(null); + + }else { + + try { + InputStream fileStream = fileStorageUtil.getFileStream(page.getImagePath()); + String text = new BufferedReader( + new InputStreamReader(fileStream, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + csDevModelPageVO.setFileContent(text); + }catch (Exception e){ + e.printStackTrace(); + } + + } + + + csDevModelPageVO.setOperater(collect2.get(csDevModelPageVO.getCreateBy())); + return csDevModelPageVO; + }).collect(Collectors.toList()); + returnpage.setRecords(collect); + returnpage.setTotal(csConfigurationPOPage.getTotal()); + + + + return returnpage; + } + + @Override + public String uploadImage(MultipartFile issuesFile) { + return fileStorageUtil.getFileUrl( fileStorageUtil.uploadMultipart(issuesFile, OssPath.CONFIGURATIONPATH)); + } + + /*将strin写入Json文件,返回一个InputStream*/ + public InputStream writeJsonStringToInputStream(String jsonString) { + // 转换为输入流 + ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)); + return inputStream; + } +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsElementServiceImpl.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsElementServiceImpl.java new file mode 100644 index 0000000..9734422 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsElementServiceImpl.java @@ -0,0 +1,65 @@ +package com.njcn.product.cnzutai.zutai.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.common.pojo.exception.BusinessException; + +import com.njcn.oss.constant.OssPath; +import com.njcn.oss.utils.FileStorageUtil; +import com.njcn.product.cnzutai.zutai.pojo.param.ElementParam; +import com.njcn.product.cnzutai.zutai.mapper.CsElementMapper; +import com.njcn.product.cnzutai.zutai.pojo.enums.CsSystemResponseEnum; +import com.njcn.product.cnzutai.zutai.pojo.po.CsElement; +import com.njcn.product.cnzutai.zutai.service.IElementService; +import lombok.AllArgsConstructor; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; + +/** + *

+ * 组态图元库 服务实现类 + *

+ * + * @author xuyang + * @since 2023-07-12 + */ +@Service +@AllArgsConstructor +public class CsElementServiceImpl extends ServiceImpl implements IElementService { + + private final FileStorageUtil fileStorageUtil; + + @Override + public CsElement addElement(ElementParam param) { + CsElement po = this.lambdaQuery().eq(CsElement::getStatus,1) + .eq(CsElement::getElementCode,param.getElementCode()) + .eq(CsElement::getElementName,param.getElementName()) + .eq(CsElement::getElementMark,param.getElementMark()).one(); + if (!Objects.isNull(po)){ + throw new BusinessException(CsSystemResponseEnum.SAME_DATA_ERROR); + } + CsElement csElement = new CsElement(); + BeanUtils.copyProperties(param,csElement); + String path = fileStorageUtil.uploadMultipart(param.getMultipartFile(), OssPath.ELEMENT); + csElement.setPath(path); + csElement.setStatus(1); + this.saveOrUpdate(csElement); + csElement.setPath(path); + return csElement; + } + + @Override + public List findElement() { + List list = this.lambdaQuery().eq(CsElement::getStatus,1).list(); + return list; + } + + @Override + public void deleteById(String id) { + CsElement csElement = this.lambdaQuery().eq(CsElement::getId,id).one(); + csElement.setStatus(0); + this.updateById(csElement); + } +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsPagePOServiceImpl.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsPagePOServiceImpl.java new file mode 100644 index 0000000..1583086 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/CsPagePOServiceImpl.java @@ -0,0 +1,151 @@ +package com.njcn.product.cnzutai.zutai.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import com.njcn.oss.constant.OssPath; +import com.njcn.oss.utils.FileStorageUtil; +import com.njcn.product.cnzutai.zutai.service.CsPagePOService; +import com.njcn.product.cnzutai.zutai.mapper.CsConfigurationMapper; +import com.njcn.product.cnzutai.zutai.mapper.CsPagePOMapper; +import com.njcn.product.cnzutai.zutai.pojo.param.CsPageParm; +import com.njcn.product.cnzutai.zutai.pojo.po.CsConfigurationPO; +import com.njcn.product.cnzutai.zutai.pojo.po.CsPagePO; +import com.njcn.product.cnzutai.zutai.pojo.vo.CsPageVO; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +/** + * + * Description: + * Date: 2023/5/31 14:31【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +@RequiredArgsConstructor +public class CsPagePOServiceImpl extends ServiceImpl implements CsPagePOService { + private final FileStorageUtil fileStorageUtil; + private final CsConfigurationMapper csConfigurationMapper; + // private final UserFeignClient userFeignClient; + + @Override + @Transactional(rollbackFor = {Exception.class}) + public boolean add(CsPageParm csPageParm) { + QueryWrapper csPagePOQueryWrapper = new QueryWrapper<>(); + String pid = csPageParm.getPid(); + csPagePOQueryWrapper.eq("pid",pid); + List csPagePOList = this.baseMapper.selectList(csPagePOQueryWrapper); + if(CollUtil.isNotEmpty(csPagePOList)){ + //先删除旧的json文件 + for(CsPagePO csPagePO : csPagePOList){ + if(StrUtil.isNotBlank(csPagePO.getPath())){ + fileStorageUtil.deleteFile(csPagePO.getPath()); + } + } + } + this.getBaseMapper().delete(csPagePOQueryWrapper); + InputStream inputStream = null; + MultipartFile multipartFile = csPageParm.getMultipartFile(); + try { + inputStream = multipartFile.getInputStream(); // 获取文件的输入流 + String text = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + List csPagePOS = JSONUtil.toList(text, CsPagePO.class); + csPagePOS.forEach(temp->{ + String s = fileStorageUtil.uploadStream(writeJsonStringToInputStream(temp.getPath()), OssPath.CONFIGURATIONPATH, OssPath.CONFIGURATIONNAME); + temp.setPid(pid); + temp.setStatus("1"); + temp.setPath(s); + this.save(temp); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + finally { + try { + inputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return true; + } + + @Override + @Transactional(rollbackFor = {Exception.class}) + public boolean audit(CsPageParm.CsPageParmAuditParam auditParm) { +// CsPagePO csPagePO = new CsPagePO(); +// CsPagePO byId = this.getById(auditParm.getId()); +// fileStorageUtil.deleteFile(byId.getPath()); +// BeanUtils.copyProperties(auditParm, csPagePO); +// String s = fileStorageUtil.uploadMultipart(auditParm.getMultipartFile(), HarmonicConstant.CONFIGURATIONPATH); +// +// csPagePO.setPath(s); +// +// return this.updateById(csPagePO); + return true; + } + + @Override + public IPage queryPage(CsPageParm.CsPageParmQueryParam csPageParam) { + Page returnpage = new Page<> (csPageParam.getPageNum(), csPageParam.getPageSize ( )); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(StrUtil.isNotBlank (csPageParam.getPid()),CsPagePO.COL_PID,csPageParam.getPid()). + like(StrUtil.isNotBlank (csPageParam.getSearchValue()),CsPagePO.COL_NAME,csPageParam.getSearchValue()). + eq ("status",1); + // orderByAsc(CsPagePO.COL_KID) + + IPage pageData = this.page(new Page<>(csPageParam.getPageNum(), csPageParam.getPageSize()), queryWrapper); + + List collect = pageData.getRecords().stream().map(temp -> { + CsPageVO csPageVO = new CsPageVO(); + CsConfigurationPO csConfigurationPO = csConfigurationMapper.selectById(temp.getPid()); + BeanUtils.copyProperties(temp, csPageVO); + csPageVO.setConfigurationName(csConfigurationPO.getName()); + InputStream fileStream = fileStorageUtil.getFileStream(temp.getPath()); + String text = new BufferedReader( + new InputStreamReader(fileStream, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + csPageVO.setPath(text); +// csPageVO.setOperater(collect2.get(csPageVO.getCreateBy())); + return csPageVO; + }).collect(Collectors.toList()); + returnpage.setRecords(collect); + returnpage.setTotal(pageData.getTotal()); + + return returnpage; + } + + @Override + public CsPagePO queryById(String id) { + return this.lambdaQuery().eq(CsPagePO::getId,id).one(); + } + + + /*将strin写入Json文件,返回一个InputStream*/ + public InputStream writeJsonStringToInputStream(String jsonString) { + // 转换为输入流 + ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)); + return inputStream; + } + + +} diff --git a/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/LineTargetServiceImpl.java b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/LineTargetServiceImpl.java new file mode 100644 index 0000000..87c3911 --- /dev/null +++ b/cn-zutai/src/main/java/com/njcn/product/cnzutai/zutai/service/impl/LineTargetServiceImpl.java @@ -0,0 +1,471 @@ +package com.njcn.product.cnzutai.zutai.service.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.njcn.common.pojo.exception.BusinessException; + +import com.njcn.influx.pojo.dto.StatisticalDataDTO; +import com.njcn.influx.service.CommonService; +import com.njcn.oss.utils.FileStorageUtil; +import com.njcn.product.cnzutai.zutai.pojo.dto.ZuTaiDTO; +import com.njcn.product.cnzutai.zutai.pojo.enums.CsSystemResponseEnum; +import com.njcn.product.cnzutai.zutai.pojo.vo.CsRtDataVO; +import com.njcn.product.cnzutai.zutai.pojo.vo.DataArrayTreeVO; +import com.njcn.product.cnzutai.zutai.service.CsPagePOService; +import com.njcn.product.cnzutai.zutai.service.ILineTargetService; + +import lombok.AllArgsConstructor; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 类的介绍: + * + * @author xuyang + * @version 1.0.0 + * @createTime 2023/6/1 10:11 + */ +@Service +@AllArgsConstructor +public class LineTargetServiceImpl implements ILineTargetService { + + + + private final FileStorageUtil fileStorageUtil; + + private final CsPagePOService csPagePOService; + + private final CommonService commonService; + + + @Override + public List getLineTarget(String lineId) { +// List setList = new ArrayList<>(); +// String devId = csLedgerFeignClient.findDevByLineId(lineId).getData(); +// //1.获取监测点的安装位置 +// List lineList = csLineFeignClient.queryLineById(Collections.singletonList(lineId)).getData(); +// if (CollectionUtils.isEmpty(lineList)){ +// return new ArrayList(); +// } +// CsLinePO line = csLineFeignClient.queryLineById(Collections.singletonList(lineId)).getData().get(0); +// String code = dicDataFeignClient.getDicDataById(line.getPosition()).getData().getCode(); +// String modelId = null; +// List dataSetList = new ArrayList<>(); +// //治理监测点 +// if (Objects.equals(code, DicDataEnum.OUTPUT_SIDE.getCode())){ +// modelId = devModelRelationFeignClient.getModelByType(devId,0).getData(); +// dataSetList = dataSetFeignClient.getDataSet(modelId,0).getData(); +// } +// //电网侧监测点 +// else if (Objects.equals(code, DicDataEnum.GRID_SIDE.getCode())){ +// modelId = devModelRelationFeignClient.getModelByType(devId,1).getData(); +// dataSetList = dataSetFeignClient.getDataSet(modelId,1).getData(); +// } +// //负载侧监测点 +// else if (Objects.equals(code, DicDataEnum.LOAD_SIDE.getCode())){ +// modelId = devModelRelationFeignClient.getModelByType(devId,1).getData(); +// dataSetList = dataSetFeignClient.getDataSet(modelId,2).getData(); +// } +// setList = dataSetList.stream().map(LineTargetVO::getId).collect(Collectors.toList()); +// return dataArrayFeignClient.getDataArray(setList).getData(); + return null; + } + + @Override + public List getLineData(String id) { + List result = new ArrayList<>(); + String path = csPagePOService.queryById(id).getPath(); + InputStream inputStream = fileStorageUtil.getFileStream(path); + ZuTaiDTO zuTaiDto = analysisJson(inputStream); + + zuTaiDto.getJson().forEach(item->{ + if (!CollectionUtils.isEmpty(item.getUidNames())){ + for (int i = 0; i < item.getUidNames().size(); i++) { + String temp = item.getUidNames().get(i); + String targetTag = null; + String phasic = null; + String dataType = null; +// String[] tmepUidName = temp.split(" / "); +// if(tmepUidName.length==2){ +// targetTag = tmepUidName[0]; +// phasic = "T"; +// dataType = tmepUidName[1]; +// }else if (tmepUidName.length==3){ +// targetTag = tmepUidName[0]; +// phasic = tmepUidName[1]; +// dataType = tmepUidName[2]; +// } + + if (CollectionUtils.isEmpty(item.getUid()) || StringUtils.isEmpty(item.getLineId())){ + throw new BusinessException(CsSystemResponseEnum.BIND_TARGET_ERROR); + } + List tempUid = item.getUid().get(i); + String s = tempUid.get(tempUid.size() - 1); + String[] tempTable = s.replace("$", "").split("#"); + result.add(getLineRtData(item.getId(),item.getLineId(),tempTable[3],tempTable[0],tempTable[1],tempTable[2].toUpperCase(),temp,item.getUnit().get(i))); + } + + } + + + }); + return result; + } + + /** + * 解析json文件 + */ + public ZuTaiDTO analysisJson(InputStream inputStream) { + + ObjectMapper mapper = new ObjectMapper(); + + String text = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + + + ZuTaiDTO config = null; + try { + config = mapper.readValue(text, ZuTaiDTO.class); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return config; + } + + /** + * 通过orm框架获取数据 + * @param id 图元Id + * @param lineId 监测点Id + * @param tableName 表名称 + * @param columnName 字段名称 + * @param phasic 相别 + * @param dataType 数据类型 + * @param target 数据名称 + * @return + */ + public CsRtDataVO getLineRtData(String id,String lineId, String tableName, String columnName, String phasic, String dataType, String target,String uint) { + CsRtDataVO csRtDataVO = new CsRtDataVO(); + StatisticalDataDTO statisticalDataDTO = commonService.getLineRtData(lineId,tableName,columnName,phasic,dataType); + if(Objects.isNull(statisticalDataDTO)){ + statisticalDataDTO = new StatisticalDataDTO(); + statisticalDataDTO.setLineId(lineId); + statisticalDataDTO.setPhaseType(phasic); + statisticalDataDTO.setValueType(dataType); + statisticalDataDTO.setValue(3.1415926); + } else { + statisticalDataDTO.setValue(BigDecimal.valueOf(statisticalDataDTO.getValue()).setScale(4, RoundingMode.HALF_UP).doubleValue()); + } + String targetTag = null; + String targetPhasic = null; + String targetDataType = null; + + String[] tmepUidName = target.split(" / "); + if(tmepUidName.length==2){ + targetTag = tmepUidName[0]; + targetDataType = getDataType(tmepUidName[1]) ; + }else if (tmepUidName.length==3){ + targetTag = tmepUidName[0]; + targetPhasic = tmepUidName[1]; + targetDataType =getDataType(tmepUidName[2]) ; + } else if (tmepUidName.length==4) { + targetTag = tmepUidName[1]; + targetPhasic = tmepUidName[2]; + targetDataType =getDataType(tmepUidName[3]) ; + } + statisticalDataDTO.setStatisticalName((Objects.isNull(targetPhasic)?"":targetPhasic+"相_")+targetTag+"_"+targetDataType); + statisticalDataDTO.setTarget(columnName + "$" + phasic + "$" + dataType); + BeanUtils.copyProperties(statisticalDataDTO,csRtDataVO); + csRtDataVO.setId(id); + csRtDataVO.setUnit(uint); + return csRtDataVO; + } + + public String getDataType(String statItem){ + String valueTypeName = ""; + switch (statItem) { + case "max": + valueTypeName = "最大值"; + break; + case "min": + valueTypeName = "最小值"; + break; + case "avg": + valueTypeName = "平均值"; + break; + case "cp95": + valueTypeName = "cp95值"; + break; + default: + break; + } + return valueTypeName; + } + public static void main(String[] args) { + ObjectMapper mapper = new ObjectMapper(); + String temp ="{\n" + + " \"canvasCfg\": {\n" + + " \"width\": 1920,\n" + + " \"height\": 1080,\n" + + " \"scale\": 1,\n" + + " \"color\": \"\",\n" + + " \"img\": \"\",\n" + + " \"guide\": true,\n" + + " \"adsorp\": true,\n" + + " \"adsorp_diff\": 5,\n" + + " \"transform_origin\": {\n" + + " \"x\": 0,\n" + + " \"y\": 0\n" + + " },\n" + + " \"drag_offset\": {\n" + + " \"x\": 0,\n" + + " \"y\": 0\n" + + " }\n" + + " },\n" + + " \"gridCfg\": {\n" + + " \"enabled\": true,\n" + + " \"align\": true,\n" + + " \"size\": 10\n" + + " },\n" + + " \"json\": [\n" + + " {\n" + + " \"id\": \"c53cccb8c65201c192d8c57fbdb4d993-fGe6GgykpF\",\n" + + " \"title\": \"传输设备\",\n" + + " \"keyId\": \"\",\n" + + " \"type\": \"svg\",\n" + + " \"binfo\": {\n" + + " \"left\": 380,\n" + + " \"top\": 120,\n" + + " \"width\": 50,\n" + + " \"height\": 50,\n" + + " \"angle\": 0\n" + + " },\n" + + " \"resize\": true,\n" + + " \"rotate\": true,\n" + + " \"lock\": false,\n" + + " \"active\": false,\n" + + " \"hide\": false,\n" + + " \"props\": {\n" + + " \"fill\": \"#FF0000\"\n" + + " },\n" + + " \"tag\": \"c53cccb8c65201c192d8c57fbdb4d993\",\n" + + " \"common_animations\": {\n" + + " \"val\": \"\",\n" + + " \"delay\": \"delay-0s\",\n" + + " \"speed\": \"slow\",\n" + + " \"repeat\": \"infinite\"\n" + + " },\n" + + " \"use_proportional_scaling\": true,\n" + + " \"events\": [],\n" + + " \"lineId\": \"782d0aa0bfbe47b83f54f9b31b2d6c9a\",\n" + + " \"lineList\": [\n" + + " \"52e34eb68eeb13fa515737048772d204\",\n" + + " \"d02b5cc062e6a0e0bcb784c2df6b2e98\",\n" + + " \"158445294c3f9b4507b6b19df403e467\",\n" + + " \"782d0aa0bfbe47b83f54f9b31b2d6c9a\"\n" + + " ],\n" + + " \"lineName\": \"无锡供电公司 / 110kV下甸桥变 / PQ-COM_7 / 10kV母线_胜境11C线\",\n" + + " \"UID\": [\n" + + " [\n" + + " \"rms\",\n" + + " \"B\",\n" + + " \"$rms#B#max#data_v#NO$\"\n" + + " ],\n" + + " [\n" + + " \"rms\",\n" + + " \"B\",\n" + + " \"$rms#B#min#data_v#NO$\"\n" + + " ],\n" + + " [\n" + + " \"rms\",\n" + + " \"B\",\n" + + " \"$rms#B#avg#data_v#NO$\"\n" + + " ],\n" + + " [\n" + + " \"rms\",\n" + + " \"C\",\n" + + " \"$rms#C#max#data_v#NO$\"\n" + + " ],\n" + + " [\n" + + " \"rms\",\n" + + " \"C\",\n" + + " \"$rms#C#min#data_v#NO$\"\n" + + " ],\n" + + " [\n" + + " \"rms\",\n" + + " \"C\",\n" + + " \"$rms#C#avg#data_v#NO$\"\n" + + " ],\n" + + " [\n" + + " \"rms\",\n" + + " \"C\",\n" + + " \"$rms#C#cp95#data_v#NO$\"\n" + + " ]\n" + + " ],\n" + + " \"UIDName\": [\n" + + " \"相电压总有效值 / B / max\",\n" + + " \"相电压总有效值 / B / min\",\n" + + " \"相电压总有效值 / B / avg\",\n" + + " \"相电压总有效值 / C / max\",\n" + + " \"相电压总有效值 / C / min\",\n" + + " \"相电压总有效值 / C / avg\",\n" + + " \"相电压总有效值 / C / cp95\"\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"id\": \"d09fd211e9908c5fea5cc38c9447e268-mVLhsvkxPK\",\n" + + " \"title\": \"断路器\",\n" + + " \"keyId\": \"\",\n" + + " \"type\": \"svg\",\n" + + " \"binfo\": {\n" + + " \"left\": 610,\n" + + " \"top\": 270,\n" + + " \"width\": 50,\n" + + " \"height\": 50,\n" + + " \"angle\": 0\n" + + " },\n" + + " \"resize\": true,\n" + + " \"rotate\": true,\n" + + " \"lock\": false,\n" + + " \"active\": false,\n" + + " \"hide\": false,\n" + + " \"props\": {\n" + + " \"fill\": \"#FF0000\"\n" + + " },\n" + + " \"tag\": \"d09fd211e9908c5fea5cc38c9447e268\",\n" + + " \"common_animations\": {\n" + + " \"val\": \"\",\n" + + " \"delay\": \"delay-0s\",\n" + + " \"speed\": \"slow\",\n" + + " \"repeat\": \"infinite\"\n" + + " },\n" + + " \"use_proportional_scaling\": true,\n" + + " \"events\": [],\n" + + " \"lineId\": \"14a81218e58fa62465d232ff22eba30e\",\n" + + " \"lineList\": [\n" + + " \"52e34eb68eeb13fa515737048772d204\",\n" + + " \"d02b5cc062e6a0e0bcb784c2df6b2e98\",\n" + + " \"591f9f6ad50db9e2dfb3f467682f7789\",\n" + + " \"14a81218e58fa62465d232ff22eba30e\"\n" + + " ],\n" + + " \"lineName\": \"无锡供电公司 / 110kV下甸桥变 / PQCOM_3 / 10kV母线_东方12E线\",\n" + + " \"UID\": [\n" + + " [\n" + + " \"p\",\n" + + " \"p_2\",\n" + + " \"A\",\n" + + " \"$p_2#A#max#data_harmpower_p#NO$\"\n" + + " ],\n" + + " [\n" + + " \"p\",\n" + + " \"p_2\",\n" + + " \"A\",\n" + + " \"$p_2#A#min#data_harmpower_p#NO$\"\n" + + " ]\n" + + " \n" + + " ],\n" + + " \"UIDName\": [\n" + + " \"有功功率 / 2次有功功率 / A / max\",\n" + + " \"有功功率 / 2次有功功率 / A / min\"\n" + + " \n" + + " ]\n" + + " },\n" + + " {\n" + + " \"id\": \"ea37cb9e81e7dfd44babc986c3547a04-oJTFPUPGC3\",\n" + + " \"title\": \"电灯\",\n" + + " \"keyId\": \"\",\n" + + " \"type\": \"svg\",\n" + + " \"binfo\": {\n" + + " \"left\": 270,\n" + + " \"top\": 460,\n" + + " \"width\": 50,\n" + + " \"height\": 50,\n" + + " \"angle\": 0\n" + + " },\n" + + " \"resize\": true,\n" + + " \"rotate\": true,\n" + + " \"lock\": false,\n" + + " \"active\": false,\n" + + " \"hide\": false,\n" + + " \"props\": {\n" + + " \"fill\": \"#FF0000\"\n" + + " },\n" + + " \"tag\": \"ea37cb9e81e7dfd44babc986c3547a04\",\n" + + " \"common_animations\": {\n" + + " \"val\": \"\",\n" + + " \"delay\": \"delay-0s\",\n" + + " \"speed\": \"slow\",\n" + + " \"repeat\": \"infinite\"\n" + + " },\n" + + " \"use_proportional_scaling\": true,\n" + + " \"events\": [],\n" + + " \"lineId\": \"14a81218e58fa62465d232ff22eba30e\",\n" + + " \"lineList\": [\n" + + " \"52e34eb68eeb13fa515737048772d204\",\n" + + " \"d02b5cc062e6a0e0bcb784c2df6b2e98\",\n" + + " \"591f9f6ad50db9e2dfb3f467682f7789\",\n" + + " \"14a81218e58fa62465d232ff22eba30e\"\n" + + " ],\n" + + " \"lineName\": \"无锡供电公司 / 110kV下甸桥变 / PQCOM_3 / 10kV母线_东方12E线\",\n" + + " \"UID\": [\n" + + " [\n" + + " \"v_neg\",\n" + + " \"$v_neg#max#data_v#NO$\"\n" + + " ],\n" + + " [\n" + + " \"v_neg\",\n" + + " \"$v_neg#min#data_v#NO$\"\n" + + " ],\n" + + " [\n" + + " \"v_neg\",\n" + + " \"$v_neg#cp95#data_v#NO$\"\n" + + " ],\n" + + " [\n" + + " \"v_neg\",\n" + + " \"$v_neg#avg#data_v#NO$\"\n" + + " ]\n" + + " ],\n" + + " \"UIDName\": [\n" + + " \"电压负序分量 / max\",\n" + + " \"电压负序分量 / min\",\n" + + " \"电压负序分量 / avg\",\n" + + " \"电压负序分量 / cp95\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + ZuTaiDTO config = null; + try { + config = mapper.readValue(temp, ZuTaiDTO.class); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + + } +} + + + diff --git a/event_smart/.gitignore b/event_smart/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/event_smart/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/event_smart/pom.xml b/event_smart/pom.xml new file mode 100644 index 0000000..f8c8686 --- /dev/null +++ b/event_smart/pom.xml @@ -0,0 +1,166 @@ + + + 4.0.0 + + com.njcn.product + CN_Product + 1.0.0 + + + event_smart + + + + + com.njcn + njcn-common + 0.0.1 + + + + com.njcn + common-redis + 1.0.0 + + + + + + org.springframework.boot + spring-boot-starter-websocket + 2.7.12 + + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 3.5.1 + + + + + com.njcn + spingboot2.3.12 + 2.3.12 + + + + com.njcn + mybatis-plus + 0.0.1 + + + + + com.oracle.database.jdbc + ojdbc8 + 21.6.0.0 + + + com.oracle.database.nls + orai18n + 21.1.0.0 + + + + + org.springframework.boot + spring-boot-starter-security + + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + + com.njcn + common-event + 1.0.0 + + + common-microservice + com.njcn + + + common-web + com.njcn + + + + + + com.google.guava + guava + 32.1.3-jre + + + + cn.afterturn + easypoi-spring-boot-starter + 4.4.0 + + + + + + + event_smart + + + org.springframework.boot + spring-boot-maven-plugin + + + package + + repackage + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + + src/main/resources + + **/* + + + + src/main/java + + **/*.xml + + + + + + + diff --git a/event_smart/src/main/java/com/njcn/product/event/EventSmartApplication.java b/event_smart/src/main/java/com/njcn/product/event/EventSmartApplication.java new file mode 100644 index 0000000..f6c6e67 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/EventSmartApplication.java @@ -0,0 +1,18 @@ +package com.njcn.product.event; + +import lombok.extern.slf4j.Slf4j; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@Slf4j +@SpringBootApplication(scanBasePackages = "com.njcn") +//@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = WavePicComponent.class)) +@MapperScan("com.njcn.**.mapper") +public class EventSmartApplication { + + public static void main(String[] args) { + SpringApplication.run(EventSmartApplication.class, args); + } + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/config/PqlineCache.java b/event_smart/src/main/java/com/njcn/product/event/devcie/config/PqlineCache.java new file mode 100644 index 0000000..fdc8aeb --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/config/PqlineCache.java @@ -0,0 +1,81 @@ +package com.njcn.product.event.devcie.config; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.njcn.product.event.devcie.mapper.PqLineMapper; +import com.njcn.product.event.devcie.pojo.po.PqLine; +import com.njcn.product.event.devcie.pojo.po.PqsDeptsline; +import com.njcn.product.event.devcie.service.PqsDeptslineService; +import com.njcn.product.event.transientes.pojo.po.PqsDepts; +import com.njcn.product.event.transientes.service.PqsDeptsService; +import com.njcn.redis.utils.RedisUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Description: + * Date: 2025/07/28 上午 9:32【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Component +@Slf4j +public class PqlineCache { + @Autowired + private PqLineMapper pqLineMapper; + @Autowired + private RedisUtil redisUtil; + @Autowired + private PqsDeptslineService pqsDeptslineService; + @Autowired + + private PqsDeptsService pqsDeptsService; + private final static String NAME_KEY = "LineCache:"; + @Value("${SYS_TYPE_ZT}") + private String sysTypeZt; + @PostConstruct + public void init() { + log.info("系统启动中。。。加载pqline"); + List pqLines = pqLineMapper.selectList(null); + redisUtil.saveByKey(NAME_KEY + StrUtil.DASHED+"pqLineList",pqLines); + List list = pqsDeptsService.lambdaQuery().eq(PqsDepts::getState, 1).list(); + for (PqsDepts pqsDepts : list) { + List deptAndChildren = pqsDeptsService.findDeptAndChildren(pqsDepts.getDeptsIndex()); + List deptslines = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getDeptsIndex, deptAndChildren).eq(PqsDeptsline::getSystype, sysTypeZt).list(); + List deptslineIds = deptslines.stream().map(PqsDeptsline::getLineIndex).collect(Collectors.toList()); + + List result = new ArrayList<>(); + if(CollUtil.isNotEmpty(deptslineIds)){ + if(deptslineIds.size()> 1000 ){ + List> listList = CollUtil.split(deptslineIds,1000); + for(List li : listList){ + List temList = pqLineMapper.getRunMonitorIds(li); + result.addAll(temList); + } + }else { + result= pqLineMapper.getRunMonitorIds(deptslineIds); + } + } + redisUtil.saveByKey(NAME_KEY + StrUtil.DASHED+pqsDepts.getDeptsIndex(),result); + } + + List deptsList = pqsDeptsService.lambdaQuery().eq(PqsDepts::getState,1).list(); + redisUtil.saveByKey(NAME_KEY + StrUtil.DASHED+"AllDept",deptsList); + } + + @PreDestroy + public void destroy() { + log.info("系统运行结束"); + redisUtil.deleteKeysByString(NAME_KEY); + } + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/job/LineCacheJob.java b/event_smart/src/main/java/com/njcn/product/event/devcie/job/LineCacheJob.java new file mode 100644 index 0000000..0504e6f --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/job/LineCacheJob.java @@ -0,0 +1,79 @@ +package com.njcn.product.event.devcie.job; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.njcn.product.event.devcie.mapper.PqDeviceMapper; +import com.njcn.product.event.devcie.mapper.PqLineMapper; +import com.njcn.product.event.devcie.pojo.po.PqLine; +import com.njcn.product.event.devcie.pojo.po.PqsDeptsline; +import com.njcn.product.event.devcie.service.PqsDeptslineService; +import com.njcn.product.event.transientes.pojo.po.PqsDepts; +import com.njcn.product.event.transientes.service.PqsDeptsService; +import com.njcn.redis.utils.RedisUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Description: + * Date: 2025/08/05 上午 10:17【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Component +@EnableScheduling +public class LineCacheJob { + @Autowired + private PqLineMapper pqLineMapper; + @Autowired + private RedisUtil redisUtil; + @Autowired + private PqsDeptslineService pqsDeptslineService; + @Autowired + private PqsDeptsService pqsDeptsService; + + @Autowired + private PqDeviceMapper pqDeviceMapper; + + private final static String NAME_KEY = "LineCache:"; + @Value("${SYS_TYPE_ZT}") + private String sysTypeZt; + @Scheduled(cron="0 0 1 * * ?" ) // 每3钟执行一次 + public void lineCache(){ + redisUtil.deleteKeysByString(NAME_KEY); + + List pqLines = pqLineMapper.selectList(null); + redisUtil.saveByKey(NAME_KEY + StrUtil.DASHED+"pqLineList",pqLines); + List list = pqsDeptsService.lambdaQuery().eq(PqsDepts::getState, 1).list(); + for (PqsDepts pqsDepts : list) { + List deptAndChildren = pqsDeptsService.findDeptAndChildren(pqsDepts.getDeptsIndex()); + List deptslines = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getDeptsIndex, deptAndChildren).eq(PqsDeptsline::getSystype, sysTypeZt).list(); + List deptslineIds = deptslines.stream().map(PqsDeptsline::getLineIndex).collect(Collectors.toList()); + + List result = new ArrayList<>(); + if(CollUtil.isNotEmpty(deptslineIds)){ + if(deptslineIds.size()> 1000 ){ + List> listList = CollUtil.split(deptslineIds,1000); + for(List li : listList){ + List temList = pqLineMapper.getRunMonitorIds(li); + result.addAll(temList); + } + }else { + result= pqLineMapper.getRunMonitorIds(deptslineIds); + } + } + redisUtil.saveByKey(NAME_KEY + StrUtil.DASHED+pqsDepts.getDeptsIndex(),result); + } + + List deptsList = pqsDeptsService.lambdaQuery().eq(PqsDepts::getState,1).list(); + redisUtil.saveByKey(NAME_KEY + StrUtil.DASHED+"AllDept",deptsList); + + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqDeviceDetailMapper.java b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqDeviceDetailMapper.java new file mode 100644 index 0000000..4cb6ec5 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqDeviceDetailMapper.java @@ -0,0 +1,13 @@ +package com.njcn.product.event.devcie.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.devcie.pojo.po.PqDeviceDetail; + +/** + * CN_Gather + * + * @author cdf + * @date 2025/8/12 + */ +public interface PqDeviceDetailMapper extends BaseMapper { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqDeviceMapper.java b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqDeviceMapper.java new file mode 100644 index 0000000..08352a7 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqDeviceMapper.java @@ -0,0 +1,29 @@ +package com.njcn.product.event.devcie.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.event.devcie.pojo.dto.DeviceDTO; +import com.njcn.product.event.devcie.pojo.dto.DeviceDeptDTO; +import com.njcn.product.event.devcie.pojo.po.PqDevice; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqDeviceMapper extends BaseMapper { + List queryListByIds(@Param("ids") List ids); + + Page selectDeviceDTOPage(Page pqsEventdetailPage, @Param("searchValue") String searchValue,@Param("devIndexs") List devIndexs); + + Page queryListByLineIds(Page pqsEventdetailPage, @Param("searchValue") String searchValue,@Param("lineIds") List lineIds); + + + List selectDeviceDept(); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqGdCompanyMapper.java b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqGdCompanyMapper.java new file mode 100644 index 0000000..83f81d8 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqGdCompanyMapper.java @@ -0,0 +1,16 @@ +package com.njcn.product.event.devcie.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.devcie.pojo.po.PqGdCompany; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:48【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqGdCompanyMapper extends BaseMapper { + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqLineMapper.java b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqLineMapper.java new file mode 100644 index 0000000..f285ab1 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqLineMapper.java @@ -0,0 +1,28 @@ +package com.njcn.product.event.devcie.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.devcie.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.event.devcie.pojo.po.PqLine; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:43【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqLineMapper extends BaseMapper { + + List getBaseLineInfo(@Param("ids")List ids); + + + List getBaseLedger(@Param("ids")List ids,@Param("searchValue")String searchValue); + + + List getRunMonitorIds(@Param("ids")List ids); + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqLinedetailMapper.java b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqLinedetailMapper.java new file mode 100644 index 0000000..c56bfcc --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqLinedetailMapper.java @@ -0,0 +1,9 @@ +package com.njcn.product.event.devcie.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.devcie.pojo.po.PqLinedetail; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PqLinedetailMapper extends BaseMapper { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqSubstationMapper.java b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqSubstationMapper.java new file mode 100644 index 0000000..c3ce358 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqSubstationMapper.java @@ -0,0 +1,20 @@ +package com.njcn.product.event.devcie.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.devcie.pojo.dto.SubstationDTO; +import com.njcn.product.event.devcie.pojo.po.PqSubstation; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:48【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqSubstationMapper extends BaseMapper { + List queryListByIds(@Param("ids")List ids); +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqsStationMapMapper.java b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqsStationMapMapper.java new file mode 100644 index 0000000..0adf451 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/PqsStationMapMapper.java @@ -0,0 +1,17 @@ +package com.njcn.product.event.devcie.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.devcie.pojo.po.PqsStationMap; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:43【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsStationMapMapper extends BaseMapper { + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqDeviceMapper.xml b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqDeviceMapper.xml new file mode 100644 index 0000000..f78e6f7 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqDeviceMapper.xml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + DEV_INDEX, GD_INDEX, SUB_INDEX, "NAME", "STATUS", DEVTYPE, LOGONTIME, UPDATETIME, + NODE_INDEX, PORTID, DEVFLAG, DEV_SERIES, DEV_KEY, IP, DEVMODEL, CALLFLAG, DATATYPE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqLineMapper.xml b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqLineMapper.xml new file mode 100644 index 0000000..6ecb2c3 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqLineMapper.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + LINE_INDEX, GD_INDEX, SUB_INDEX, SUBV_INDEX, DEV_INDEX, "NAME", PT1, PT2, CT1, CT2, + DEVCMP, DLCMP, JZCMP, XYCMP, SUBV_NO, "SCALE", SUBV_NAME + + + + + + + + + + + + diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqSubstationMapper.xml b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqSubstationMapper.xml new file mode 100644 index 0000000..f15fd61 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/mapper/mapping/PqSubstationMapper.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + SUB_INDEX, GD_INDEX, "NAME", "SCALE" + + + + \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/DeviceDTO.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/DeviceDTO.java new file mode 100644 index 0000000..f2f0b09 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/DeviceDTO.java @@ -0,0 +1,45 @@ +package com.njcn.product.event.devcie.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * Description: + * Date: 2025/06/27 下午 3:25【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class DeviceDTO { + private Integer devId; + private String devName; + private Integer stationId; + private String stationName; + private String gdName; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + private String devFlag; + private String ip; + private String manufacturerName; + + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate thisTimeCheck; + + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate nextTimeCheck; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime logonTime; + + private String deptName; + //通讯状态 + private Integer runFlag=0; + //装置通讯状态(0:中断;1:正常) + private Integer status; + private double onLineRate=0.00; + private double integrityRate = 0.00; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/DeviceDeptDTO.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/DeviceDeptDTO.java new file mode 100644 index 0000000..b158f55 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/DeviceDeptDTO.java @@ -0,0 +1,18 @@ +package com.njcn.product.event.devcie.pojo.dto; + +import lombok.Data; + +/** + * Description: + * Date: 2025/06/27 下午 3:25【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class DeviceDeptDTO { + private Integer devId; + private String deptId; + private String deptName; + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/LedgerBaseInfoDTO.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/LedgerBaseInfoDTO.java new file mode 100644 index 0000000..6a25a34 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/LedgerBaseInfoDTO.java @@ -0,0 +1,41 @@ +package com.njcn.product.event.devcie.pojo.dto; + +import lombok.Data; + +/** + * @Author: cdf + * @CreateTime: 2025-06-25 + * @Description: + */ +@Data +public class LedgerBaseInfoDTO { + private String gdName; + private String gdIndex; + + private Integer lineId; + + private String lineName; + + private Integer busBarId; + + private String busBarName; + + private String scale; + + private Integer devId; + + private String devName; + + private String objName; + + private Integer stationId; + + private String stationName; + //通讯状态 + private Integer runFlag=0; + + private Integer eventCount; + + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/PqsDeptDTO.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/PqsDeptDTO.java new file mode 100644 index 0000000..167e08a --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/PqsDeptDTO.java @@ -0,0 +1,70 @@ +package com.njcn.product.event.devcie.pojo.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * Description: + * Date: 2025/07/29 下午 3:15【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class PqsDeptDTO { + /** + * 部门表Guid + */ + private String deptsIndex; + + /** + * 部门名称 + */ + + private String deptsname; + + /** + * 排序 + */ + + private Integer deptsDesc; + + /** + * (关联表PQS_User)用户表Guid + */ + + private String userIndex; + + /** + * 更新时间 + */ + + private LocalDateTime updatetime; + + /** + * 部门描述 + */ + + private String deptsDescription; + + /** + * 角色状态0:删除;1:正常; + */ + + private Integer state; + + /** + * 行政区域 + */ + + private String area; + + private String areaName; + + + private Integer customDept; + + + private String parentnodeid; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/SubstationDTO.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/SubstationDTO.java new file mode 100644 index 0000000..6e9b9dc --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/dto/SubstationDTO.java @@ -0,0 +1,22 @@ +package com.njcn.product.event.devcie.pojo.dto; + +import lombok.Data; + +/** + * Description: + * Date: 2025/06/27 下午 3:37【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class SubstationDTO { + + private Integer stationId; + private String stationName; + private String gdName; + private double longitude; + private double latitude; + private Integer runFlag=0;; + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqDevice.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqDevice.java new file mode 100644 index 0000000..658964c --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqDevice.java @@ -0,0 +1,127 @@ +package com.njcn.product.event.devcie.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +/** + * 靠靠? + */ +@Data +@NoArgsConstructor +@TableName(value = "PQ_DEVICE") +public class PqDevice { + /** + * 靠靠 + */ + @TableId(value = "DEV_INDEX", type = IdType.INPUT) + private Integer devIndex; + + /** + * 靠靠靠 + */ + @TableField(value = "GD_INDEX") + private Integer gdIndex; + + /** + * 靠靠? + */ + @TableField(value = "SUB_INDEX") + private Integer subIndex; + + /** + * 靠靠 + */ + @TableField(value = "\"NAME\"") + private String name; + + /** + * 靠靠靠(0:靠;1:靠) + */ + @TableField(value = "\"STATUS\"") + private Integer status; + + /** + * (靠縋QS_Dicdata)靠靠Guid + */ + @TableField(value = "DEVTYPE") + private String devtype; + + /** + * 靠靠 + */ + @TableField(value = "LOGONTIME") + private LocalDateTime logontime; + + /** + * 靠靠靠 + */ + @TableField(value = "UPDATETIME") + private LocalDateTime updatetime; + + /** + * 靠縉odeInformation)靠靠靠,靠靠靠靠靠靠靠? + */ + @TableField(value = "NODE_INDEX") + private Integer nodeIndex; + + /** + * 靠ID,靠靠靠 + */ + @TableField(value = "PORTID") + private Long portid; + + /** + * 靠靠(0:靠;1:靠;2:靠) + */ + @TableField(value = "DEVFLAG") + private Integer devflag; + + /** + * 靠靠?靠3ds靠 + */ + @TableField(value = "DEV_SERIES") + private String devSeries; + + /** + * 靠靠,靠3ds靠 + */ + @TableField(value = "DEV_KEY") + private String devKey; + + /** + * IP靠 + */ + @TableField(value = "IP") + private String ip; + + /** + * 靠靠(0:靠靠;1:靠靠) + */ + @TableField(value = "DEVMODEL") + private Integer devmodel; + + /** + * 靠靠? + */ + @TableField(value = "CALLFLAG") + private Integer callflag; + + /** + * 靠靠(0:靠靠;1:靠靠;2:靠靠) + */ + @TableField(value = "DATATYPE") + private Integer datatype; +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqDeviceDetail.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqDeviceDetail.java new file mode 100644 index 0000000..031cc5e --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqDeviceDetail.java @@ -0,0 +1,70 @@ +package com.njcn.product.event.devcie.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDate; + +/** + * CN_Gather + * + * @author cdf + * @date 2025/8/12 + */ +@TableName(value = "PQ_DEVICEDETAIL") +@Data +public class PqDeviceDetail { + + + @TableId + @TableField(value = "DEV_INDEX") + private Long devIndex; + + @TableField(value = "Manufacturer") + private String manufacturer; + + @TableField(value = "CheckFlag") + private Long checkFlag; + + @TableField(value="ThisTimeCheck") + private LocalDate ThisTimeCheck; + + @TableField(value="NextTimeCheck") + private LocalDate NextTimeCheck; + + @TableField(value="DATAPLAN") + private Long dataplan; + + @TableField(value="NEWTRAFFIC") + private Long newtraffic; + + + @TableField(value = "electroplate") + private Integer electroplate = 0; + + @TableField(value = "ONTIME") + private Integer ontime; + @TableField(value = "contract") + private String contract; + + @TableField(value = "DEV_CATENA") + private String devCatnea; + + @TableField(value = "SIM") + private String sim; + + @TableField(value = "DEV_NO") + private String devNo; + + @TableField(value = "DEV_LOCATION") + private String devLocation; + + @TableField(value = "IS_ALARM") + private Integer isAlarm; + + + + + } diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqGdCompany.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqGdCompany.java new file mode 100644 index 0000000..8082e7d --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqGdCompany.java @@ -0,0 +1,27 @@ +package com.njcn.product.event.devcie.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * CN_Gather + * + * @author cdf + * @date 2025/8/9 + */ +@Data +@TableName(value = "PQ_GDINFORMATION") +public class PqGdCompany { + + @TableId + @TableField(value="GD_INDEX") + private Long gdIndex; + + @TableField(value="NAME") + private String name; + + @TableField(value="PROVINCE_INDEX") + private Long provinceIndex; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqLine.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqLine.java new file mode 100644 index 0000000..31f7a12 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqLine.java @@ -0,0 +1,132 @@ +package com.njcn.product.event.devcie.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:43【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +/** + * 靠靠? + */ +@Data +@NoArgsConstructor +@TableName(value = "PQ_LINE") +public class PqLine { + /** + * 靠靠 + */ + @TableId(value = "LINE_INDEX", type = IdType.INPUT) + private Integer lineIndex; + + /** + * 靠靠靠 + */ + @TableField(value = "GD_INDEX") + private Integer gdIndex; + + /** + * 靠靠? + */ + @TableField(value = "SUB_INDEX") + private Integer subIndex; + + /** + * 靠靠 + */ + @TableField(value = "SUBV_INDEX") + private Integer subvIndex; + + /** + * 靠靠 + */ + @TableField(value = "DEV_INDEX") + private Integer devIndex; + + /** + * 靠靠 + */ + @TableField(value = "\"NAME\"") + private String name; + + /** + * PT靠靠 + */ + @TableField(value = "PT1") + private Double pt1; + + /** + * PT靠靠 + */ + @TableField(value = "PT2") + private Double pt2; + + /** + * CT靠靠 + */ + @TableField(value = "CT1") + private Double ct1; + + /** + * CT靠靠 + */ + @TableField(value = "CT2") + private Double ct2; + + /** + * 靠靠 + */ + @TableField(value = "DEVCMP") + private Double devcmp; + + /** + * 靠靠 + */ + @TableField(value = "DLCMP") + private Double dlcmp; + + /** + * 靠靠 + */ + @TableField(value = "JZCMP") + private Double jzcmp; + + /** + * 靠靠 + */ + @TableField(value = "XYCMP") + private Double xycmp; + + /** + * 靠?靠靠靠靠靠靠? + */ + @TableField(value = "SUBV_NO") + private Integer subvNo; + + /** + * (靠PQS_Dictionary?靠靠Guid + */ + @TableField(value = "\"SCALE\"") + private String scale; + + /** + * 靠靠 + */ + @TableField(value = "SUBV_NAME") + private String subvName; + + @TableField(exist = false) + private String subName; + + @TableField(exist = false) + private String deptName; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqLinedetail.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqLinedetail.java new file mode 100644 index 0000000..c227c41 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqLinedetail.java @@ -0,0 +1,52 @@ +package com.njcn.product.event.devcie.pojo.po; + +/** + * @Author: cdf + * @CreateTime: 2025-06-26 + * @Description: + */ +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +@Data +@TableName("PQ_LINEDETAIL") +public class PqLinedetail { + + @TableId(value = "LINE_INDEX", type = IdType.INPUT) + private Integer lineIndex; + + private Integer gdIndex; + + private Integer subIndex; + + private String lineName; + + private Integer pttype; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date lastTime; + + private Integer tinterval; + + private String loadtype; + + private String businesstype; + + private String remark; + + private String monitorId; + + private Integer powerid; + + private String objname; + + @TableField(fill = FieldFill.INSERT) + private Integer statflag; + + private String lineGrade; + + private String powerSubstationName; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqSubstation.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqSubstation.java new file mode 100644 index 0000000..edc0c5e --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqSubstation.java @@ -0,0 +1,45 @@ +package com.njcn.product.event.devcie.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:48【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +/** + * 靠靠靠 + */ +@Data +@NoArgsConstructor +@TableName(value = "PQ_SUBSTATION") +public class PqSubstation { + /** + * 靠靠? + */ + @TableId(value = "SUB_INDEX", type = IdType.INPUT) + private Integer subIndex; + + /** + * 靠靠靠 + */ + @TableField(value = "GD_INDEX") + private Integer gdIndex; + + /** + * 靠靠? + */ + @TableField(value = "\"NAME\"") + private String name; + + @TableField(value = "\"SCALE\"") + private String scale; +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqsDeptsline.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqsDeptsline.java new file mode 100644 index 0000000..4bc3653 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqsDeptsline.java @@ -0,0 +1,31 @@ +package com.njcn.product.event.devcie.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/06/19 下午 3:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +@TableName(value = "PQS_DEPTSLINE") +public class PqsDeptsline { + /** + * 部门表Guid + */ + @TableField(value = "DEPTS_INDEX") + private String deptsIndex; + + @TableField(value = "LINE_INDEX") + private Integer lineIndex; + + @TableField(value = "SYSTYPE") + private String systype; +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqsStationMap.java b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqsStationMap.java new file mode 100644 index 0000000..b885cb8 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/pojo/po/PqsStationMap.java @@ -0,0 +1,58 @@ +package com.njcn.product.event.devcie.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * CN_Gather + * + * @author cdf + * @date 2025/8/11 + */ +@TableName(value="PQS_MAP") +@Data +public class PqsStationMap { + + + @TableId + @TableField(value = "MAP_INDEX") + private String mapIndex; + + + @TableField(value = "SUB_INDEX") + private Long subIndex; + + + @TableField(value = "GD_INDEX") + private Long gdIndex; + + //经度 + + @TableField(value = "LONGITUDE") + private Float longItude; + + //纬度 + + @TableField(value = "LATITUDE") + private Float latItude; + + //数据状态 + + @TableField(value = "STATE") + private Long state; + + //用户ID + + @TableField(value = "USER_INDEX") + private String userIndex; + + //更新时间 + + @TableField(value = "UPDATETIME") + private Date updateTime; + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqDeviceService.java b/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqDeviceService.java new file mode 100644 index 0000000..f33513e --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqDeviceService.java @@ -0,0 +1,26 @@ +package com.njcn.product.event.devcie.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.event.devcie.pojo.dto.DeviceDTO; +import com.njcn.product.event.devcie.pojo.dto.DeviceDeptDTO; +import com.njcn.product.event.devcie.pojo.po.PqDevice; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqDeviceService extends IService{ + + List queryListByIds(List lineIds); + + Page selectDeviceDTOPage(Page pqsEventdetailPage, String searchValue, List devIndexs); + + List selectDeviceDept(); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqLineService.java b/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqLineService.java new file mode 100644 index 0000000..d04181b --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqLineService.java @@ -0,0 +1,24 @@ +package com.njcn.product.event.devcie.service; + +import com.njcn.product.event.devcie.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.event.devcie.pojo.po.PqLine; +import com.baomidou.mybatisplus.extension.service.IService; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:43【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqLineService extends IService{ + + + List getBaseLineInfo(List ids); + + List getBaseLedger(@Param("ids") List ids, @Param("searchValue") String searchValue); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqSubstationService.java b/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqSubstationService.java new file mode 100644 index 0000000..15ca5bd --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqSubstationService.java @@ -0,0 +1,20 @@ +package com.njcn.product.event.devcie.service; + +import com.njcn.product.event.devcie.pojo.dto.SubstationDTO; +import com.njcn.product.event.devcie.pojo.po.PqSubstation; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:48【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqSubstationService extends IService{ + + List queryListByIds(List lineIds); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqsDeptslineService.java b/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqsDeptslineService.java new file mode 100644 index 0000000..048a45f --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/service/PqsDeptslineService.java @@ -0,0 +1,16 @@ +package com.njcn.product.event.devcie.service; + +import com.njcn.product.event.devcie.pojo.po.PqsDeptsline; +import com.baomidou.mybatisplus.extension.service.IService; + /** + * + * Description: + * Date: 2025/06/19 下午 3:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsDeptslineService extends IService{ + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqDeviceServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqDeviceServiceImpl.java new file mode 100644 index 0000000..4447b6a --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqDeviceServiceImpl.java @@ -0,0 +1,38 @@ +package com.njcn.product.event.devcie.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.event.devcie.pojo.dto.DeviceDTO; +import com.njcn.product.event.devcie.pojo.dto.DeviceDeptDTO; +import org.springframework.stereotype.Service; + +import java.util.List; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.devcie.pojo.po.PqDevice; +import com.njcn.product.event.devcie.mapper.PqDeviceMapper; +import com.njcn.product.event.devcie.service.PqDeviceService; +/** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqDeviceServiceImpl extends ServiceImpl implements PqDeviceService{ + + @Override + public List queryListByIds(List lineIds) { + return this.baseMapper.queryListByIds(lineIds); + } + + @Override + public Page selectDeviceDTOPage(Page pqsEventdetailPage, String searchValue, List devIndexs) { + return this.baseMapper.selectDeviceDTOPage(pqsEventdetailPage,searchValue,devIndexs); + } + + @Override + public List selectDeviceDept() { + return this.baseMapper.selectDeviceDept(); + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqLineServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqLineServiceImpl.java new file mode 100644 index 0000000..077a9ff --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqLineServiceImpl.java @@ -0,0 +1,69 @@ +package com.njcn.product.event.devcie.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.njcn.product.event.devcie.pojo.dto.LedgerBaseInfoDTO; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.devcie.mapper.PqLineMapper; +import com.njcn.product.event.devcie.pojo.po.PqLine; +import com.njcn.product.event.devcie.service.PqLineService; +import org.springframework.util.CollectionUtils; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:43【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqLineServiceImpl extends ServiceImpl implements PqLineService{ + + + @Override + public List getBaseLineInfo(List ids){ + List ledgerBaseInfoDTOS = new ArrayList<>(); + + if(CollectionUtils.isEmpty(ids)){ + return ledgerBaseInfoDTOS; + } + if(ids.size()>1000){ + List> listIds = CollUtil.split(ids,1000); + for(List itemIds : listIds){ + List temp =this.baseMapper.getBaseLineInfo(itemIds); + ledgerBaseInfoDTOS.addAll(temp); + } + }else { + List temp =this.baseMapper.getBaseLineInfo(ids); + ledgerBaseInfoDTOS.addAll(temp); + } + return ledgerBaseInfoDTOS; + } + + @Override + public List getBaseLedger(List ids,String searchValue) { + List ledgerBaseInfoDTOS = new ArrayList<>(); + + if(CollectionUtils.isEmpty(ids)){ + return ledgerBaseInfoDTOS; + } + if(ids.size()>1000){ + List> listIds = CollUtil.split(ids,1000); + for(List itemIds : listIds){ + List temp =this.baseMapper.getBaseLedger(itemIds,searchValue); + ledgerBaseInfoDTOS.addAll(temp); + } + }else { + List temp =this.baseMapper.getBaseLedger(ids,searchValue); + ledgerBaseInfoDTOS.addAll(temp); + } + return ledgerBaseInfoDTOS; + }; + + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqSubstationServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqSubstationServiceImpl.java new file mode 100644 index 0000000..6e045de --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqSubstationServiceImpl.java @@ -0,0 +1,26 @@ +package com.njcn.product.event.devcie.service.impl; + +import com.njcn.product.event.devcie.pojo.dto.SubstationDTO; +import org.springframework.stereotype.Service; + +import java.util.List; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.devcie.pojo.po.PqSubstation; +import com.njcn.product.event.devcie.mapper.PqSubstationMapper; +import com.njcn.product.event.devcie.service.PqSubstationService; +/** + * + * Description: + * Date: 2025/06/19 下午 1:48【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqSubstationServiceImpl extends ServiceImpl implements PqSubstationService{ + + @Override + public List queryListByIds(List lineIds) { + return this.baseMapper.queryListByIds(lineIds); + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqsDeptslineServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqsDeptslineServiceImpl.java new file mode 100644 index 0000000..661c392 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/devcie/service/impl/PqsDeptslineServiceImpl.java @@ -0,0 +1,19 @@ +package com.njcn.product.event.devcie.service.impl; + +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.PqsDeptslineMapper; +import com.njcn.product.event.devcie.pojo.po.PqsDeptsline; +import com.njcn.product.event.devcie.service.PqsDeptslineService; +/** + * + * Description: + * Date: 2025/06/19 下午 3:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqsDeptslineServiceImpl extends ServiceImpl implements PqsDeptslineService{ + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/report/controller/EasyPoiWordExportController.java b/event_smart/src/main/java/com/njcn/product/event/report/controller/EasyPoiWordExportController.java new file mode 100644 index 0000000..5de4cfa --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/report/controller/EasyPoiWordExportController.java @@ -0,0 +1,49 @@ +package com.njcn.product.event.report.controller; + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.event.report.pojo.param.ReportExportParam; +import com.njcn.product.event.report.service.EasyPoiWordExportService; +import com.njcn.product.event.report.utils.WordTemplate; +import com.njcn.product.event.transientes.pojo.param.LargeScreenCountParam; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; + +/** + * @Author: cdf + * @CreateTime: 2025-09-23 + * @Description: + */ +@Api(tags = "报告导出") +@RequestMapping("report") +@RestController +@RequiredArgsConstructor +@Slf4j +public class EasyPoiWordExportController extends BaseController { + + private final EasyPoiWordExportService easyPoiWordExportService; + + + @OperateInfo + @PostMapping("/get") + @ApiOperation("") + public void test(HttpServletResponse response, @RequestBody ReportExportParam param) { + String methodDescribe = getMethodDescribe("test"); + try { + easyPoiWordExportService.test(response,param); + } catch (Exception e) { + throw new RuntimeException(e); + } + // return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/report/pojo/dto/BjCustomReportDTO.java b/event_smart/src/main/java/com/njcn/product/event/report/pojo/dto/BjCustomReportDTO.java new file mode 100644 index 0000000..878b0d7 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/report/pojo/dto/BjCustomReportDTO.java @@ -0,0 +1,46 @@ +package com.njcn.product.event.report.pojo.dto; + +import lombok.Data; + +/** + * @Author: cdf + * @CreateTime: 2025-09-24 + * @Description: + */ +@Data +public class BjCustomReportDTO { + // 日期格式化(如“2025年09月17日”) + private String dateFormat; + // 总监测装置数量 + private Integer totalDevice; + // 总变电站数量 + private Integer totalSubstation; + // 总母线数量 + private Integer totalBus; + // 母线电压等级列表(如“220kV母线XX条、110kV母线XX条、10kV母线XX条”) + private String busVoltageList; + //亦庄涉及变电站22座,母线102条,其中:110kV母线7条,10kV母线95条。 + private String areaInfo; + // 统计日期范围(如“2025年09月17日16:46-16:53”) + private String dateRange; + // 北京地区总事件数 + private Integer bjTotalEvent; + // 北京地区涉及变电站数 + private Integer totalEventSubstation; + // 北京地区涉及母线数 + private Integer bjTotalBus; + // 变电站电压等级说明(如“220kV变电站X座、110kV变电站X座、10kV变电站X座”) + private String stationVoltage; + // 发生暂降的母线数 + private Integer busEventNum; + // 残余电压范围(如“16.48%-86.99%”) + private String residualVoltageRange; + // 持续时间范围(如“0.05s-0.086s”) + private String durationRange; + // 受影响用户类型(如“半导体企业、地铁、医院、政府机关”) + private String objTypeList; + // 受影响用户总数 + private Integer affectedUserCount; + // 暂降区域说明(如“亦庄经济开发区、通州新城、中心城区”) + private String areaContent; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/report/pojo/param/ReportExportParam.java b/event_smart/src/main/java/com/njcn/product/event/report/pojo/param/ReportExportParam.java new file mode 100644 index 0000000..6669c5f --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/report/pojo/param/ReportExportParam.java @@ -0,0 +1,30 @@ +package com.njcn.product.event.report.pojo.param; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-09-24 + * @Description: + */ +@Data +public class ReportExportParam { + + @ApiModelProperty("部门id") + private String deptId; + + @ApiModelProperty("开始时间") + private String searchBeginTime; + + @ApiModelProperty("结束时间") + private String searchEndTime; + + @ApiModelProperty("部门集合") + private List deptList; + + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/report/service/EasyPoiWordExportService.java b/event_smart/src/main/java/com/njcn/product/event/report/service/EasyPoiWordExportService.java new file mode 100644 index 0000000..6c25e6c --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/report/service/EasyPoiWordExportService.java @@ -0,0 +1,16 @@ +package com.njcn.product.event.report.service; + +import com.njcn.product.event.report.pojo.param.ReportExportParam; +import com.njcn.product.event.transientes.pojo.param.LargeScreenCountParam; + +import javax.servlet.http.HttpServletResponse; + +/** + * @Author: cdf + * @CreateTime: 2025-09-23 + * @Description: + */ +public interface EasyPoiWordExportService { + + void test(HttpServletResponse response, ReportExportParam param); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/report/service/impl/EasyPoiWordExportServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/report/service/impl/EasyPoiWordExportServiceImpl.java new file mode 100644 index 0000000..b9dc8ff --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/report/service/impl/EasyPoiWordExportServiceImpl.java @@ -0,0 +1,253 @@ +package com.njcn.product.event.report.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.event.devcie.mapper.PqLineMapper; +import com.njcn.product.event.devcie.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.event.devcie.pojo.dto.PqsDeptDTO; +import com.njcn.product.event.report.pojo.dto.BjCustomReportDTO; +import com.njcn.product.event.report.pojo.param.ReportExportParam; +import com.njcn.product.event.report.service.EasyPoiWordExportService; +import com.njcn.product.event.report.utils.WordTemplate; +import com.njcn.product.event.transientes.mapper.*; +import com.njcn.product.event.transientes.pojo.enums.DicTypeEnum; +import com.njcn.product.event.transientes.pojo.param.LargeScreenCountParam; +import com.njcn.product.event.transientes.pojo.po.*; +import com.njcn.product.event.transientes.service.CommGeneralService; +import com.njcn.product.event.transientes.service.MsgEventConfigService; +import lombok.RequiredArgsConstructor; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.DoubleStream; + +/** + * @Author: cdf + * @CreateTime: 2025-09-23 + * @Description: + */ +@Service +@RequiredArgsConstructor +public class EasyPoiWordExportServiceImpl implements EasyPoiWordExportService { + + private final CommGeneralService commGeneralService; + + private final PqLineMapper pqLineMapper; + + private final PqsDicDataMapper pqsDicDataMapper; + + private final PqsDicTypeMapper pqsDicTypeMapper; + + private final PqsDeptsMapper pqsDeptsMapper; + + private final PqsEventdetailMapper pqsEventdetailMapper; + + private final MsgEventConfigService msgEventConfigService; + private final PqUserLineAssMapper pqUserLineAssMapper; + private final PqUserLedgerMapper pqUserLedgerMapper; + private final PqsDicTreeMapper pqsDicTreeMapper; + + + public void test(HttpServletResponse response, ReportExportParam param) { + try { + List deptIds = commGeneralService.getLineIdsByRedis(param.getDeptId()); + if (CollUtil.isEmpty(deptIds)) { + throw new BusinessException(CommonResponseEnum.FAIL, "当前部门未绑定监测点"); + } + + //字典信息 + PqsDicType pqsDicType = pqsDicTypeMapper.selectOne(new LambdaQueryWrapper().eq(PqsDicType::getDicTypeName, DicTypeEnum.VOLTAGE.getDicName())); + List pqsDicDataList = pqsDicDataMapper.selectList(new LambdaQueryWrapper().eq(PqsDicData::getDicType, pqsDicType.getDicTypeIndex())); + Map pqsDicDataMap = pqsDicDataList.stream().collect(Collectors.toMap(PqsDicData::getDicIndex, dic -> dic)); + + List pqsDicTreePOList = pqsDicTreeMapper.selectList(null); + Map treePOMap = pqsDicTreePOList.stream().collect(Collectors.toMap(PqsDicTreePO::getId,tree->tree)); + + BjCustomReportDTO bjReportDTO = new BjCustomReportDTO(); + ledgerAssemble(bjReportDTO,param,deptIds,pqsDicDataMap); + + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .between(PqsEventdetail::getTimeid,DateUtil.parse(param.getSearchBeginTime(),DatePattern.NORM_DATETIME_PATTERN),DateUtil.parse(param.getSearchEndTime(),DatePattern.NORM_DATETIME_PATTERN)); + if (deptIds.size() > 1000) { + List> listList = CollUtil.split(deptIds, 1000); + lambdaQueryWrapper.and(w->{ + w.or(i->{ + for(List ids : listList){ + i.in(PqsEventdetail::getLineid,ids); + } + }); + }); + } else { + lambdaQueryWrapper.in(PqsEventdetail::getLineid,deptIds); + } + + List pqsEventdetailList = pqsEventdetailMapper.selectList(lambdaQueryWrapper); + bjReportDTO.setBjTotalEvent(pqsEventdetailList.size()); + List lineIds = pqsEventdetailList.stream().map(PqsEventdetail::getLineid).distinct().collect(Collectors.toList()); + List ledgerBaseInfoDTOList = pqLineMapper.getBaseLedger(lineIds,null); + long stationCount = ledgerBaseInfoDTOList.stream().map(LedgerBaseInfoDTO::getStationId).distinct().count(); + bjReportDTO.setTotalEventSubstation((int)stationCount); + long busCount = ledgerBaseInfoDTOList.stream().map(LedgerBaseInfoDTO::getBusBarId).distinct().count(); + bjReportDTO.setBjTotalBus((int)busCount); + String busVoltageStr = busVoltageDeal(ledgerBaseInfoDTOList,pqsDicDataMap); + bjReportDTO.setStationVoltage(busVoltageStr); + + double min = pqsEventdetailList.stream().mapToDouble(PqsEventdetail::getEventvalue).min().getAsDouble()*100; + double max = pqsEventdetailList.stream().mapToDouble(PqsEventdetail::getEventvalue).max().getAsDouble()*100; + bjReportDTO.setResidualVoltageRange(min+"%-"+max+"%"); + + double minPersisTime = pqsEventdetailList.stream().mapToDouble(PqsEventdetail::getPersisttime).min().getAsDouble()/1000; + double maxPersisTime = pqsEventdetailList.stream().mapToDouble(PqsEventdetail::getPersisttime).max().getAsDouble()/1000; + bjReportDTO.setDurationRange(minPersisTime+"s-"+maxPersisTime+"s"); + + List pqUserLineAssPOS = pqUserLineAssMapper.selectList(new LambdaQueryWrapper().in(PqUserLineAssPO::getLineIndex,lineIds)); + List userIds = pqUserLineAssPOS.stream().map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + List pqUserLedgerPOList = pqUserLedgerMapper.selectList(new LambdaQueryWrapper().in(PqUserLedgerPO::getId,userIds)); + Map> stringListMap = pqUserLedgerPOList.stream().collect(Collectors.groupingBy(PqUserLedgerPO::getSmallObjType)); + + String treeStr = userToStr(stringListMap,treePOMap); + + bjReportDTO.setObjTypeList(treeStr); + bjReportDTO.setAffectedUserCount(pqUserLedgerPOList.size()); + + ObjectMapper mapper = new ObjectMapper(); + Map map = mapper.convertValue(bjReportDTO,Map.class); + + WordTemplate.generateWordDownload("template/test.docx", response, "aa.docx", map); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void addUserDataToTable(XWPFDocument document) { + // 创建表格 + // 填充数据到表格(此处为示例代码框架,实际需完善表格创建和数据填充逻辑) + // 例如:创建表头、添加数据行等 + } + + /** + * 组装台账 + * @param bjReportDTO + * @param param + * @param deptIds + */ + private void ledgerAssemble(BjCustomReportDTO bjReportDTO,ReportExportParam param,List deptIds,Map pqsDicDataMap){ + bjReportDTO.setDateRange(param.getSearchBeginTime()+"至"+param.getSearchEndTime()); + + //台账信息 + List ledgerList = new ArrayList<>(); + if (deptIds.size() > 1000) { + List> listList = CollUtil.split(deptIds, 1000); + for (List ids : listList) { + List temList = pqLineMapper.getBaseLedger(ids, null); + ledgerList.addAll(temList); + } + } else { + ledgerList = pqLineMapper.getBaseLedger(deptIds, null); + } + + + long devCount = ledgerList.stream().map(LedgerBaseInfoDTO::getDevId).distinct().count(); + long stationCount = ledgerList.stream().map(LedgerBaseInfoDTO::getStationId).distinct().count(); + long busCount = ledgerList.stream().map(LedgerBaseInfoDTO::getBusBarId).distinct().count(); + + Map> scaleMap = ledgerList.stream().collect(Collectors.groupingBy(LedgerBaseInfoDTO::getScale)); + String result = scaleMap.entrySet().stream() + .map(entry -> { + String scale = entry.getKey(); + long busNum = entry.getValue().stream() + .map(LedgerBaseInfoDTO::getBusBarId) + .distinct() + .count(); + return pqsDicDataMap.get(scale).getDicName() + "母线" + busNum + "条"; + }) + // 用分号连接 + .collect(Collectors.joining(StrUtil.COMMA)); + bjReportDTO.setBusVoltageList(result); + + StringBuilder temStr = new StringBuilder(); + List pqsDeptsList = pqsDeptsMapper.getDeptList(param.getDeptList()); + Map deptMap = pqsDeptsList.stream().collect(Collectors.toMap(PqsDeptDTO::getDeptsIndex,PqsDeptDTO::getDeptsname)); + for (String deptId : param.getDeptList()) { + String deptName = deptMap.get(deptId); + StrBuilder strBuilderInner = new StrBuilder(deptName+"涉及变电站"); + List temIds = commGeneralService.getLineIdsByRedis(deptId); + List dtoList = pqLineMapper.getBaseLedger(temIds, null); + + long innerStation = dtoList.stream().map(LedgerBaseInfoDTO::getStationId).distinct().count(); + long innerBus = dtoList.stream().map(LedgerBaseInfoDTO::getStationId).distinct().count(); + + strBuilderInner.append(String.valueOf(innerStation)).append("座,母线").append(String.valueOf(innerBus)).append("条,其中:"); + Map> scaleInnerMap = dtoList.stream().collect(Collectors.groupingBy(LedgerBaseInfoDTO::getScale)); + String resultContent = scaleInnerMap.entrySet().stream() + .map(entry -> { + String scale = entry.getKey(); + long busNum = entry.getValue().stream() + .map(LedgerBaseInfoDTO::getBusBarId) + .distinct() + .count(); + return pqsDicDataMap.get(scale).getDicName() + "母线" + busNum + "条"; + }) + // 用分号连接 + .collect(Collectors.joining(StrUtil.COMMA)); + strBuilderInner.append(resultContent).append(";"); + temStr.append(strBuilderInner); + } + bjReportDTO.setAreaInfo(temStr.toString()); + + bjReportDTO.setDateFormat(DateUtil.format(DateUtil.parse(param.getSearchBeginTime()), DatePattern.NORM_DATE_PATTERN)); + bjReportDTO.setTotalDevice((int) devCount); + bjReportDTO.setTotalSubstation((int) stationCount); + bjReportDTO.setTotalBus((int) busCount); + } + + + private String busVoltageDeal(List ledgerList,Map pqsDicDataMap){ + Map> scaleMap = ledgerList.stream().collect(Collectors.groupingBy(LedgerBaseInfoDTO::getScale)); + String result = scaleMap.entrySet().stream() + .map(entry -> { + String scale = entry.getKey(); + long busNum = entry.getValue().stream() + .map(LedgerBaseInfoDTO::getBusBarId) + .distinct() + .count(); + return pqsDicDataMap.get(scale).getDicName() + "母线" + busNum + "条"; + }) + // 用分号连接 + .collect(Collectors.joining(StrUtil.COMMA)); + return result; + } + + private String userToStr(Map> stringListMap,Map treePOMap){ + String result = stringListMap.entrySet().stream() + .map(entry -> { + String scale = entry.getKey(); + long busNum = entry.getValue().stream() + .map(PqUserLedgerPO::getId) + .distinct() + .count(); + return treePOMap.get(scale).getName(); + }) + // 用分号连接 + .collect(Collectors.joining(StrUtil.COMMA)); + return result; + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/report/utils/WordTemplate.java b/event_smart/src/main/java/com/njcn/product/event/report/utils/WordTemplate.java new file mode 100644 index 0000000..0df4936 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/report/utils/WordTemplate.java @@ -0,0 +1,119 @@ +package com.njcn.product.event.report.utils; + +import cn.afterturn.easypoi.word.WordExportUtil; +import org.apache.poi.xwpf.usermodel.XWPFDocument; + +import java.io.*; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @Author: cdf + * @CreateTime: 2025-09-23 + * @Description: + */ +public class WordTemplate { + + public static void main(String[] args) throws Exception { + // 模板文件路径 + // 1. 处理模板路径并获取输入流 + String temPath = getTemplateInputStream("template/test.docx"); + if (temPath == null) { + throw new FileNotFoundException("模板文件不存在: template/test.docx"); + } + // 准备数据 + Map map = new HashMap<>(); + map.put("title", "用户信息表"); + map.put("userList", getUsersData()); // 假设getUsersData()方法返回用户数据列表 + // 导出 Word 文档 + XWPFDocument doc = WordExportUtil.exportWord07(temPath, map); + try (FileOutputStream outStream = new FileOutputStream("user_info.docx")) { + doc.write(outStream); + } + } + + private static List> getUsersData() { + // 返回模拟用户数据(实际需从数据库或其他数据源获取) + return new ArrayList<>(); + } + + + /** + * 生成Word文档(响应到浏览器下载) + * + * @param templatePath 模板路径 + * @param response HttpServletResponse + * @param fileName 下载文件名(如“20250917电压暂降监测报告.docx”) + * @param data 数据Map + */ + public static void generateWordDownload(String templatePath, javax.servlet.http.HttpServletResponse response, String fileName, Map data) throws Exception { + // 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("UTF-8"), "ISO8859-1")); + response.setCharacterEncoding("UTF-8"); + // 1. 处理模板路径并获取输入流 + String temPath = getTemplateInputStream(templatePath); + if (temPath == null) { + throw new FileNotFoundException("模板文件不存在: " + templatePath); + } + // 渲染文档并响应 + XWPFDocument document = WordExportUtil.exportWord07(temPath, data); + try (OutputStream outputStream = response.getOutputStream()) { + document.write(outputStream); + } catch (Exception e) { + + } finally { + document.close(); + } + } + + /** + * 导出Word文档,带模板文件检查 + * + * @param templatePath 模板文件路径(支持classpath路径和绝对路径) + * @param data 导出数据 + * @param outputPath 输出文件路径 + * @throws IOException 当模板不存在或IO操作失败时抛出 + */ + public static void exportWord(String templatePath, Map data, String outputPath) throws IOException { + // 1. 处理模板路径并获取输入流 + String temPath = getTemplateInputStream(templatePath); + if (temPath == null) { + throw new FileNotFoundException("模板文件不存在: " + templatePath); + } + + // 3. 写入输出文件 + try (FileOutputStream outStream = new FileOutputStream(outputPath); XWPFDocument doc = WordExportUtil.exportWord07(temPath, data)) { + // 2. 导出Word文档 + doc.write(outStream); + } catch (Exception e) { + e.printStackTrace(); + } + + } + + /** + * 获取模板文件输入流(优先从classpath查找,再查找绝对路径) + * + * @param templatePath 模板路径 + * @return 输入流,若文件不存在则返回null + */ + private static String getTemplateInputStream(String templatePath) { + // 1. 尝试从classpath资源加载(适用于Spring Boot项目的resources目录) + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + URL url = classLoader.getResource(templatePath); + if (url != null) { + return url.getPath(); + } + + // 2. 尝试从绝对路径加载 + File file = new File(templatePath); + if (file.exists() && file.isFile()) { + return file.getPath(); + } + return null; + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/controller/EventGateController.java b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/EventGateController.java new file mode 100644 index 0000000..d75bc5f --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/EventGateController.java @@ -0,0 +1,365 @@ +package com.njcn.product.event.transientes.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.common.LogEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.event.file.pojo.dto.WaveDataDTO; +import com.njcn.product.event.devcie.mapper.PqLineMapper; +import com.njcn.product.event.devcie.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.event.devcie.pojo.po.PqLine; +import com.njcn.product.event.devcie.pojo.po.PqsDeptsline; +import com.njcn.product.event.devcie.service.PqsDeptslineService; +import com.njcn.product.event.transientes.mapper.PqUserLedgerMapper; +import com.njcn.product.event.transientes.mapper.PqUserLineAssMapper; +import com.njcn.product.event.transientes.pojo.param.MonitorTerminalParam; +import com.njcn.product.event.transientes.pojo.param.SimulationMsgParam; +import com.njcn.product.event.transientes.pojo.po.*; +import com.njcn.product.event.transientes.service.*; +import com.njcn.product.event.transientes.service.impl.MsgEventInfoServiceImpl; +import com.njcn.product.event.transientes.websocket.WebSocketServer; +import com.njcn.redis.utils.RedisUtil; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.njcn.product.event.transientes.pojo.constant.RedisConstant.REDIS_DEPT_INDEX; + +/** + * @Author: cdf + * @CreateTime: 2025-06-23 + * @Description: + */ +@Api(tags = "暂降接收") +@RequestMapping("accept") +@RestController +@RequiredArgsConstructor +@Slf4j +public class EventGateController extends BaseController { + private final MsgEventInfoServiceImpl msgEventInfoServiceImpl; + private final PqUserLineAssMapper pqUserLineAssMapper; + private final PqUserLedgerMapper pqUserLedgerMapper; + @Value("${SYS_TYPE_ZT}") + private String sysTypeZt; + + private final WebSocketServer webSocketServer; + + private final PqsDeptslineService pqsDeptslineService; + + private final PqsDeptsService pqsDeptsService; + + private final PqsUserService pqsUserService; + + private final PqsUsersetService pqsUsersetService; + + private final PqLineMapper pqLineMapper; + + private final EventGateService eventGateService; + + private final MsgEventConfigService msgEventConfigService; + + private final MsgEventInfoService msgEventInfoService; + + private final RedisUtil redisUtil; + + + @OperateInfo + @GetMapping("/eventMsg") + @ApiOperation("接收远程推送的暂态事件") + @ApiImplicitParam(name = "eventMsg", value = "暂态事件json字符", required = true) + public HttpResult eventMsg(@RequestParam("msg") String msg) { + String methodDescribe = getMethodDescribe("eventMsg"); + log.info("收到前置推送暂降事件:"+msg); + + JSONObject jsonObject; + try { + //下面一行代码正式环境需要放开 + jsonObject = new JSONObject(msg); + //下面一行代码正式环境需要放开 + //jsonObject = test(); + + if (msgEventConfigService.getEventType().contains(jsonObject.get("wavetype").toString()) + && Float.parseFloat(jsonObject.get("eventvalue").toString()) <= msgEventConfigService.getEventValue() + && (Float.parseFloat(jsonObject.get("persisttime").toString())*1000) >= msgEventConfigService.getEventDuration()) { + //过滤重要暂降事件 + Integer lineId = Integer.valueOf(jsonObject.get("lineid").toString()); + List assList = pqUserLineAssMapper.selectList(new LambdaQueryWrapper().eq(PqUserLineAssPO::getLineIndex,lineId)); + + String str ="/"; + if(CollUtil.isNotEmpty(assList)){ + List userIds = assList.stream().map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + List poList = pqUserLedgerMapper.selectList(new LambdaQueryWrapper().select(PqUserLedgerPO::getId,PqUserLedgerPO::getCustomerName).in(PqUserLedgerPO::getId,userIds)); + str = poList.stream().map(PqUserLedgerPO::getCustomerName).collect(Collectors.joining(StrUtil.COMMA)); + } + + List deptsList = (List)redisUtil.getObjectByKey(REDIS_DEPT_INDEX+ StrUtil.DASHED+"AllDept"); + Map deptsMap = deptsList.stream().collect(Collectors.toMap(PqsDepts::getDeptsIndex,dept->dept)); + + List deptslineList = pqsDeptslineService.lambdaQuery().eq(PqsDeptsline::getLineIndex,lineId).list(); + List deptIds = deptslineList.stream().map(PqsDeptsline::getDeptsIndex).collect(Collectors.toList()); + Set set =getAllParentIdsWithChildrenBatch(deptIds,deptsMap); + jsonObject.putOpt("objName",str); + jsonObject.putOpt("dept", String.join(StrUtil.COMMA, set)); + + webSocketServer.sendMessageToAll(jsonObject.toString()); + } + + } catch (Exception e) { + e.printStackTrace(); + log.error("暂降json格式异常!{}", e.getMessage()); + } + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + + public Set getAllParentIdsRecursive(String deptId, Map deptMap, Set result) { + if (deptId == null || result.contains(deptId)) { + return result; + } + + result.add(deptId); // 添加当前ID + PqsDepts dept = deptMap.get(deptId); + if (dept != null && dept.getParentnodeid() != null) { + getAllParentIdsRecursive(dept.getParentnodeid(), deptMap, result); // 递归处理父节点 + } + return result; + } + + // 批量处理入口方法 + public Set getAllParentIdsWithChildrenBatch(Collection deptIds, Map deptMap) { + Set result = new HashSet<>(); + for (String deptId : deptIds) { + getAllParentIdsRecursive(deptId, deptMap, result); + } + return result; + } + + @OperateInfo + @GetMapping("/testEvent") + @ApiOperation("接收远程推送的暂态事件") + public HttpResult testEvent() { + String methodDescribe = getMethodDescribe("testEvent"); + log.info("模拟测试发送暂降事件-------------------------"); + + JSONObject jsonObject; + try { + //下面一行代码正式环境需要放开 + jsonObject = test(); + + if (msgEventConfigService.getEventType().contains(jsonObject.get("wavetype").toString()) &&Float.parseFloat(jsonObject.get("eventvalue").toString()) <= msgEventConfigService.getEventValue()) { + webSocketServer.sendMessageToAll(jsonObject.toString()); + + //开始发送短信 + try { + sendMessage(jsonObject); + }catch (Exception e){ + log.error("短信组装发送失败!失败原因{}",e.getMessage()); + } + + } + + } catch (Exception e) { + log.error("暂降json格式异常!{}", e.getMessage()); + } + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + + + //测试模拟,正式环境删除 + private PqsEventdetail createEvent(JSONObject jsonObject, LocalDateTime now) { + PqsEventdetail pqsEventdetail = new PqsEventdetail(); + pqsEventdetail.setEventdetailIndex(jsonObject.get("eventdetail_index").toString()); + pqsEventdetail.setLineid(Integer.valueOf(jsonObject.get("lineid").toString())); + pqsEventdetail.setTimeid(now); + pqsEventdetail.setMs(new BigDecimal(jsonObject.get("ms").toString())); + pqsEventdetail.setWavetype(Integer.valueOf(jsonObject.get("wavetype").toString())); + pqsEventdetail.setPersisttime(Double.valueOf(jsonObject.get("persisttime").toString())); + pqsEventdetail.setEventvalue(Double.valueOf(jsonObject.get("eventvalue").toString())); + pqsEventdetail.setEventreason(jsonObject.get("eventreason").toString()); + pqsEventdetail.setEventtype(jsonObject.get("eventtype").toString()); + + return pqsEventdetail; + } + + //测试模拟,正式环境删除 + private JSONObject test() { + /*----------------------------------------------------------------------------------------*/ + //以下部分为测试数据后续删除 + List pqLineList = pqLineMapper.selectList(new LambdaQueryWrapper<>()); + List lineList = pqLineList.stream().map(PqLine::getLineIndex).collect(Collectors.toList()); + List baseInfoDTOList = pqLineMapper.getBaseLineInfo(lineList); + Map map = baseInfoDTOList.stream().collect(Collectors.toMap(LedgerBaseInfoDTO::getLineId, Function.identity())); + + Random random = new Random(); + Integer lineId = lineList.get(random.nextInt(lineList.size())); + LedgerBaseInfoDTO dto = map.get(lineId); + + LocalDateTime now = LocalDateTime.now(); + String timeStr = DateUtil.format(now, DatePattern.NORM_DATETIME_PATTERN); + Long ms = (long) random.nextInt(999); + + Integer[] temArr = new Integer[]{1, 3}; + Integer wave = random.nextInt(2); + + + Double per = (double)random.nextInt(5000); + + double minV = 0.1; + double maxV = 0.9; + Double eventValue = minV + (maxV - minV) * Math.random(); + + String id = IdUtil.simpleUUID(); + + JSONObject tem = new JSONObject(); + tem.set("eventdetail_index", id); + tem.set("lineid", lineId.toString()); + tem.set("timeid", timeStr); + tem.set("ms", ms.toString()); + tem.set("wavetype", temArr[wave]); + tem.set("persisttime", per.toString()); + tem.set("eventvalue", eventValue); + tem.set("eventreason", "97a56e0f-b546-4c1e-b27c-52463fc1d82f"); + tem.set("eventtype", "676683a0-7f80-43e6-8df8-bea8ed235d67"); + tem.set("gdname", "测试供电公司"); + tem.set("bdname", dto.getStationName()); + tem.set("pointname", dto.getLineName()); + + /* PqsEventdetail pqsEventdetail = createEvent(tem, now); + if (msgEventConfigService.getEventType().contains(tem.get("wavetype").toString())) { + webSocketServer.sendMessageToAll(tem.toString()); + } + pqsEventdetailService.save(pqsEventdetail);*/ + /*----------------------------------------------------------------------------------------*/ + + return tem; + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/getTransientAnalyseWave") + @ApiOperation("暂态事件波形分析") + public HttpResult getTransientAnalyseWave(@RequestBody MonitorTerminalParam param) { + String methodDescribe = getMethodDescribe("getTransientAnalyseWave"); + WaveDataDTO wave = eventGateService.getTransientAnalyseWave(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, wave, methodDescribe); + } + + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @PostMapping("/simulationSend") + @ApiOperation("模拟发送短信") + public HttpResult simulationSend(@RequestBody @Validated SimulationMsgParam param) { + String methodDescribe = getMethodDescribe("simulationSend"); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + + + private void sendMessage(JSONObject jsonObject) throws Exception{ + Integer lineId = Integer.valueOf(jsonObject.get("lineid").toString()); + List pqLineDept = pqsDeptslineService.lambdaQuery().eq(PqsDeptsline::getLineIndex, lineId).eq(PqsDeptsline::getSystype, sysTypeZt).list(); + Set deptIds = pqLineDept.stream().map(PqsDeptsline::getDeptsIndex).collect(Collectors.toSet()); + Set resultIds = getAllParentDeptIds(deptIds); + + List pqsUserSetList = pqsUsersetService.lambdaQuery().eq(PqsUserSet::getIsNotice, 1).in(PqsUserSet::getDeptsIndex, resultIds).list(); + if (CollUtil.isEmpty(pqsUserSetList)) { + //当前事件未找到用户信息,判断为不需要发送短信用户 + return; + } + List pqsUserList = pqsUserService.lambdaQuery().select(PqsUser::getUserIndex,PqsUser::getPhone,PqsUser::getName).in(PqsUser::getUserIndex, pqsUserSetList.stream().map(PqsUserSet::getUserIndex).collect(Collectors.toList())).list(); + List userIds = pqsUserList.stream().map(PqsUser::getUserIndex).collect(Collectors.toList()); + List poList = pqsUserSetList.stream().filter(it -> userIds.contains(it.getUserIndex())).collect(Collectors.toList()); + if (CollUtil.isNotEmpty(poList)) { + StringBuilder stringBuilder = new StringBuilder(jsonObject.get("timeid").toString()); + List list = pqLineMapper.getBaseLineInfo(Stream.of(lineId).collect(Collectors.toList())); + LedgerBaseInfoDTO ledgerBaseInfoDTO = list.get(0); + BigDecimal bigDecimal = new BigDecimal(jsonObject.get("eventvalue").toString()).multiply(new BigDecimal(100)).setScale(2, RoundingMode.HALF_UP); + stringBuilder.append(".").append(jsonObject.get("ms").toString()).append(", ").append(ledgerBaseInfoDTO.getStationName()).append(ledgerBaseInfoDTO.getLineName()) + .append("发生暂降事件,事件特征幅值").append(bigDecimal).append("%,持续时间:").append(jsonObject.get("persisttime").toString()).append("S"); + //TODO 发送短信 + // System.out.println(stringBuilder); + + List resultList = new ArrayList<>(); + for (PqsUser user : pqsUserList) { + MsgEventInfo msgEventInfo = new MsgEventInfo(); + msgEventInfo.setEventIndex(jsonObject.get("eventdetail_index").toString()); + msgEventInfo.setMsgContent(stringBuilder.toString()); + msgEventInfo.setMsgIndex(IdUtil.simpleUUID()); + msgEventInfo.setPhone(user.getPhone()); + msgEventInfo.setSendResult(0); + msgEventInfo.setUserId(user.getUserIndex()); + msgEventInfo.setUserName(user.getName()); + msgEventInfo.setIsHandle(0); + msgEventInfo.setSendTime(LocalDateTime.now()); + resultList.add(msgEventInfo); + } + msgEventInfoService.saveBatch(resultList); + } + } + + /** + * 获取远程短信平台token + */ + private String apiToken() { + + return "token"; + } + + private boolean apiSend(){ + return false; + } + + + public Set getAllParentDeptIds(Set deptIds) { + // 首次获取直接父级 + List allDeptList = pqsDeptsService.lambdaQuery().list(); + // 递归获取所有父级 + Set result = recursivelyGetParentIds(deptIds, allDeptList); + return result; + } + + /** + * 递归获取所有父级ID + * + * @param currentParentIds 当前层级的父级ID集合 + * @return 所有层级的父级ID集合 + */ + private Set recursivelyGetParentIds(Set currentParentIds, List allDeptList) { + Set result = new HashSet<>(currentParentIds); + Set nextLevelParentIds = new HashSet<>(); + List parentDeptList = allDeptList.stream().filter(it -> currentParentIds.contains(it.getDeptsIndex())).collect(Collectors.toList()); + for (PqsDepts pqsDepts : parentDeptList) { + if (!pqsDepts.getParentnodeid().equals("0")) { + nextLevelParentIds.add(pqsDepts.getParentnodeid()); + } + } + // 如果有更高层级的父级,继续递归 + if (!nextLevelParentIds.isEmpty()) { + Set deeperParentIds = recursivelyGetParentIds(nextLevelParentIds, allDeptList); + result.addAll(deeperParentIds); + } + return result; + } + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/controller/EventRightController.java b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/EventRightController.java new file mode 100644 index 0000000..8a38304 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/EventRightController.java @@ -0,0 +1,130 @@ +package com.njcn.product.event.transientes.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.event.devcie.pojo.po.PqGdCompany; +import com.njcn.product.event.devcie.pojo.po.PqSubstation; +import com.njcn.product.event.transientes.pojo.param.LargeScreenCountParam; +import com.njcn.product.event.transientes.pojo.po.*; +import com.njcn.product.event.transientes.pojo.vo.EventDetailVO; +import com.njcn.product.event.transientes.pojo.vo.UserLedgerStatisticVO; +import com.njcn.product.event.transientes.service.*; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.*; + +/** + * @Author: cdf + * @CreateTime: 2025-06-23 + * @Description: + */ +@Api(tags = "暂降接收") +@RequestMapping("right") +@RestController +@RequiredArgsConstructor +@Slf4j +public class EventRightController extends BaseController { + + private final EventRightService eventRightService; + + + @OperateInfo + @PostMapping("/rightEvent") + @ApiOperation("右侧表头") + @ApiImplicitParam(name = "largeScreenCountParam", value = "", required = true) + public HttpResult rightEvent(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("rightEvent"); + UserLedgerStatisticVO userLedgerStatisticVO = eventRightService.userLedgerStatisticClone(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, userLedgerStatisticVO, methodDescribe); + } + + + @OperateInfo + @PostMapping("/rightImportUser") + @ApiOperation("右侧重要用户") + @ApiImplicitParam(name = "largeScreenCountParam", value = "", required = true) + public HttpResult rightImportUser(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("rightImportUser"); + List result = eventRightService.rightImportUser(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo + @PostMapping("/rightEventOpen") + @ApiOperation("右侧表头点击事件") + @ApiImplicitParam(name = "largeScreenCountParam", value = "", required = true) + public HttpResult rightEventOpen(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("rightEventOpen"); + Page page = eventRightService.rightEventOpenClone(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, page, methodDescribe); + } + + @OperateInfo + @PostMapping("/rightEventOpenClone") + @ApiOperation("右侧表头点击事件") + @ApiImplicitParam(name = "largeScreenCountParam", value = "", required = true) + public HttpResult rightEventOpenClone(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("rightEventOpenClone"); + Page page = eventRightService.rightEventOpen(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, page, methodDescribe); + } + + @OperateInfo + @PostMapping("/rightEventOpenForDetail") + @ApiOperation("右侧表头点击事件") + @ApiImplicitParam(name = "largeScreenCountParam", value = "", required = true) + public HttpResult rightEventOpenForDetail(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("rightEventOpenForDetail"); + Page page = eventRightService.rightEventOpenForDetail(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, page, methodDescribe); + } + + + + @OperateInfo + @PostMapping("/rightEventDevOpen") + @ApiOperation("右侧表头终端点击事件") + @ApiImplicitParam(name = "largeScreenCountParam", value = "", required = true) + public HttpResult rightEventDevOpen(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("rightEventDevOpen"); + Page page = eventRightService.rightEventDevOpen(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, page, methodDescribe); + } + + + @OperateInfo + @PostMapping("/rightImportOpenDetail") + @ApiOperation("右侧表头终端点击事件") + @ApiImplicitParam(name = "largeScreenCountParam", value = "", required = true) + public HttpResult rightImportOpenDetail(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("rightImportOpenDetail"); + PqUserLedgerPO po = eventRightService.rightImportOpenDetail(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, po, methodDescribe); + } + + + @GetMapping("gdSelect") + public HttpResult> gdSelect() { + String methodDescribe = getMethodDescribe("gdSelect"); + List list = eventRightService.gdSelect(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe); + } + + + @GetMapping("bdSelect") + public HttpResult> bdSelect() { + String methodDescribe = getMethodDescribe("bdSelect"); + List list = eventRightService.bdSelect(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe); + } + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/controller/LargeScreenCountController.java b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/LargeScreenCountController.java new file mode 100644 index 0000000..ce561f3 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/LargeScreenCountController.java @@ -0,0 +1,265 @@ +package com.njcn.product.event.transientes.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.event.devcie.pojo.dto.DeviceDTO; +import com.njcn.product.event.transientes.pojo.param.LargeScreenCountParam; +import com.njcn.product.event.transientes.pojo.param.MessageEventFeedbackParam; +import com.njcn.product.event.transientes.pojo.po.MsgEventInfo; +import com.njcn.product.event.transientes.pojo.vo.*; +import com.njcn.product.event.transientes.service.LargeScreenCountService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Description: + * Date: 2025/06/19 下午 3:00【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Slf4j +@Api(tags = "大屏统计") +@RestController +@RequestMapping("/largescreen") +@RequiredArgsConstructor +public class LargeScreenCountController extends BaseController { + + private final LargeScreenCountService largeScreenCountService; + + @OperateInfo + @PostMapping("/initLedger") + @ApiOperation("台账规模统计") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult initLedger(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("initLedger"); + largeScreenCountService.initLedger(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe); + } + + @OperateInfo + @PostMapping("/ledgercount") + @ApiOperation("台账规模统计") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult scaleStatistics(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("scaleStatistics"); + LedgerCountVO result = largeScreenCountService.scaleStatistics(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo + @PostMapping("/alarmAnalysis") + @ApiOperation("告警统计分析") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult alarmAnalysis(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("alarmAnalysis"); + AlarmAnalysisVO result = largeScreenCountService.alarmAnalysis(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + @OperateInfo + @PostMapping("/alarmAnalysisDetail") + @ApiOperation("告警统计分析详情") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult alarmAnalysisDetail(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("alarmAnalysisDetail"); + AlarmAnalysisVO result = largeScreenCountService.alarmAnalysisDetail(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + @OperateInfo + @PostMapping("/eventTablePage") + @ApiOperation("告警统计分析详情") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> eventTablePage(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("alarmAnalysisDetail"); + Page result = largeScreenCountService.eventTablePage(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo + @PostMapping("/eventTrend") + @ApiOperation("暂降事件趋势") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> eventTrend(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("eventTrend"); + List result = largeScreenCountService.eventTrend(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo + @PostMapping("/eventList") + @ApiOperation("暂降事件列表") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> eventList(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("eventList"); + Page result = largeScreenCountService.eventList(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + @OperateInfo + @PostMapping("/noDealEventList") + @ApiOperation("未处理暂降事件列表") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> noDealEventList(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("noDealEventList"); + List result = largeScreenCountService.noDealEventList(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + @OperateInfo + @PostMapping("/mapCount") + @ApiOperation("地图统计") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> mapCount(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("mapCount"); + List result = largeScreenCountService.mapCount(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + @OperateInfo(operateType= OperateType.UPDATE) + @PostMapping("/lookEvent") + @ApiOperation("处理暂降事件") + @ApiImplicitParam(name = "eventIds", value = "暂降事件id", required = true) + public HttpResult lookEvent(@RequestBody List eventIds) { + String methodDescribe = getMethodDescribe("lookEvent"); + boolean result = largeScreenCountService.lookEvent(eventIds); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo + @GetMapping("/eventMsgDetail") + @ApiOperation("暂降事件列表详情按钮") + @ApiImplicitParam(name = "eventId", value = "暂降事件id", required = true) + public HttpResult eventMsgDetail(@RequestParam("eventId")String eventId) { + String methodDescribe = getMethodDescribe("eventMsgDetail"); + EventMsgDetailVO result = largeScreenCountService.eventMsgDetail(eventId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo + @PostMapping("/msgSendList") + @ApiOperation("远程通知列表") + @ApiImplicitParam(name = "largeScreenCountParam", value = "参数", required = true) + public HttpResult> msgSendList(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("msgSendList"); + List result = largeScreenCountService.msgSendList(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo + @PostMapping("/hasSendMsgPage") + @ApiOperation("已发送短信列表") + @ApiImplicitParam(name = "largeScreenCountParam", value = "参数", required = true) + public HttpResult> hasSendMsgPage(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("hasSendMsgPage"); + Page result = largeScreenCountService.hasSendMsgPage(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(operateType = OperateType.ADD) + @PostMapping("/msgHandle") + @ApiOperation("短信处理") + @ApiImplicitParam(name = "eventId", value = "暂降事件id", required = true) + public HttpResult msgHandle(@RequestBody @Validated MessageEventFeedbackParam messageEventFeedbackParam) { + String methodDescribe = getMethodDescribe("msgHandle"); + largeScreenCountService.msgHandle(messageEventFeedbackParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } + + + @OperateInfo + @PostMapping("/devFlagCount") + @ApiOperation("终端运行统计") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult devFlagCount(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("devFlagCount"); + DeviceCountVO deviceCountVO = largeScreenCountService.devFlagCount(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, deviceCountVO, methodDescribe); + } + @OperateInfo + @PostMapping("/devDetail") + @ApiOperation("终端运行统计") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> devDetail(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("devDetail"); + List deviceDTOList = largeScreenCountService.devDetail(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, deviceDTOList, methodDescribe); + } + + @OperateInfo + @PostMapping("/areaDevCount") + @ApiOperation("区域终端统计") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> areaDevCount(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("areaDevCount"); + List result = largeScreenCountService.regionDevCount(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo + @PostMapping("/substationCount") + @ApiOperation("变电站统计") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> substationCount(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("substationCount"); + List result = largeScreenCountService.substationCount(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo + @PostMapping("/regionDevCount") + @ApiOperation("区域终端统计") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> rightUserStatistic(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("rightUserStatistic"); + List result = largeScreenCountService.regionDevCount(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + + + + @OperateInfo + @PostMapping("/eventPage") + @ApiOperation("分页查询暂降事件") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> eventPage(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("eventPage"); + Page result = largeScreenCountService.eventPage(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + @OperateInfo + @PostMapping("/devicePage") + @ApiOperation("终端分页查询") + @ApiImplicitParam(name = "largeScreenCountParam", value = "查询参数", required = true) + public HttpResult> devicePage(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("devicePage"); + Page result = largeScreenCountService.devicePage(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(operateType= OperateType.UPDATE) + @PostMapping("/userEventList") + @ApiOperation("查询暂降事件") + @ApiImplicitParam(name = "eventIds", value = "暂降事件id", required = true) + public HttpResult> userEventList(@RequestBody LargeScreenCountParam largeScreenCountParam) { + String methodDescribe = getMethodDescribe("userEventList"); + Page result = largeScreenCountService.userEventList(largeScreenCountParam); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/controller/MsgEventConfigController.java b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/MsgEventConfigController.java new file mode 100644 index 0000000..13dc44e --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/MsgEventConfigController.java @@ -0,0 +1,53 @@ +package com.njcn.product.event.transientes.controller; + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.constant.OperateType; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.event.transientes.pojo.po.MsgEventConfig; +import com.njcn.product.event.transientes.service.MsgEventConfigService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * @Author: cdf + * @CreateTime: 2025-06-27 + * @Description: + */ +@Api(tags = "暂降平台配置") +@RequestMapping("config") +@RestController +@RequiredArgsConstructor +@Slf4j +public class MsgEventConfigController extends BaseController { + + private final MsgEventConfigService msgEventConfigService; + + @OperateInfo(operateType = OperateType.ADD) + @PostMapping("/eventConfig") + @ApiOperation("暂降平台配置") + @ApiImplicitParam(name = "msgEventConfig", value = "实体", required = true) + @Transactional(rollbackFor = Exception.class) + public HttpResult eventConfig(@RequestBody @Validated MsgEventConfig msgEventConfig) { + String methodDescribe = getMethodDescribe("eventConfig"); + msgEventConfigService.eventConfig(msgEventConfig); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe); + } + + @OperateInfo + @GetMapping("/queryConfig") + @ApiOperation("接收远程推送的暂态事件") + public HttpResult queryConfig() { + String methodDescribe = getMethodDescribe("queryConfig"); + MsgEventConfig msgEventConfig = msgEventConfigService.queryConfig(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, msgEventConfig, methodDescribe); + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/controller/PqUserLedgerController.java b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/PqUserLedgerController.java new file mode 100644 index 0000000..4f87727 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/PqUserLedgerController.java @@ -0,0 +1,54 @@ +package com.njcn.product.event.transientes.controller; + + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.event.transientes.pojo.param.PqUserLedgerParam; +import com.njcn.product.event.transientes.pojo.po.PqUserLedgerPO; +import com.njcn.product.event.transientes.service.PqUserLedgerService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-07-28 + * @Description: + */ +@RestController +@RequestMapping("/pqUser/ledger") +public class PqUserLedgerController { + + @Autowired + private PqUserLedgerService pqUserLedgerService; + + // 添加记录 + @PostMapping("addLedger") + public boolean addLedger(@RequestBody PqUserLedgerParam ledgerParam) { + return pqUserLedgerService.addLedger(ledgerParam); + } + + // 更新记录 + @PostMapping("updateLedger") + public boolean updateLedger(@RequestBody PqUserLedgerParam ledgerParam) { + return pqUserLedgerService.updateLedger(ledgerParam); + } + + // 删除记录 + @PostMapping("deleteLedger") + public boolean deleteLedger(@RequestBody List ids) { + return pqUserLedgerService.deleteLedger(ids); + } + + // 查询单条记录 + @GetMapping("/getLedgerById") + public PqUserLedgerPO getLedgerById(@PathVariable String id) { + return pqUserLedgerService.getLedgerById(id); + } + + // 查询所有记录 + @GetMapping + public Page pageList(@RequestBody PqUserLedgerParam ledgerParam) { + return pqUserLedgerService.pageList(ledgerParam); + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/controller/PqsDicTreeController.java b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/PqsDicTreeController.java new file mode 100644 index 0000000..57a98b9 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/controller/PqsDicTreeController.java @@ -0,0 +1,46 @@ +package com.njcn.product.event.transientes.controller; + +import com.njcn.common.pojo.annotation.OperateInfo; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.event.transientes.pojo.po.PqsDicTreePO; +import com.njcn.product.event.transientes.service.PqsDicTreeService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-08-01 + * @Description: + */ +@Api(tags = "字典树") +@RequestMapping("dicTree") +@RestController +@RequiredArgsConstructor +@Slf4j +public class PqsDicTreeController extends BaseController { + + private final PqsDicTreeService pqsDicTreeService; + + + @OperateInfo + @GetMapping("/getDicTree") + @ApiOperation("获取树结构") + public HttpResult> getDicTree(@RequestParam("code") String code){ + String methodDescribe = getMethodDescribe("getDicTree"); + + List pqsDicTreePOList = pqsDicTreeService.getDicTree(code); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, pqsDicTreePOList, methodDescribe); + } + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/filter/JwtRequestFilter.java b/event_smart/src/main/java/com/njcn/product/event/transientes/filter/JwtRequestFilter.java new file mode 100644 index 0000000..6a38736 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/filter/JwtRequestFilter.java @@ -0,0 +1,82 @@ +package com.njcn.product.event.transientes.filter; + +import cn.hutool.json.JSONObject; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.event.transientes.utils.JwtUtil; +import io.jsonwebtoken.ExpiredJwtException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +@Slf4j +public class JwtRequestFilter extends OncePerRequestFilter { + + private final UserDetailsService userDetailsService; + private final JwtUtil jwtUtil; + + public JwtRequestFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) { + this.userDetailsService = userDetailsService; + this.jwtUtil = jwtUtil; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + System.out.println(55); + + final String authorizationHeader = request.getHeader("Authorization"); + String username = null; + String jwt = null; + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + jwt = authorizationHeader.substring(7); + try { + username = jwtUtil.extractUsername(jwt); + } catch (ExpiredJwtException e) { + log.error(e.getMessage()); + sendErrorResponse(response,CommonResponseEnum.TOKEN_EXPIRE_JWT); + return; + } catch (Exception e) { + log.error(e.getMessage()); + sendErrorResponse(response,CommonResponseEnum.PARSE_TOKEN_ERROR); + return; + } + } + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); + + if (jwtUtil.validateToken(jwt, userDetails)) { + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = + new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); + } + } + chain.doFilter(request, response); + } + + private void sendErrorResponse(HttpServletResponse response, CommonResponseEnum error) throws IOException { + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("application/json;charset=UTF-8"); + + HttpResult httpResult = new HttpResult<>(); + httpResult.setCode(error.getCode()); + httpResult.setMessage(error.getMessage()); + + response.getWriter().write(new JSONObject(httpResult, false).toString()); + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/handler/ControllerUtil.java b/event_smart/src/main/java/com/njcn/product/event/transientes/handler/ControllerUtil.java new file mode 100644 index 0000000..d8807d0 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/handler/ControllerUtil.java @@ -0,0 +1,37 @@ +package com.njcn.product.event.transientes.handler; + +import com.njcn.common.pojo.constant.LogInfo; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.MethodArgumentNotValidException; + +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年06月22日 10:25 + */ +@Slf4j +public class ControllerUtil { + + /** + * 针对methodArgumentNotValidException 异常的处理 + * @author cdf + */ + public static String getMethodArgumentNotValidException(MethodArgumentNotValidException methodArgumentNotValidException) { + String operate = LogInfo.UNKNOWN_OPERATE; + Method method = null; + try { + method = methodArgumentNotValidException.getParameter().getMethod(); + if (!Objects.isNull(method) && method.isAnnotationPresent(ApiOperation.class)) { + ApiOperation apiOperation = method.getAnnotation(ApiOperation.class); + operate = apiOperation.value(); + } + }catch (Exception e){ + log.error("根据方法参数非法异常获取@ApiOperation注解值失败,参数非法异常信息:{},方法名:{},异常信息:{}",methodArgumentNotValidException.getMessage(),method,e.getMessage()); + } + return operate; + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/handler/GlobalBusinessExceptionHandler.java b/event_smart/src/main/java/com/njcn/product/event/transientes/handler/GlobalBusinessExceptionHandler.java new file mode 100644 index 0000000..652e479 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/handler/GlobalBusinessExceptionHandler.java @@ -0,0 +1,252 @@ +package com.njcn.product.event.transientes.handler; + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.StrUtil; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.common.utils.LogUtil; +import com.njcn.web.utils.HttpResultUtil; +import com.njcn.web.utils.ReflectCommonUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.json.JSONException; +import org.springframework.validation.ObjectError; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.util.NestedServletException; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 全局通用业务异常处理器 + * + * @author hongawen + * @version 1.0.0 + * @date 2021年04月20日 18:04 + */ +@Slf4j +@AllArgsConstructor +@RestControllerAdvice +public class GlobalBusinessExceptionHandler { + + + + private final ThreadPoolExecutor executor = new ThreadPoolExecutor( + 4, 8, 30, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(100), + // 队列满时由主线程执行 + new ThreadPoolExecutor.CallerRunsPolicy() + ); + + + /** + * 捕获业务功能异常,通常为业务数据抛出的异常 + * + * @param businessException 业务异常 + */ + @ExceptionHandler(BusinessException.class) + public HttpResult handleBusinessException(BusinessException businessException) { + String operate = ReflectCommonUtil.getMethodDescribeByException(businessException); + // recodeBusinessExceptionLog(businessException, businessException.getMessage()); + return HttpResultUtil.assembleBusinessExceptionResult(businessException, null, operate); + } + + + /** + * 空指针异常捕捉 + * + * @param nullPointerException 空指针异常 + */ + @ExceptionHandler(NullPointerException.class) + public HttpResult handleNullPointerException(NullPointerException nullPointerException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.NULL_POINTER_EXCEPTION.getMessage(), nullPointerException); + //recodeBusinessExceptionLog(nullPointerException, CommonResponseEnum.NULL_POINTER_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.NULL_POINTER_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(nullPointerException)); + } + + /** + * 算数运算异常 + * + * @param arithmeticException 算数运算异常,由于除数为0引起的异常 + */ + @ExceptionHandler(ArithmeticException.class) + public HttpResult handleArithmeticException(ArithmeticException arithmeticException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.ARITHMETIC_EXCEPTION.getMessage(), arithmeticException); + // recodeBusinessExceptionLog(arithmeticException, CommonResponseEnum.ARITHMETIC_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.ARITHMETIC_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(arithmeticException)); + } + + /** + * 类型转换异常捕捉 + * + * @param classCastException 类型转换异常 + */ + @ExceptionHandler(ClassCastException.class) + public HttpResult handleClassCastException(ClassCastException classCastException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.CLASS_CAST_EXCEPTION.getMessage(), classCastException); + // recodeBusinessExceptionLog(classCastException, CommonResponseEnum.CLASS_CAST_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.CLASS_CAST_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(classCastException)); + } + + + /** + * 索引下标越界异常捕捉 + * + * @param indexOutOfBoundsException 索引下标越界异常 + */ + @ExceptionHandler(IndexOutOfBoundsException.class) + public HttpResult handleIndexOutOfBoundsException(IndexOutOfBoundsException indexOutOfBoundsException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION.getMessage(), indexOutOfBoundsException); + // recodeBusinessExceptionLog(indexOutOfBoundsException, CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(indexOutOfBoundsException)); + } + + /** + * 前端请求后端,请求中参数的媒体方式不支持异常 + * + * @param httpMediaTypeNotSupportedException 请求中参数的媒体方式不支持异常 + */ + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + public HttpResult httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException httpMediaTypeNotSupportedException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION.getMessage(), httpMediaTypeNotSupportedException); + // 然后提取错误提示信息进行返回 + // recodeBusinessExceptionLog(httpMediaTypeNotSupportedException, CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION, null, ReflectCommonUtil.getMethodDescribeByException(httpMediaTypeNotSupportedException)); + } + + /** + * 前端请求后端,参数校验异常捕捉 + * RequestBody注解参数异常 + * + * @param methodArgumentNotValidException 参数校验异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public HttpResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException methodArgumentNotValidException) { + // 从异常对象中拿到allErrors数据 + String messages = methodArgumentNotValidException.getBindingResult().getAllErrors() + .stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(";")); + // 然后提取错误提示信息进行返回 + LogUtil.njcnDebug(log, "参数校验异常,异常为:{}", messages); + // recodeBusinessExceptionLog(methodArgumentNotValidException, CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages, ControllerUtil.getMethodArgumentNotValidException(methodArgumentNotValidException)); + } + + /** + * 前端请求后端,参数校验异常捕捉 + * PathVariable注解、RequestParam注解参数异常 + * + * @param constraintViolationException 参数校验异常 + */ + @ExceptionHandler(ConstraintViolationException.class) + public HttpResult constraintViolationExceptionExceptionHandler(ConstraintViolationException constraintViolationException) { + String exceptionMessage = constraintViolationException.getMessage(); + StringBuilder messages = new StringBuilder(); + if (exceptionMessage.indexOf(StrUtil.COMMA) > 0) { + String[] tempMessage = exceptionMessage.split(StrUtil.COMMA); + Stream.of(tempMessage).forEach(message -> { + messages.append(message.substring(message.indexOf(StrUtil.COLON) + 2)).append(';'); + }); + } else { + messages.append(exceptionMessage.substring(exceptionMessage.indexOf(StrUtil.COLON) + 2)); + } + // 然后提取错误提示信息进行返回 + LogUtil.njcnDebug(log, "参数校验异常,异常为:{}", messages); + // recodeBusinessExceptionLog(constraintViolationException, CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION.getMessage()); + List> constraintViolationList = new ArrayList<>(constraintViolationException.getConstraintViolations()); + ConstraintViolation constraintViolation = constraintViolationList.get(0); + Class rootBeanClass = constraintViolation.getRootBeanClass(); + //判断校验参数异常捕获的根源是controller还是service处 + if (rootBeanClass.getName().endsWith("Controller")) { + String methodName = constraintViolation.getPropertyPath().toString().substring(0, constraintViolation.getPropertyPath().toString().indexOf(StrUtil.DOT)); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages.toString(), ReflectCommonUtil.getMethodDescribeByClassAndMethodName(rootBeanClass, methodName)); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages.toString(), ReflectCommonUtil.getMethodDescribeByException(constraintViolationException)); + } + + } + + + /** + * 索引下标越界异常捕捉 + * + * @param illegalArgumentException 参数校验异常 + */ + @ExceptionHandler(IllegalArgumentException.class) + public HttpResult handleIndexOutOfBoundsException(IllegalArgumentException illegalArgumentException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION.getMessage(), illegalArgumentException); + // recodeBusinessExceptionLog(illegalArgumentException, CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION, illegalArgumentException.getMessage(), ReflectCommonUtil.getMethodDescribeByException(illegalArgumentException)); + } + + + /** + * 未声明异常捕捉 + * + * @param exception 未声明异常 + */ + @ExceptionHandler(Exception.class) + public HttpResult handleException(Exception exception) { + //针对fallbackFactory降级异常特殊处理 + Exception tempException = exception; + String exceptionCause = CommonResponseEnum.UN_DECLARE.getMessage(); + String code = CommonResponseEnum.UN_DECLARE.getCode(); + if (exception instanceof NestedServletException) { + Throwable cause = exception.getCause(); + if (cause instanceof AssertionError) { + if (cause.getCause() instanceof BusinessException) { + tempException = (BusinessException) cause.getCause(); + BusinessException tempBusinessException = (BusinessException) cause.getCause(); + exceptionCause = tempBusinessException.getMessage(); + code = tempBusinessException.getCode(); + } + } + } + LogUtil.logExceptionStackInfo(exceptionCause, tempException); + // recodeBusinessExceptionLog(exception, exceptionCause); + //判断方法上是否有自定义注解,做特殊处理 +// Method method = ReflectCommonUtil.getMethod(exception); +// if (!Objects.isNull(method)){ +// if(method.isAnnotationPresent(ReturnMsg.class)){ +// return HttpResultUtil.assembleResult(code, null, StrFormatter.format("{}",exceptionCause)); +// } +// } + return HttpResultUtil.assembleResult(code, null, StrFormatter.format("{}{}{}", ReflectCommonUtil.getMethodDescribeByException(tempException), StrUtil.C_COMMA, exceptionCause)); + } + + + /** + * json解析异常 + * + * @param jsonException json参数 + */ + @ExceptionHandler(JSONException.class) + public HttpResult handleIndexOutOfBoundsException(JSONException jsonException) { + LogUtil.logExceptionStackInfo(CommonResponseEnum.JSON_CONVERT_EXCEPTION.getMessage(), jsonException); + // recodeBusinessExceptionLog(jsonException, CommonResponseEnum.JSON_CONVERT_EXCEPTION.getMessage()); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.JSON_CONVERT_EXCEPTION, jsonException.getMessage(), ReflectCommonUtil.getMethodDescribeByException(jsonException)); + } +/* + private void recodeBusinessExceptionLog(Exception businessException, String methodDescribe) { + HttpServletRequest httpServletRequest = HttpServletUtil.getRequest(); + Future future = executor.submit(() -> { + HttpServletUtil.setRequest(httpServletRequest); + sysLogAuditService.recodeBusinessExceptionLog(businessException, methodDescribe); + }); + try { + // 抛出 ExecutionException + future.get(); + } catch (ExecutionException | InterruptedException e) { + log.error("保存审计日志异常,异常为:" + e.getMessage()); + } + }*/ + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/handler/SqlExecuteTimeInterceptor.java b/event_smart/src/main/java/com/njcn/product/event/transientes/handler/SqlExecuteTimeInterceptor.java new file mode 100644 index 0000000..6e334dc --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/handler/SqlExecuteTimeInterceptor.java @@ -0,0 +1,57 @@ +//package com.njcn.gather.event.transientes.handler; +// +//import org.apache.ibatis.executor.statement.StatementHandler; +//import org.apache.ibatis.plugin.*; +//import org.apache.ibatis.session.ResultHandler; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.springframework.stereotype.Component; +// +//import java.sql.Statement; +//import java.util.Properties; +// +///** +// * @Author: cdf +// * @CreateTime: 2025-07-14 +// * @Description: +// */ +//@Intercepts({ +// @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), +// @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), +// @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}) +//}) +//@Component +//public class SqlExecuteTimeInterceptor implements Interceptor { +// private static final Logger logger = LoggerFactory.getLogger(SqlExecuteTimeInterceptor.class); +// +// @Override +// public Object intercept(Invocation invocation) throws Throwable { +// long startTime = System.currentTimeMillis(); +// try { +// return invocation.proceed(); +// } finally { +// long endTime = System.currentTimeMillis(); +// long executeTime = endTime - startTime; +// +// // 获取 SQL 语句 +// StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); +// String sql = statementHandler.getBoundSql().getSql(); +// +// // 打印执行时间和 SQL +// logger.info("SQL 执行时间: {}ms, SQL: {}", executeTime, sql); +// } +// } +// +// @Override +// public Object plugin(Object target) { +// if (target instanceof StatementHandler) { +// return Plugin.wrap(target, this); +// } +// return target; +// } +// +// @Override +// public void setProperties(Properties properties) { +// // 可配置参数 +// } +//} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MessageEventFeedbackMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MessageEventFeedbackMapper.java new file mode 100644 index 0000000..886ab4d --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MessageEventFeedbackMapper.java @@ -0,0 +1,13 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.MessageEventFeedback; + +/** + * @Author: cdf + * @CreateTime: 2025-06-26 + * @Description: + */ + +public interface MessageEventFeedbackMapper extends BaseMapper { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MsgEventConfigMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MsgEventConfigMapper.java new file mode 100644 index 0000000..c2d55dd --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MsgEventConfigMapper.java @@ -0,0 +1,12 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.MsgEventConfig; +import org.apache.ibatis.annotations.Mapper; + +/** + * MSG_EVENT_CONFIG表Mapper接口 + */ +@Mapper +public interface MsgEventConfigMapper extends BaseMapper { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MsgEventInfoMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MsgEventInfoMapper.java new file mode 100644 index 0000000..b808df1 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/MsgEventInfoMapper.java @@ -0,0 +1,9 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.MsgEventInfo; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface MsgEventInfoMapper extends BaseMapper { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqDevicedetailMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqDevicedetailMapper.java new file mode 100644 index 0000000..b64dcc0 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqDevicedetailMapper.java @@ -0,0 +1,15 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.devcie.pojo.po.PqDeviceDetail; + +/** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqDevicedetailMapper extends BaseMapper { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqUserLedgerMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqUserLedgerMapper.java new file mode 100644 index 0000000..228dae5 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqUserLedgerMapper.java @@ -0,0 +1,14 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.PqUserLedgerPO; +import com.njcn.product.event.transientes.pojo.po.PqUserLineAssPO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface PqUserLedgerMapper extends BaseMapper { + + + List getUserByParam(@Param("lineIds") List lineIds, @Param("searchValue")String searchValue); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqUserLineAssMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqUserLineAssMapper.java new file mode 100644 index 0000000..00db368 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqUserLineAssMapper.java @@ -0,0 +1,9 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.PqUserLineAssPO; + +public interface PqUserLineAssMapper extends BaseMapper { + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDeptsMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDeptsMapper.java new file mode 100644 index 0000000..049dc5f --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDeptsMapper.java @@ -0,0 +1,22 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.devcie.pojo.dto.PqsDeptDTO; +import com.njcn.product.event.transientes.pojo.po.PqsDepts; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 3:57【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsDeptsMapper extends BaseMapper { + List findDeptAndChildren(@Param("deptId") String deptId); + + List getDeptList(@Param("deptIds") List deptIds); +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDeptslineMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDeptslineMapper.java new file mode 100644 index 0000000..b3877a7 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDeptslineMapper.java @@ -0,0 +1,20 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.devcie.pojo.po.PqsDeptsline; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 3:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsDeptslineMapper extends BaseMapper { + + List getPhoneUser(@Param("lineId")String lineId); +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicDataMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicDataMapper.java new file mode 100644 index 0000000..24d2d10 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicDataMapper.java @@ -0,0 +1,9 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.PqsDicData; + +public interface PqsDicDataMapper extends BaseMapper { + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicTreeMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicTreeMapper.java new file mode 100644 index 0000000..63bdd22 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicTreeMapper.java @@ -0,0 +1,16 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.PqsDicTreePO; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +public interface PqsDicTreeMapper extends BaseMapper { + + @Select("SELECT ID,NAME,CODE,PARENT_ID as parentId,level FROM PQS_DICTREE " + + "START WITH CODE = #{code} " + + "CONNECT BY PRIOR ID = PARENT_ID") + List selectChildrenByCode(@Param("code") String code); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicTypeMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicTypeMapper.java new file mode 100644 index 0000000..178bc66 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsDicTypeMapper.java @@ -0,0 +1,10 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.PqsDicData; +import com.njcn.product.event.transientes.pojo.po.PqsDicType; + +public interface PqsDicTypeMapper extends BaseMapper { + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsEventdetailMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsEventdetailMapper.java new file mode 100644 index 0000000..1d398f6 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsEventdetailMapper.java @@ -0,0 +1,15 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.PqsEventdetail; + +/** + * + * Description: + * Date: 2025/06/20 上午 10:06【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsEventdetailMapper extends BaseMapper { +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsIntegrityMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsIntegrityMapper.java new file mode 100644 index 0000000..236a350 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsIntegrityMapper.java @@ -0,0 +1,15 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.PqsIntegrity; + +/** + * + * Description: + * Date: 2025/07/29 下午 6:40【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsIntegrityMapper extends BaseMapper { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsOnlinerateMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsOnlinerateMapper.java new file mode 100644 index 0000000..410fdfa --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsOnlinerateMapper.java @@ -0,0 +1,15 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.PqsOnlinerate; + +/** + * + * Description: + * Date: 2025/07/29 下午 6:40【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsOnlinerateMapper extends BaseMapper { +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsUserMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsUserMapper.java new file mode 100644 index 0000000..4145355 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsUserMapper.java @@ -0,0 +1,9 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.PqsUser; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PqsUserMapper extends BaseMapper { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsUserSetMapper.java b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsUserSetMapper.java new file mode 100644 index 0000000..f4b7a6a --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/PqsUserSetMapper.java @@ -0,0 +1,13 @@ +package com.njcn.product.event.transientes.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.product.event.transientes.pojo.po.PqsUserSet; + +/** + * @Author: cdf + * @CreateTime: 2025-06-24 + * @Description: + */ +public interface PqsUserSetMapper extends BaseMapper { + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqDevicedetailMapper.xml b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqDevicedetailMapper.xml new file mode 100644 index 0000000..babd49a --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqDevicedetailMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + DEV_INDEX, MANUFACTURER, CHECKFLAG, THISTIMECHECK, NEXTTIMECHECK, ONLINERATETJ, DATAPLAN, + NEWTRAFFIC, ELECTROPLATE, ONTIME, CONTRACT, SIM, DEV_CATENA, DEV_LOCATION, DEV_NO + + diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqUserLedger.xml b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqUserLedger.xml new file mode 100644 index 0000000..2643b63 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqUserLedger.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsDeptsMapper.xml b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsDeptsMapper.xml new file mode 100644 index 0000000..cf3a642 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsDeptsMapper.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + DEPTS_INDEX, DEPTSNAME, DEPTS_DESC, USER_INDEX, UPDATETIME, DEPTS_DESCRIPTION, "STATE", + AREA, CUSTOM_DEPT, PARENTNODEID + + + + + + \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsDeptslineMapper.xml b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsDeptslineMapper.xml new file mode 100644 index 0000000..e3ffaae --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsDeptslineMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + DEPTS_INDEX, LINE_INDEX, SYSTYPE + + + + \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsEventdetailMapper.xml b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsEventdetailMapper.xml new file mode 100644 index 0000000..8871c1b --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsEventdetailMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + EVENTDETAIL_INDEX, LINEID, TIMEID, MS, "DESCRIBE", WAVETYPE, PERSISTTIME, EVENTVALUE, + EVENTREASON, EVENTTYPE, EVENTASS_INDEX, DQTIME, DEALTIME, DEALFLAG, NUM, FILEFLAG, + FIRSTTIME, FIRSTTYPE, FIRSTMS, WAVENAME, ENERGY, SEVERITY, LOOK_FLAG + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsOnlinerateMapper.xml b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsOnlinerateMapper.xml new file mode 100644 index 0000000..cc58789 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/mapper/mapping/PqsOnlinerateMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + TIMEID, DEV_INDEX, ONLINEMIN, OFFLINEMIN + + \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/DicTreeEnum.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/DicTreeEnum.java new file mode 100644 index 0000000..9195891 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/DicTreeEnum.java @@ -0,0 +1,24 @@ +package com.njcn.product.event.transientes.pojo; + +import lombok.Getter; + +@Getter +public enum DicTreeEnum { + + BJ_USER("BJ_USER","半导体及精密加工"), + OI_USER("OI_USER","其他敏感用户"), + OT_USER("OT_USER","其他干扰源用户"), + + + ; + + private final String code; + + private final String dicName; + + + DicTreeEnum(String code, String dicName) { + this.code = code; + this.dicName = dicName; + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/constant/RedisConstant.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/constant/RedisConstant.java new file mode 100644 index 0000000..abed817 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/constant/RedisConstant.java @@ -0,0 +1,13 @@ +package com.njcn.product.event.transientes.pojo.constant; + +/** + * @Author: cdf + * @CreateTime: 2025-07-30 + * @Description: + */ + +public class RedisConstant { + + public static final String REDIS_DEPT_INDEX ="LineCache:"; + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/enums/DicTypeEnum.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/enums/DicTypeEnum.java new file mode 100644 index 0000000..ec65d02 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/enums/DicTypeEnum.java @@ -0,0 +1,33 @@ +package com.njcn.product.event.transientes.pojo.enums; + +import lombok.Data; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + + +@Getter +@RequiredArgsConstructor +public enum DicTypeEnum { + + VOLTAGE(5,"电压等级") + + + + + ; + + + + + + + + + private final Integer number; + + private final String dicName; + + + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/LargeScreenCountParam.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/LargeScreenCountParam.java new file mode 100644 index 0000000..e08b6f6 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/LargeScreenCountParam.java @@ -0,0 +1,65 @@ +package com.njcn.product.event.transientes.pojo.param; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.njcn.web.pojo.param.BaseParam; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.time.LocalDate; +import java.util.List; + +/** + * Description: + * Date: 2025/06/19 下午 3:38【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class LargeScreenCountParam extends BaseParam { + @ApiModelProperty(name="deptId",value="部门id") + private String deptId; + @ApiModelProperty(name="type",value="类型(1年 2季度 3月份 4周 5日)") + private Integer type; + + @ApiModelProperty(name="eventtype",value="类型(0 暂降事件 1远程通知)") + private Integer eventtype; + + @ApiModelProperty(name="eventDeep",value="0.普通事件 1.严重事件 null.全部事件") + private Integer eventDeep; + + @ApiModelProperty(name="t通讯状态",value="0.离线 1.在线") + private String state; + + private Integer sendResult; + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate startTime; + @JsonFormat(pattern = "yyyy-MM-dd") + private LocalDate endTime; + + @ApiModelProperty(value = "字典树 对象大类") + private String bigObjType; + @ApiModelProperty(value = "字典树 对象大小") + private String smallObjType; + + private List eventIds; + + private Integer gdIndex; + + private Integer bdId; + + private String devName; + + private Float eventValueMin; + + private Float eventValueMax; + + private Float eventDurationMin; + + private Float eventDurationMax; + + @ApiModelProperty(value = "导出标识") + private Boolean exportFlag = false; + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MessageEventFeedbackParam.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MessageEventFeedbackParam.java new file mode 100644 index 0000000..9ab3fe4 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MessageEventFeedbackParam.java @@ -0,0 +1,38 @@ +package com.njcn.product.event.transientes.pojo.param; + + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDate; + +/** + * @Author: cdf + * @CreateTime: 2025-06-26 + * @Description: + */ +@Data +public class MessageEventFeedbackParam { + + @NotBlank(message = "暂降事件id不可为空") + private String eventIndex; + + @NotNull(message = "是否影响敏感用户不可为空") + @ApiModelProperty(value = " 0.否 1.是") + private Integer isSensitive; + + @ApiModelProperty(value = "方案") + private String influenceFactors; + + @DateTimeFormat(pattern = "yyyy-MM-dd") + @ApiModelProperty(value = "处理时间") + private LocalDate dealDate; + + @ApiModelProperty(value = "原因") + private String dealScheme; + + private String remark; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MonitorTerminalParam.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MonitorTerminalParam.java new file mode 100644 index 0000000..7d387b8 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MonitorTerminalParam.java @@ -0,0 +1,30 @@ +package com.njcn.product.event.transientes.pojo.param; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * <监测点波形入参> + * + * @author wr + * @createTime: 2023-03-23 + */ +@Data +public class MonitorTerminalParam { + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @NotBlank(message = "id不能为空") + private String id; + + @ApiModelProperty(value = "区分主配网(0:主网 1:配网)") + @NotNull(message = "区分类别不能为空") + private Integer type; + + @ApiModelProperty(value = "区分系统(0:pq 1:pms)") + @NotNull(message = "区分系统不能为空") + private Integer systemType; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MsgEventConfigParam.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MsgEventConfigParam.java new file mode 100644 index 0000000..f7f1e30 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/MsgEventConfigParam.java @@ -0,0 +1,49 @@ +package com.njcn.product.event.transientes.pojo.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-07-01 + * @Description: + */ +@Data +public class MsgEventConfigParam { + + + /** + * 主键ID + */ + private String id; + + /** + * 严重通知标识 + */ + @NotNull(message = "严重通知标识不可为空") + private Integer seriousNotice; + + /** + * 普通通知标识 + */ + @NotNull(message = "普通通知标识不可为空") + private Integer normalNotic; + + /** + * 语音类型 + */ + @NotNull(message = "语音类型不可为空") + private Integer voiceType; + + /** + * 屏幕通知标识 + */ + @NotNull(message = "屏幕通知标识不可为空") + private Integer screenNotic; + + @NotBlank(message = "事件类型不可为空") + private List eventTypeList; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/PqUserLedgerParam.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/PqUserLedgerParam.java new file mode 100644 index 0000000..b540d01 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/PqUserLedgerParam.java @@ -0,0 +1,44 @@ +package com.njcn.product.event.transientes.pojo.param; + +import com.njcn.web.pojo.param.BaseParam; +import lombok.Data; + +/** + * @Author: cdf + * @CreateTime: 2025-07-28 + * @Description: + */ +@Data +public class PqUserLedgerParam extends BaseParam { + private static final long serialVersionUID = 1L; + + + private String id; + + private String powerSupplyArea; + + private String customerName; + + private String electricityAddress; + + private String industryType; + + private String voltageLevel; + + private String importantLevel; + + private String substationName; + + private String busbarName; + + private String operationUnit; + + private String manufacturer; + + private String bigObjType; + + private String smallObjType; + + private Integer isShow; + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/SimulationMsgParam.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/SimulationMsgParam.java new file mode 100644 index 0000000..6b6a27c --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/param/SimulationMsgParam.java @@ -0,0 +1,21 @@ +package com.njcn.product.event.transientes.pojo.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @Author: cdf + * @CreateTime: 2025-07-01 + * @Description: + */ +@Data +public class SimulationMsgParam { + + @NotBlank(message = "号码不可为空") + private String phone; + + @NotBlank(message = "短信内容不可为空") + private String msg; + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MessageEventFeedback.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MessageEventFeedback.java new file mode 100644 index 0000000..4a6570e --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MessageEventFeedback.java @@ -0,0 +1,35 @@ +package com.njcn.product.event.transientes.pojo.po; + +/** + * @Author: cdf + * @CreateTime: 2025-06-26 + * @Description: 暂降远程通知反馈 + */ +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +@Data +@TableName("MSG_EVENT_FEEDBACK") +public class MessageEventFeedback { + + @TableId(type = IdType.INPUT) + private String id; + + private String eventIndex; + + private Integer isSensitive; + + private String influenceFactors; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date dealDate; + + private String dealScheme; + + private String remark; + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MsgEventConfig.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MsgEventConfig.java new file mode 100644 index 0000000..45b210f --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MsgEventConfig.java @@ -0,0 +1,84 @@ +package com.njcn.product.event.transientes.pojo.po; + +/** + * @Author: cdf + * @CreateTime: 2025-06-27 + * @Description: + */ +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.List; + +import lombok.Data; +import lombok.ToString; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * MSG_EVENT_CONFIG表实体类 + */ +@Data +@TableName("MSG_EVENT_CONFIG") +@ToString +public class MsgEventConfig implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId("ID") + private String id; + + /** + * 严重通知标识 + */ + @TableField("SERIOUS_NOTICE") + @NotNull(message = "严重通知标识不可为空") + private Integer seriousNotice; + + /** + * 普通通知标识 + */ + @TableField("NORMAL_NOTIC") + @NotNull(message = "普通通知标识不可为空") + private Integer normalNotic; + + /** + * 语音类型 + */ + @TableField("VOICE_TYPE") + @NotNull(message = "语音类型不可为空") + private Integer voiceType; + + /** + * 屏幕通知标识 + */ + @TableField("SCREEN_NOTIC") + @NotNull(message = "屏幕通知标识不可为空") + private Integer screenNotic; + + /** + * 暂降类型,以逗号隔开 + */ + private String eventType; + + /** + * 暂降残余电压告警阈值 + */ + private Float eventValue; + + /** + * 暂降持续时间告警阈值 + */ + private Integer eventDuration; + + + @NotEmpty(message = "事件类型不可为空") + @TableField(exist = false) + private List eventTypeList; + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MsgEventInfo.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MsgEventInfo.java new file mode 100644 index 0000000..088b701 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/MsgEventInfo.java @@ -0,0 +1,66 @@ +package com.njcn.product.event.transientes.pojo.po; + +/** + * @Author: cdf + * @CreateTime: 2025-06-25 + * @Description: + */ + +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 消息事件信息实体 + */ +@Data +@TableName("MSG_EVENT_INFO") +public class MsgEventInfo implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 消息索引 + */ + private String msgIndex; + + /** + * 用户ID + */ + private String userId; + + private String userName; + + /** + * 发送时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime sendTime; + + /** + * 消息内容 + */ + private String msgContent; + + /** + * 事件索引 + */ + private String eventIndex; + + /** + * 手机号 + */ + private String phone; + + /** + * 发送结果 + */ + private Integer sendResult; + + /** + * 是否反馈 + */ + private Integer isHandle; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqDevicedetailaaa.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqDevicedetailaaa.java new file mode 100644 index 0000000..5cff653 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqDevicedetailaaa.java @@ -0,0 +1,94 @@ +//package com.njcn.gather.event.transientes.pojo.po; +// +//import com.baomidou.mybatisplus.annotation.IdType; +//import com.baomidou.mybatisplus.annotation.TableField; +//import com.baomidou.mybatisplus.annotation.TableId; +//import com.baomidou.mybatisplus.annotation.TableName; +//import java.time.LocalDateTime; +//import lombok.Data; +//import lombok.NoArgsConstructor; +// +///** +// * +// * Description: +// * Date: 2025/06/19 下午 1:47【需求编号】 +// * +// * @author clam +// * @version V1.0.0 +// */ +///** +// * 靠靠靠? +// */ +//@Data +//@NoArgsConstructor +//@TableName(value = "PQ_DEVICEDETAIL") +//public class PqDevicedetail { +// /** +// * 靠靠 +// */ +// @TableId(value = "DEV_INDEX", type = IdType.INPUT) +// private Integer devIndex; +// +// /** +// * (靠PQS_Dicdata)靠靠縂uid +// */ +// @TableField(value = "MANUFACTURER") +// private String manufacturer; +// +// /** +// * 靠靠(0:靠 1:靠) +// */ +// @TableField(value = "CHECKFLAG") +// private Integer checkflag; +// +// /** +// * 靠靠靠 +// */ +// @TableField(value = "THISTIMECHECK") +// private LocalDateTime thistimecheck; +// +// /** +// * 靠靠靠(靠靠靠靠靠3靠靠靠靠靠靠靠) +// */ +// @TableField(value = "NEXTTIMECHECK") +// private LocalDateTime nexttimecheck; +// +// /** +// * 靠靠靠? +// */ +// @TableField(value = "ONLINERATETJ") +// private Integer onlineratetj; +// +// @TableField(value = "DATAPLAN") +// private Integer dataplan; +// +// @TableField(value = "NEWTRAFFIC") +// private Integer newtraffic; +// +// @TableField(value = "ELECTROPLATE") +// private Integer electroplate; +// +// @TableField(value = "ONTIME") +// private Integer ontime; +// +// /** +// * 合同 +// */ +// @TableField(value = "CONTRACT") +// private String contract; +// +// /** +// * sim卡号 +// */ +// @TableField(value = "SIM") +// private String sim; +// +// @TableField(value = "DEV_CATENA") +// private String devCatena; +// +// @TableField(value = "DEV_LOCATION") +// private String devLocation; +// +// @TableField(value = "DEV_NO") +// private String devNo; +//} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqUserLedgerPO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqUserLedgerPO.java new file mode 100644 index 0000000..63a3076 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqUserLedgerPO.java @@ -0,0 +1,108 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-07-28 + * @Description: + */ +@Data +@TableName(value = "pq_user_ledger") +public class PqUserLedgerPO implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId + @TableField(value = "id") + private String id; + + @TableField(value = "POWER_SUPPLY_AREA") + private String powerSupplyArea; + + @TableField(value = "CUSTOMER_NAME") + private String customerName; + + @TableField(value = "ELECTRICITY_ADDRESS") + private String electricityAddress; + + @TableField(value = "INDUSTRY_TYPE") + private String industryType; + + @TableField(value = "VOLTAGE_LEVEL") + private String voltageLevel; + + @TableField(value = "IMPORTANT_LEVEL") + private String importantLevel; + + @TableField(value = "SUBSTATION_NAME") + private String substationName; + + @TableField(value = "BUSBAR_NAME") + private String busbarName; + + @TableField(value = "OPERATION_UNIT") + private String operationUnit; + + @TableField(value = "MANUFACTURER") + private String manufacturer; + + @TableField(value = "BIG_OBJ_TYPE") + private String bigObjType; + + @TableField(value = "SMALL_OBJ_TYPE") + private String smallObjType; + + /** + * 设备或对象的分类小类 + */ + @TableField(value = "CREATE_BY") + private String createBy; + + @TableField(value = "UPDATE_BY") + private String updateBy; + + + /** + * 创建时间(自动填充) + */ + @TableField(value = "CREATE_TIME") + private LocalDateTime createTime; + + /** + * 更新时间(自动填充) + */ + @TableField(value = "UPDATE_TIME") + private LocalDateTime updateTime; + + @TableField(value = "IS_SHOW") + private Integer isShow; + + @TableField(exist = false) + private Integer eventCount = 0; + + @TableField(exist = false) + private List eventIds; + + @TableField(exist = false) + private String deptName; + + @TableField(exist = false) + private String gdName; + + @TableField(exist = false) + private String station; + + @TableField(exist = false) + private String info; + + @TableField(exist = false) + private List eventList; + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqUserLineAssPO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqUserLineAssPO.java new file mode 100644 index 0000000..2237144 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqUserLineAssPO.java @@ -0,0 +1,25 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @Author: cdf + * @CreateTime: 2025-07-28 + * @Description: + */ +@Data +@TableName(value = "pq_user_line_ass") +public class PqUserLineAssPO { + + @TableField(value = "USER_INDEX") + private String userIndex; + + @TableField(value = "LINE_INDEX") + private Integer lineIndex; + + + @TableField(exist = false) + private String userName; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDepts.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDepts.java new file mode 100644 index 0000000..0b991d7 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDepts.java @@ -0,0 +1,79 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/06/19 下午 3:57【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +/** + * 部门表 + */ +@Data +@NoArgsConstructor +@TableName(value = "PQS_DEPTS") +public class PqsDepts { + /** + * 部门表Guid + */ + @TableId(value = "DEPTS_INDEX", type = IdType.INPUT) + private String deptsIndex; + + /** + * 部门名称 + */ + @TableField(value = "DEPTSNAME") + private String deptsname; + + /** + * 排序 + */ + @TableField(value = "DEPTS_DESC") + private Integer deptsDesc; + + /** + * (关联表PQS_User)用户表Guid + */ + @TableField(value = "USER_INDEX") + private String userIndex; + + /** + * 更新时间 + */ + @TableField(value = "UPDATETIME") + private LocalDateTime updatetime; + + /** + * 部门描述 + */ + @TableField(value = "DEPTS_DESCRIPTION") + private String deptsDescription; + + /** + * 角色状态0:删除;1:正常; + */ + @TableField(value = "\"STATE\"") + private Integer state; + + /** + * 行政区域 + */ + @TableField(value = "AREA") + private String area; + + @TableField(value = "CUSTOM_DEPT") + private Integer customDept; + + @TableField(value = "PARENTNODEID") + private String parentnodeid; +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicData.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicData.java new file mode 100644 index 0000000..e92f1b0 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicData.java @@ -0,0 +1,49 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * CN_Gather + * + * @author cdf + * @date 2025/8/12 + */ +@TableName(value = "PQS_DICDATA") +@Data +public class PqsDicData { + + @TableId + @TableField(value = "DIC_INDEX") + private String dicIndex; + + @TableField(value = "DIC_NAME") + private String dicName; + + @TableField(value = "DIC_TYPE") + private String dicType; + + @TableField(value = "DIC_NUMBER") + private Integer dicNumber; + + @TableField(value = "UPDATETIME") + private Date updateTime; + + @TableField(value = "USER_INDEX") + private String userIndex; + + //事件等级 + @TableField(value = "DIC_LEAVE") + private Integer dicLeave; + + @TableField(value = "STATE") + private Integer state; + @TableField(value = "TRIPHASE") + private Integer triphase; + + @TableField(value = "BACK_UP") + private String backUp;} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicTreePO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicTreePO.java new file mode 100644 index 0000000..1c3b0b5 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicTreePO.java @@ -0,0 +1,48 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * @Author: cdf + * @CreateTime: 2025-07-28 + * @Description: + */ +@Data +@TableName(value = "PQS_DICTREE") +public class PqsDicTreePO { + @TableId // 标记主键字段 + @TableField(value ="ID") // 显式指定列名(默认按字段名映射,可省略) + private String id; + + @TableField(value ="NAME") + private String name; + + @TableField(value ="CODE") + private String code; + + @TableField(value ="PARENT_ID") + private String parentId; + + @TableField(value ="DIC_VALUE") + private String dicValue; + + @TableField(value ="CREATE_BY") + private String createBy; + + @TableField(value ="CREATE_TIME") + private Date createTime; + + @TableField(value ="UPDATE_BY") + private String updateBy; + + @TableField(value ="UPDATE_TIME") + private Date updateTime; + + @TableField(exist = false) + private Integer level; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicType.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicType.java new file mode 100644 index 0000000..1cdc3f4 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsDicType.java @@ -0,0 +1,39 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * @Author: cdf + * @CreateTime: 2025-09-24 + * @Description: + */ +@TableName(value="PQS_DICTYPE") +@Data +public class PqsDicType { + + @TableId(value = "DICTYPE_INDEX") + private String dicTypeIndex; + + @TableField(value = "DICTYPE_NAME") + private String dicTypeName; + + @TableField(value = "DICTYPE_NUMBER") + private Integer dicTypeNumber; + + @TableField(value = "DICTYPE_DESCRIBE") + private String dicTypeDiscribe; + + @TableField(value = "USER_INDEX") + private String userIndex; + + @TableField(value = "UPDATETIME") + private Date updateTime; + + @TableField(value = "STATE") + private Integer state; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsEventdetail.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsEventdetail.java new file mode 100644 index 0000000..eab2338 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsEventdetail.java @@ -0,0 +1,107 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/06/20 上午 10:06【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +@TableName(value = "PQS_EVENTDETAIL") +public class PqsEventdetail { + @TableId(value = "EVENTDETAIL_INDEX", type = IdType.INPUT) + private String eventdetailIndex; + + @TableField(value = "LINEID") + private Integer lineid; + + @TableField(value = "TIMEID") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timeid; + + @TableField(value = "MS") + private BigDecimal ms; + + @TableField(value = "\"DESCRIBE\"") + private String describe; + + @TableField(value = "WAVETYPE") + private Integer wavetype; + + @TableField(value = "PERSISTTIME") + private Double persisttime; + + @TableField(value = "EVENTVALUE") + private Double eventvalue; + + @TableField(value = "EVENTREASON") + private String eventreason; + + @TableField(value = "EVENTTYPE") + private String eventtype; + + @TableField(value = "EVENTASS_INDEX") + private String eventassIndex; + + @TableField(value = "DQTIME") + private Double dqtime; + + @TableField(value = "DEALTIME") + private LocalDateTime dealtime; + + @TableField(value = "DEALFLAG") + private Integer dealflag; + + @TableField(value = "NUM") + private BigDecimal num; + + @TableField(value = "FILEFLAG") + private Integer fileflag; + + @TableField(value = "FIRSTTIME") + private LocalDateTime firsttime; + + @TableField(value = "FIRSTTYPE") + private String firsttype; + + @TableField(value = "FIRSTMS") + private BigDecimal firstms; + + @TableField(value = "WAVENAME") + private String wavename; + + @TableField(value = "ENERGY") + private Double energy; + + @TableField(value = "SEVERITY") + private Double severity; + + @TableField(value = "LOOK_FLAG") + private Integer lookFlag; + + @TableField(value = "NOTICE_FLAG") + private Integer noticeFlag; + + @TableField(exist = false) + private Integer eventSeverity; + + @TableField(exist = false) + private String stationName; + + @TableField(exist = false) + private String busBarName; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsIntegrity.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsIntegrity.java new file mode 100644 index 0000000..f6db708 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsIntegrity.java @@ -0,0 +1,34 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDate; + +/** + * CN_Gather + * + * @author cdf + * @date 2025/8/9 + */ + + +@TableName(value="PQS_INTEGRITY") +@Data +public class PqsIntegrity { + + @TableField(value="TIMEID") + private LocalDate timeID; + + @TableField(value="Line_index") + private Integer lineIndex; + + @TableField(value="due") + private Integer due; + + @TableField(value="real") + private Integer real; + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsOnlinerate.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsOnlinerate.java new file mode 100644 index 0000000..0a3f419 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsOnlinerate.java @@ -0,0 +1,32 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import java.time.LocalDateTime; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + * Description: + * Date: 2025/07/29 下午 6:40【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +@NoArgsConstructor +@TableName(value = "PQS_ONLINERATE") +public class PqsOnlinerate { + @TableField(value = "TIMEID" ) + private LocalDateTime timeid; + + @TableField(value = "DEV_INDEX") + private Integer devIndex; + + @TableField(value = "ONLINEMIN") + private Integer onlinemin; + + @TableField(value = "OFFLINEMIN") + private Integer offlinemin; +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsUser.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsUser.java new file mode 100644 index 0000000..c43e6ac --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsUser.java @@ -0,0 +1,58 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.util.Date; + +/** + * @Author: cdf + * @CreateTime: 2025-06-26 + * @Description: + */ +@Data +@TableName("PQS_USER") +public class PqsUser { + + @TableId(type = IdType.INPUT) + private String userIndex; + + private String name; + + private String loginname; + + private String password; + + private String phone; + + private String email; + + @TableField(fill = FieldFill.INSERT) + private Date registertime; + + private Date psdvalidity; + + private Date logintime; + + private Integer state; + + private Integer mark; + + private String limitIpstart; + + private String limitIpend; + + private String limitTime; + + private Integer loginErrorTimes; + + @TableField("CASUAL_USER") + private Integer casualUser; + + private Date firsterrorTime; + + + private Date lockTime; + + private String referralCode; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsUserSet.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsUserSet.java new file mode 100644 index 0000000..8137326 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/po/PqsUserSet.java @@ -0,0 +1,49 @@ +package com.njcn.product.event.transientes.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: cdf + * @CreateTime: 2025-06-24 + * @Description: + */ +@Data +@TableName("PQS_USERSET") +public class PqsUserSet implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 用户设置索引 + */ + @TableId("USERSET_INDEX") + private String usersetIndex; + + /** + * 用户索引 + */ + @TableField("USER_INDEX") + private String userIndex; + + /** + * 是否通知(0-否,1-是) + */ + @TableField("ISNOTICE") + private Integer isNotice; + + /** + * 角色组索引 + */ + @TableField("ROLEGP_INDEX") + private String roleGpIndex; + + /** + * 部门索引 + */ + @TableField("DEPTS_INDEX") + private String deptsIndex; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/AlarmAnalysisVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/AlarmAnalysisVO.java new file mode 100644 index 0000000..bfe528b --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/AlarmAnalysisVO.java @@ -0,0 +1,41 @@ +package com.njcn.product.event.transientes.pojo.vo; + +import com.njcn.product.event.transientes.pojo.po.MsgEventInfo; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * Description: + * Date: 2025/06/20 上午 9:29【需求编号】 + * + * @author clam + * @version V1.0.0 + */ + +@Data +public class AlarmAnalysisVO { + @ApiModelProperty(name="eventCount",value="暂降次数") + private Integer eventCount; + @ApiModelProperty(name="aLarmCount",value="告警事件统计") + private Integer aLarmCount; + @ApiModelProperty(name="warnCount",value="预警事件统计") + private Integer warnCount; + @ApiModelProperty(name="noticeCount",value="远程通知统计") + private Integer noticeCount; + @ApiModelProperty(name="lookALarmCount",value="告警事件处置数") + private Integer lookALarmCount; + @ApiModelProperty(name="lookWarnCount",value="预警事件处置数") + private Integer lookWarnCount; + @ApiModelProperty(name="lookNoticeCount",value="远程通知处置数") + private Integer lookNoticeCount; + + List eventdetails; + List aLarmEvent; + List warnEvent; + List noticeEvent; + List lookALarmEvent; + List lookWarnEvent; + List lookNoticeEvent; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/DeviceCountVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/DeviceCountVO.java new file mode 100644 index 0000000..d3cdc32 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/DeviceCountVO.java @@ -0,0 +1,18 @@ +package com.njcn.product.event.transientes.pojo.vo; + +import lombok.Data; + +/** + * Description: + * Date: 2025/07/28 上午 8:50【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class DeviceCountVO { + private Integer allCount; + private Integer onLine; + private Integer offLine; + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventDetailVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventDetailVO.java new file mode 100644 index 0000000..3b622c0 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventDetailVO.java @@ -0,0 +1,52 @@ +package com.njcn.product.event.transientes.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * Description: + * Date: 2025/06/20 下午 2:50【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class EventDetailVO { + + private String eventdetail_index; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timeid; + + private BigDecimal ms; + + private String wavetype; + + private Double eventvalue; + + private Integer lookFlag; + + private Integer noticeFlag; + + private Integer lineid; + + private String pointname; + private String gdName; + private String busName; + private String devName; + + private String persisttime; + + + private String bdname; + + private String objName; + + private Integer needDealFlag; + private long msgEventInfoSize; + //1告警,2预警 + private Integer eventSeverity; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventMsgDetailVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventMsgDetailVO.java new file mode 100644 index 0000000..d77e2c4 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventMsgDetailVO.java @@ -0,0 +1,35 @@ +package com.njcn.product.event.transientes.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.njcn.product.event.transientes.pojo.po.MsgEventInfo; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-06-26 + * @Description: 暂降远程通知详情 + */ +@Data +public class EventMsgDetailVO { + + private String eventIndex; + + private Integer isSensitive; + + private String influenceFactors; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date dealDate; + + private String dealScheme; + + private String remark; + + private String objName; + + private List msgList; + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventTrendVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventTrendVO.java new file mode 100644 index 0000000..6ca36a9 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/EventTrendVO.java @@ -0,0 +1,18 @@ +package com.njcn.product.event.transientes.pojo.vo; + +import lombok.Data; + +import java.time.LocalDate; + +/** + * Description: + * Date: 2025/06/20 上午 11:33【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class EventTrendVO { + private LocalDate localDate; + private Integer eventCount; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/LedgerCountVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/LedgerCountVO.java new file mode 100644 index 0000000..e0e9665 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/LedgerCountVO.java @@ -0,0 +1,31 @@ +package com.njcn.product.event.transientes.pojo.vo; + +import com.njcn.product.event.devcie.pojo.dto.DeviceDTO; +import com.njcn.product.event.devcie.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.event.devcie.pojo.dto.SubstationDTO; +import lombok.Data; + +import java.util.List; + +/** + * Description: + * Date: 2025/06/19 下午 3:06【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class LedgerCountVO { + + private long allSubCount; + private long allDevCount; + private long allLineCount; + private long runDevCount; + private long runSubCount; + private long runLineCount; + + private List allSubList; + private List allDevList; + private List allLineList; + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/MapCountVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/MapCountVO.java new file mode 100644 index 0000000..a5e682b --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/MapCountVO.java @@ -0,0 +1,28 @@ +package com.njcn.product.event.transientes.pojo.vo; + +import com.njcn.product.event.devcie.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.event.transientes.pojo.po.MsgEventInfo; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * Description: + * Date: 2025/06/26 上午 8:50【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class MapCountVO { + private String deptsIndex; + private String deptsName; + private Integer lineCount; + private Integer eventCount; + private Integer noticeCount; + + private List lineList = new ArrayList<>(); + private List eventList = new ArrayList<>(); + private List noticeList = new ArrayList<>(); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/PqsDicTreeVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/PqsDicTreeVO.java new file mode 100644 index 0000000..ffdcb64 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/PqsDicTreeVO.java @@ -0,0 +1,36 @@ +package com.njcn.product.event.transientes.pojo.vo; + + +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-08-01 + * @Description: + */ +@Data +public class PqsDicTreeVO { + + private String id; + + private String name; + + private String code; + + private String parentId; + + private String dicValue; + + private String createBy; + + private Date createTime; + + private String updateBy; + + private Date updateTime; + + private List children; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/RegionDevCountVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/RegionDevCountVO.java new file mode 100644 index 0000000..4f35776 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/RegionDevCountVO.java @@ -0,0 +1,28 @@ +package com.njcn.product.event.transientes.pojo.vo; + +import lombok.Data; + +/** + * Description: + * Date: 2025/07/28 上午 10:26【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class RegionDevCountVO { + + /** + * 部门表Guid + */ + private String deptsIndex; + + /** + * 部门名称 + */ + private String deptsname; + private String areaName; + private Integer allCount; + private Integer onLine; + private Integer offLine; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/SubStationCountVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/SubStationCountVO.java new file mode 100644 index 0000000..29d86f0 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/SubStationCountVO.java @@ -0,0 +1,63 @@ +package com.njcn.product.event.transientes.pojo.vo; + +import com.njcn.product.event.devcie.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.event.transientes.pojo.po.PqsEventdetail; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * Description: + * Date: 2025/07/29 上午 11:03【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Data +public class SubStationCountVO { + private Integer stationId; + private String stationName; + private String gdName; + private double longitude; + private double latitude; + private Integer lineCount; + private Integer eventCount; + + private List lineEventDetails; + + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class LineEventDetail { + private String gdName; + private String gdIndex; + + private Integer lineId; + + private String lineName; + + private Integer busBarId; + + private String busBarName; + + private Integer devId; + + private String devName; + + private String objName; + + private Integer stationId; + + private String stationName; + //通讯状态 + private Integer runFlag=0; + + private Integer eventCount; + + private List pqsEventdetails; + } + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/UserLedgerStatisticVO.java b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/UserLedgerStatisticVO.java new file mode 100644 index 0000000..2f6f0ce --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/pojo/vo/UserLedgerStatisticVO.java @@ -0,0 +1,63 @@ +package com.njcn.product.event.transientes.pojo.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-07-28 + * @Description: 大屏右侧实体 + */ +@Data +public class UserLedgerStatisticVO { + + @ApiModelProperty(value = "半导体及精密加工大类id") + private String importId; + + private Integer importNum = 0; + + private Integer importDevNum = 0; + + @ApiModelProperty(value = "其他敏感用户大类id") + private String otherImportId; + + private Integer otherImportNum = 0; + + private Integer otherImportDevNum = 0; + + @ApiModelProperty(value = "其他干扰源大类id") + private String otherId; + + private Integer otherNum = 0; + + private Integer otherDevNum = 0; + + private List innerList = new ArrayList<>(); + + + + @Data + public static class Inner{ + + private String treeId; + + private String parentId; + + private String customId; + + private String name; + + private String code; + + private Integer count; + + private List eventList; + + private List children; + } + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/security/AuthController.java b/event_smart/src/main/java/com/njcn/product/event/transientes/security/AuthController.java new file mode 100644 index 0000000..e9a64e9 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/security/AuthController.java @@ -0,0 +1,102 @@ +package com.njcn.product.event.transientes.security; + +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.response.HttpResult; +import com.njcn.product.event.transientes.utils.JwtUtil; +import com.njcn.redis.utils.RedisUtil; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.constraints.NotBlank; + +@RestController +@Slf4j +@RequiredArgsConstructor +public class AuthController extends BaseController { + + private final String eventRedisKey = "event_smart_"; + + + private final AuthenticationManager authenticationManager; + + + private final JwtUtil jwtUtil; + + private final RedisUtil redisUtil; + + + + + + @PostMapping("/cn_authenticate") + @ApiOperation("登录认证") + public HttpResult createAuthenticationToken(@RequestBody @Validated AuthRequest authRequest) { + String methodDescribe = getMethodDescribe("createAuthenticationToken"); + //log.info("Authentication request - username: {}, password: {}",authRequest.getUsername(),authRequest.getPassword()); + try { + boolean hasFlag = redisUtil.hasKey(eventRedisKey+authRequest.getUsername()); + if(hasFlag){ + String pass = redisUtil.getRawValue(eventRedisKey+authRequest.getUsername()); + + // 执行认证,内部会调用 UserDetailsService 加载用户信息 + Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(),pass)); + + // 将认证信息存入 SecurityContext + SecurityContextHolder.getContext().setAuthentication(authentication); + + // 直接从 Authentication 对象中获取已加载的 UserDetails,避免重复查询 + MyUserDetails userDetails = (MyUserDetails) authentication.getPrincipal(); + + // 获取用户部门(假设 CustomUserDetails 包含部门信息) + String department = userDetails.getDeptId(); + + final String jwt = jwtUtil.generateToken(userDetails); + + AuthResponse authResponse = new AuthResponse(); + authResponse.setToken(jwt); + authResponse.setDeptId(department); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, authResponse, methodDescribe); + }else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } catch (Exception e) { + e.printStackTrace(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe); + } + } +} + +// 认证请求类 +class AuthRequest { + + @NotBlank(message = "用户名不可为空") + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/security/AuthResponse.java b/event_smart/src/main/java/com/njcn/product/event/transientes/security/AuthResponse.java new file mode 100644 index 0000000..ddf6230 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/security/AuthResponse.java @@ -0,0 +1,20 @@ +package com.njcn.product.event.transientes.security; + +import lombok.Data; + +/** + * @Author: cdf + * @CreateTime: 2025-06-26 + * @Description: + */ +@Data +public class AuthResponse { + + private String token; + + private String deptId; + + private String roleId; + + private String userIndex; +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/security/MyUserDetails.java b/event_smart/src/main/java/com/njcn/product/event/transientes/security/MyUserDetails.java new file mode 100644 index 0000000..471ab25 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/security/MyUserDetails.java @@ -0,0 +1,64 @@ +package com.njcn.product.event.transientes.security; + +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +/** + * @Author: cdf + * @CreateTime: 2025-06-26 + * @Description: + */ +@Data +public class MyUserDetails implements UserDetails { + + private String userId; // 用户唯一标识 + private String username; // 用户名 + private String password; // 密码 + private String deptId; // 部门信息 + private Collection authorities; // 权限集合 + private boolean accountNonExpired; // 账户是否未过期 + private boolean accountNonLocked; // 账户是否未锁定 + private boolean credentialsNonExpired; // 凭证是否未过期 + private boolean enabled; // 账户是否启用 + + public MyUserDetails(String userId,String username, String password, String deptId,Collection authorities) { + this.userId = userId; + this.username = username; + this.password = password; + this.deptId = deptId; + this.authorities = authorities; + } + + @Override + public String getPassword() { + return this.password; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} \ No newline at end of file diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/security/MyUserDetailsService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/security/MyUserDetailsService.java new file mode 100644 index 0000000..5915763 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/security/MyUserDetailsService.java @@ -0,0 +1,66 @@ +package com.njcn.product.event.transientes.security; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.njcn.product.event.transientes.mapper.PqsUserMapper; +import com.njcn.product.event.transientes.mapper.PqsUserSetMapper; +import com.njcn.product.event.transientes.pojo.po.PqsUser; +import com.njcn.product.event.transientes.pojo.po.PqsUserSet; +import com.njcn.redis.utils.RedisUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Objects; + +@Service +@RequiredArgsConstructor +public class MyUserDetailsService implements UserDetailsService { + + private final PqsUserMapper pqsUserMapper; + + private final PqsUserSetMapper pqsUserSetMapper; + + private final RedisUtil redisUtil; + + + + @Override + public MyUserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + + + if("system_event".equals(username)){ + return new MyUserDetails("12345678910","system_event", "@#001njcnpqs","10001", + new ArrayList<>()); + } + + + if(redisUtil.hasKey("event_smart_"+username)){ + String password = redisUtil.getRawValue("event_smart_"+username); + // 这里应该从数据库中获取用户信息,本示例使用硬编码用户 + PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + String encodedPassword = passwordEncoder.encode(password); + + LambdaQueryWrapper userWrapper = new LambdaQueryWrapper<>(); + userWrapper.eq(PqsUser::getLoginname,username); + PqsUser pqsUser = pqsUserMapper.selectOne(userWrapper); + if(Objects.isNull(pqsUser)){ + throw new UsernameNotFoundException("User not found with username: " + username); + } + + LambdaQueryWrapper userSetWrapper = new LambdaQueryWrapper<>(); + userSetWrapper.eq(PqsUserSet::getUserIndex,pqsUser.getUserIndex()); + PqsUserSet pqsUserSet = pqsUserSetMapper.selectOne(userSetWrapper); + String deptId = pqsUserSet.getDeptsIndex(); + + + return new MyUserDetails(pqsUser.getUserIndex(),pqsUser.getLoginname(), encodedPassword,deptId, + new ArrayList<>()); + }else { + throw new UsernameNotFoundException("User not found with username: " + username); + } + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/security/SecurityConfig.java b/event_smart/src/main/java/com/njcn/product/event/transientes/security/SecurityConfig.java new file mode 100644 index 0000000..1f1b5cb --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/security/SecurityConfig.java @@ -0,0 +1,57 @@ +package com.njcn.product.event.transientes.security; + +import com.njcn.product.event.transientes.filter.JwtRequestFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private UserDetailsService userDetailsService; + + @Autowired + private JwtRequestFilter jwtRequestFilter; + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable() + .authorizeRequests() + //.antMatchers("/cn_authenticate","/ws/**","/accept/testEvent","/accept/eventMsg").permitAll() // 允许访问认证接口 + .antMatchers("/**").permitAll() // 允许访问认证接口 + .anyRequest().authenticated() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 使用无状态会话 + + http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/CommGeneralService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/CommGeneralService.java new file mode 100644 index 0000000..aa557ae --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/CommGeneralService.java @@ -0,0 +1,52 @@ +package com.njcn.product.event.transientes.service; + +import cn.hutool.core.util.StrUtil; +import com.njcn.product.event.devcie.pojo.po.PqsDeptsline; +import com.njcn.product.event.devcie.service.PqsDeptslineService; +import com.njcn.product.event.transientes.pojo.param.LargeScreenCountParam; +import com.njcn.redis.utils.RedisUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +import static com.njcn.product.event.transientes.pojo.constant.RedisConstant.REDIS_DEPT_INDEX; + +/** + * @Author: cdf + * @CreateTime: 2025-06-25 + * @Description: + */ +@Service +@RequiredArgsConstructor +public class CommGeneralService { + @Value("${SYS_TYPE_ZT}") + private String sysTypeZt; + + private final PqsDeptslineService pqsDeptslineService; + private final PqsDeptsService pqsDeptsService; + private final RedisUtil redisUtil; + + /** + * 根据部门id获取部门所拥有的监测点 + * @param largeScreenCountParam + * @return + */ + public List getLineIdsByDept(LargeScreenCountParam largeScreenCountParam){ + List deptAndChildren = pqsDeptsService.findDeptAndChildren(largeScreenCountParam.getDeptId()); + List deptslines = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getDeptsIndex, deptAndChildren).eq(PqsDeptsline::getSystype, sysTypeZt).list(); + List deptslineIds = deptslines.stream().map(PqsDeptsline::getLineIndex).collect(Collectors.toList()); + return deptslineIds; + + } + + + public List getLineIdsByRedis(String deptId){ + List deptLineIds = (List) redisUtil.getObjectByKey( REDIS_DEPT_INDEX+ StrUtil.DASHED+deptId); + return deptLineIds; + } + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/EventGateService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/EventGateService.java new file mode 100644 index 0000000..7fca510 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/EventGateService.java @@ -0,0 +1,15 @@ +package com.njcn.product.event.transientes.service; + +import com.njcn.event.file.pojo.dto.WaveDataDTO; +import com.njcn.product.event.transientes.pojo.param.MonitorTerminalParam; + +public interface EventGateService { + + + /** + * 功能描述: 暂态事件波形分析 + * @param param + * @return + */ + WaveDataDTO getTransientAnalyseWave(MonitorTerminalParam param); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/EventRightService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/EventRightService.java new file mode 100644 index 0000000..6edbef5 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/EventRightService.java @@ -0,0 +1,48 @@ +package com.njcn.product.event.transientes.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.event.devcie.pojo.po.PqGdCompany; +import com.njcn.product.event.devcie.pojo.po.PqSubstation; +import com.njcn.product.event.transientes.pojo.param.LargeScreenCountParam; +import com.njcn.product.event.transientes.pojo.po.PqUserLedgerPO; +import com.njcn.product.event.transientes.pojo.vo.EventDetailVO; +import com.njcn.product.event.transientes.pojo.vo.UserLedgerStatisticVO; + +import java.util.List; + +public interface EventRightService { + + + UserLedgerStatisticVO userLedgerStatistic(LargeScreenCountParam param); + + + Page rightEventOpen(LargeScreenCountParam param); + + Page rightEventOpenForDetail(LargeScreenCountParam param); + + + Page rightEventDevOpen(LargeScreenCountParam param); + + + + + List rightImportUser(LargeScreenCountParam param); + + + PqUserLedgerPO rightImportOpenDetail(LargeScreenCountParam param); + + + + List gdSelect(); + + List bdSelect(); + + + + + /*-------------------------------------------------------*/ + UserLedgerStatisticVO userLedgerStatisticClone(LargeScreenCountParam param); + + Page rightEventOpenClone(LargeScreenCountParam param); + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/LargeScreenCountService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/LargeScreenCountService.java new file mode 100644 index 0000000..7f3ed9b --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/LargeScreenCountService.java @@ -0,0 +1,64 @@ +package com.njcn.product.event.transientes.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.event.devcie.pojo.dto.DeviceDTO; +import com.njcn.product.event.transientes.pojo.param.LargeScreenCountParam; +import com.njcn.product.event.transientes.pojo.param.MessageEventFeedbackParam; +import com.njcn.product.event.transientes.pojo.po.MsgEventInfo; +import com.njcn.product.event.transientes.pojo.vo.*; + +import java.util.List; + +/** + * Description: + * Date: 2025/06/19 下午 3:05【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface LargeScreenCountService { + + void initLedger(LargeScreenCountParam largeScreenCountParam); + + LedgerCountVO scaleStatistics(LargeScreenCountParam largeScreenCountParam); + + AlarmAnalysisVO alarmAnalysis(LargeScreenCountParam largeScreenCountParam); + + List eventTrend(LargeScreenCountParam largeScreenCountParam); + + Page eventList(LargeScreenCountParam largeScreenCountParam); + + List noDealEventList(LargeScreenCountParam largeScreenCountParam); + + + boolean lookEvent(List ids); + + List mapCount(LargeScreenCountParam largeScreenCountParam); + + EventMsgDetailVO eventMsgDetail(String eventId); + + List msgSendList(LargeScreenCountParam largeScreenCountParam); + + Page hasSendMsgPage(LargeScreenCountParam largeScreenCountParam); + + boolean msgHandle(MessageEventFeedbackParam messageEventFeedbackParam); + + + AlarmAnalysisVO alarmAnalysisDetail(LargeScreenCountParam largeScreenCountParam); + + Page eventTablePage(LargeScreenCountParam largeScreenCountParam); + + DeviceCountVO devFlagCount(LargeScreenCountParam largeScreenCountParam); + + List devDetail(LargeScreenCountParam largeScreenCountParam); + + List regionDevCount(LargeScreenCountParam largeScreenCountParam); + + List substationCount(LargeScreenCountParam largeScreenCountParam); + + Page eventPage(LargeScreenCountParam largeScreenCountParam); + + Page devicePage(LargeScreenCountParam largeScreenCountParam); + + Page userEventList(LargeScreenCountParam largeScreenCountParam); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/MessageEventFeedbackService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/MessageEventFeedbackService.java new file mode 100644 index 0000000..3156159 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/MessageEventFeedbackService.java @@ -0,0 +1,7 @@ +package com.njcn.product.event.transientes.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.event.transientes.pojo.po.MessageEventFeedback; + +public interface MessageEventFeedbackService extends IService { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/MsgEventConfigService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/MsgEventConfigService.java new file mode 100644 index 0000000..697f077 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/MsgEventConfigService.java @@ -0,0 +1,20 @@ +package com.njcn.product.event.transientes.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.event.transientes.pojo.po.MsgEventConfig; + +import java.util.List; + +public interface MsgEventConfigService extends IService { + + boolean eventConfig(MsgEventConfig msgEventConfig); + + + MsgEventConfig queryConfig(); + + List getEventType(); + + Float getEventValue(); + + Integer getEventDuration(); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/MsgEventInfoService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/MsgEventInfoService.java new file mode 100644 index 0000000..43db3bd --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/MsgEventInfoService.java @@ -0,0 +1,11 @@ +package com.njcn.product.event.transientes.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.event.transientes.pojo.po.MsgEventInfo; + +import java.util.List; + +public interface MsgEventInfoService extends IService { + + List getMsgByIds(List ids); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqDevicedetailService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqDevicedetailService.java new file mode 100644 index 0000000..1730877 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqDevicedetailService.java @@ -0,0 +1,16 @@ +package com.njcn.product.event.transientes.service; + +import com.njcn.product.event.devcie.pojo.po.PqDeviceDetail; +import com.baomidou.mybatisplus.extension.service.IService; + /** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqDevicedetailService extends IService{ + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqUserLedgerService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqUserLedgerService.java new file mode 100644 index 0000000..64484ae --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqUserLedgerService.java @@ -0,0 +1,26 @@ +package com.njcn.product.event.transientes.service; + + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.event.transientes.pojo.param.PqUserLedgerParam; +import com.njcn.product.event.transientes.pojo.po.PqUserLedgerPO; + +import java.util.List; + +public interface PqUserLedgerService extends IService { + // 添加记录 + boolean addLedger(PqUserLedgerParam ledgerParam); + + // 更新记录 + boolean updateLedger(PqUserLedgerParam ledgerParam); + + // 删除记录(物理删除) + boolean deleteLedger(List ids); + + // 查询单条记录 + PqUserLedgerPO getLedgerById(String id); + + // 查询所有记录 + Page pageList(PqUserLedgerParam param); +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsDeptsService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsDeptsService.java new file mode 100644 index 0000000..a9f6649 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsDeptsService.java @@ -0,0 +1,24 @@ +package com.njcn.product.event.transientes.service; + +import com.njcn.product.event.devcie.pojo.dto.PqsDeptDTO; +import com.njcn.product.event.transientes.pojo.po.PqsDepts; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + * + * Description: + * Date: 2025/06/19 下午 3:57【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsDeptsService extends IService{ + + + List findDeptAndChildren( String deptId); + + List getDeptList( List deptIds); + + } diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsDicTreeService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsDicTreeService.java new file mode 100644 index 0000000..97cf056 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsDicTreeService.java @@ -0,0 +1,14 @@ +package com.njcn.product.event.transientes.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.event.transientes.pojo.po.PqsDicTreePO; + +import java.util.List; + +public interface PqsDicTreeService extends IService { + + + + List getDicTree(String code); + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsEventdetailService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsEventdetailService.java new file mode 100644 index 0000000..cabf8ca --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsEventdetailService.java @@ -0,0 +1,17 @@ +package com.njcn.product.event.transientes.service; + +import com.njcn.product.event.transientes.pojo.po.PqsEventdetail; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * + * Description: + * Date: 2025/06/20 上午 10:06【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsEventdetailService extends IService{ + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsOnlinerateService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsOnlinerateService.java new file mode 100644 index 0000000..23a77cf --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsOnlinerateService.java @@ -0,0 +1,16 @@ +package com.njcn.product.event.transientes.service; + +import com.njcn.product.event.transientes.pojo.po.PqsOnlinerate; +import com.baomidou.mybatisplus.extension.service.IService; + /** + * + * Description: + * Date: 2025/07/29 下午 6:40【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsOnlinerateService extends IService{ + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsUserService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsUserService.java new file mode 100644 index 0000000..5a82bc9 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsUserService.java @@ -0,0 +1,14 @@ +package com.njcn.product.event.transientes.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.event.transientes.pojo.po.PqsUser; + +/** + * Description: + * Date: 2025/06/27 上午 9:45【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsUserService extends IService { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsUsersetService.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsUsersetService.java new file mode 100644 index 0000000..50243f4 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/PqsUsersetService.java @@ -0,0 +1,18 @@ +package com.njcn.product.event.transientes.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.njcn.product.event.transientes.pojo.po.PqsUserSet; + +/** + * + * Description: + * Date: 2025/06/26 下午 2:27【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +public interface PqsUsersetService extends IService{ + + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/EventGateServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/EventGateServiceImpl.java new file mode 100644 index 0000000..4c306fd --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/EventGateServiceImpl.java @@ -0,0 +1,76 @@ +package com.njcn.product.event.transientes.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.event.file.component.WaveFileComponent; +import com.njcn.event.file.pojo.dto.WaveDataDTO; +import com.njcn.event.file.pojo.enums.WaveFileResponseEnum; +import com.njcn.product.event.devcie.mapper.PqLinedetailMapper; +import com.njcn.product.event.devcie.pojo.po.PqDevice; +import com.njcn.product.event.devcie.pojo.po.PqLine; +import com.njcn.product.event.devcie.pojo.po.PqLinedetail; +import com.njcn.product.event.devcie.service.PqDeviceService; +import com.njcn.product.event.devcie.service.PqLineService; +import com.njcn.product.event.transientes.pojo.param.MonitorTerminalParam; +import com.njcn.product.event.transientes.pojo.po.PqsEventdetail; +import com.njcn.product.event.transientes.service.EventGateService; +import com.njcn.product.event.transientes.service.PqsEventdetailService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.util.Objects; + +/** + * @Author: cdf + * @CreateTime: 2025-06-30 + * @Description: + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class EventGateServiceImpl implements EventGateService { + + private final PqsEventdetailService pqsEventdetailService; + private final PqDeviceService pqDeviceService; + private final WaveFileComponent waveFileComponent; + private final PqLineService pqLineService; + private final PqLinedetailMapper pqLinedetailMapper; + @Value("${WAVEPATH}") + private String WAVEPATH; + @Override + public WaveDataDTO getTransientAnalyseWave(MonitorTerminalParam param) { + WaveDataDTO waveDataDTO; + //获取暂降事件 + PqsEventdetail eventDetail = pqsEventdetailService.getById(param.getId()); + Integer lineid = eventDetail.getLineid(); + PqLine pqLine = pqLineService.getById(lineid); + PqLinedetail pqLinedetail = pqLinedetailMapper.selectById(lineid); + PqDevice device = pqDeviceService.getById(pqLine.getDevIndex()); + String waveName = eventDetail.getWavename(); + String cfgPath, datPath; + if (StrUtil.isBlank(waveName)) { + throw new BusinessException(WaveFileResponseEnum.ANALYSE_WAVE_NOT_FOUND); + } + cfgPath = WAVEPATH+"/"+device.getIp()+"/"+waveName+".CFG"; + datPath = WAVEPATH+"/"+device.getIp()+"/"+waveName+".DAT"; + log.info("本地磁盘波形文件路径----" + cfgPath); + InputStream cfgStream = waveFileComponent.getFileInputStreamByFilePath(cfgPath); + InputStream datStream = waveFileComponent.getFileInputStreamByFilePath(datPath); + if (Objects.isNull(cfgStream) || Objects.isNull(datStream)) { + throw new BusinessException(WaveFileResponseEnum.ANALYSE_WAVE_NOT_FOUND); + } + waveDataDTO = waveFileComponent.getComtrade(cfgStream, datStream, 1); + + waveDataDTO = waveFileComponent.getValidData(waveDataDTO); + + waveDataDTO.setPtType(pqLinedetail.getPttype()); + waveDataDTO.setPt(pqLine.getPt1()/ pqLine.getPt2()); + waveDataDTO.setCt(pqLine.getCt1()/ pqLine.getCt2()); + waveDataDTO.setMonitorName(pqLine.getName()); + return waveDataDTO; + + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/EventRightServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/EventRightServiceImpl.java new file mode 100644 index 0000000..6157527 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/EventRightServiceImpl.java @@ -0,0 +1,983 @@ +package com.njcn.product.event.transientes.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.product.event.devcie.mapper.PqGdCompanyMapper; +import com.njcn.product.event.devcie.mapper.PqSubstationMapper; +import com.njcn.product.event.devcie.pojo.dto.LedgerBaseInfoDTO; +import com.njcn.product.event.devcie.pojo.po.*; +import com.njcn.product.event.devcie.service.PqLineService; +import com.njcn.product.event.devcie.service.PqsDeptslineService; +import com.njcn.product.event.transientes.mapper.PqUserLedgerMapper; +import com.njcn.product.event.transientes.mapper.PqUserLineAssMapper; +import com.njcn.product.event.transientes.mapper.PqsDicTreeMapper; +import com.njcn.product.event.transientes.pojo.DicTreeEnum; +import com.njcn.product.event.transientes.pojo.param.LargeScreenCountParam; +import com.njcn.product.event.transientes.pojo.po.*; +import com.njcn.product.event.transientes.pojo.vo.EventDetailVO; +import com.njcn.product.event.transientes.pojo.vo.UserLedgerStatisticVO; +import com.njcn.product.event.transientes.service.*; +import com.njcn.web.factory.PageFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @Author: cdf + * @CreateTime: 2025-06-30 + * @Description: + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class EventRightServiceImpl implements EventRightService { + + + private final PqUserLineAssMapper pqUserLineAssMapper; + + private final PqUserLedgerMapper pqUserLedgerMapper; + + private final PqsDicTreeMapper pqsDicTreeMapper; + + private final PqsEventdetailService pqsEventdetailService; + + private final MsgEventConfigService msgEventConfigService; + + private final PqLineService pqLineService; + + private final PqSubstationMapper pqSubstationMapper; + private final CommGeneralService commGeneralService; + + private final PqGdCompanyMapper pqGdCompanyMapper; + + private final PqsDeptslineService pqsDeptslineService; + + private final PqsDeptsService pqsDeptsService; + @Value("${SYS_TYPE_ZT}") + private String sysTypeZt; + + + + @Override + public UserLedgerStatisticVO userLedgerStatistic(LargeScreenCountParam param) { + UserLedgerStatisticVO result = new UserLedgerStatisticVO(); + + // 1. 获取字典树数据 + List dicTreeList = getAllDicTrees(); + Map treeMap = getDicTreeMap(dicTreeList); + setResultIds(result, treeMap); + + // 2. 获取线路ID列表 + List lineIds = commGeneralService.getLineIdsByDept(param); + if (CollUtil.isEmpty(lineIds)) { + return result; + } + + // 3. 获取用户线路关联数据 + List assList = getUserLineAssociations(lineIds); + if (CollUtil.isEmpty(assList)) { + return result; + } + + // 4. 获取用户台账信息 + Set assUserIds = assList.stream() + .map(PqUserLineAssPO::getUserIndex) + .collect(Collectors.toSet()); + List userLedgers = getUserLedgers(new ArrayList<>(assUserIds),null,false); + if (CollUtil.isEmpty(userLedgers)) { + return result; + } + + // 5. 获取事件和线路数据 + List events = getEventsInTimeRange(param, lineIds); + List lines = getLines(lineIds); + + // 6. 按用户类型分组处理 + Map> userMap = userLedgers.stream() + .collect(Collectors.groupingBy(PqUserLedgerPO::getBigObjType)); + + // 7. 构建结果 + buildResult(result, treeMap, userMap, assList, events, lines,dicTreeList); + + return result; + } + + + private List getAllDicTrees(){ + return pqsDicTreeMapper.selectList(new LambdaQueryWrapper<>()); + } + + private Map getDicTreeMap(List dicTreeList) { + List touList = dicTreeList.stream().filter(it -> Objects.equals(it.getCode(), DicTreeEnum.BJ_USER.getCode())||Objects.equals(it.getCode(), DicTreeEnum.OI_USER.getCode())||Objects.equals(it.getCode(), DicTreeEnum.OT_USER.getCode())).collect(Collectors.toList()); + Map treeMap = touList.stream().collect(Collectors.toMap(PqsDicTreePO::getCode, Function.identity())); + return treeMap; + } + + private void setResultIds (UserLedgerStatisticVO result,Map treeMap){ + treeMap.forEach((tree, obj) -> { + if (tree.equals(DicTreeEnum.BJ_USER.getCode())) { + result.setImportId(obj.getId()); + } else if (tree.equals(DicTreeEnum.OI_USER.getCode())) { + result.setOtherImportId(obj.getId()); + } else if (tree.equals(DicTreeEnum.OT_USER.getCode())) { + result.setOtherId(obj.getId()); + } + }); + } + + + private List getUserLineAssociations(List lineIds){ + LambdaQueryWrapper assQuery = new LambdaQueryWrapper<>(); + // assQuery.in(PqUserLineAssPO::getLineIndex, lineIds); + + if(lineIds.size()>1000){ + List> lineList = CollUtil.split(lineIds, 1000); + assQuery.and(w -> { + for (List ids : lineList) { + w.or(wIn -> wIn.in(PqUserLineAssPO::getLineIndex, ids)); + } + }); + }else { + assQuery.in(PqUserLineAssPO::getLineIndex, lineIds); + } + + return pqUserLineAssMapper.selectList(assQuery); + } + + private List getEventsInTimeRange(LargeScreenCountParam param,List lineIds){ + //查询时间段的暂降事件 + LambdaQueryWrapper eventQuery = new LambdaQueryWrapper<>(); + eventQuery.between(PqsEventdetail::getTimeid, DateUtil.parse(param.getSearchBeginTime()), DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime()))) + .in(PqsEventdetail::getWavetype, msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()); + if (lineIds.size() > 1000) { + List> listLineIds = CollUtil.split(lineIds, 1000); + eventQuery.and(w -> { + for (List ids : listLineIds) { + w.or(wIn -> wIn.in(PqsEventdetail::getLineid, ids)); + } + }); + } else { + eventQuery.in(PqsEventdetail::getLineid, lineIds); + } + List eventdetailList = pqsEventdetailService.list(eventQuery); + if(CollUtil.isNotEmpty(eventdetailList)){ + eventdetailList.forEach(it->it.setPersisttime(BigDecimal.valueOf(it.getPersisttime() / 1000).setScale(3,RoundingMode.HALF_UP).doubleValue())); + } + return eventdetailList; + } + + private List getUserLedgers(List assUserIds,LargeScreenCountParam param,boolean queryFlag){ + LambdaQueryWrapper userWrapper = new LambdaQueryWrapper<>(); + if(assUserIds.size()>1000){ + List> assUserIdsList = CollUtil.split(assUserIds, 1000); + userWrapper.and(w -> { + for (List ids : assUserIdsList) { + w.or(wIn -> wIn.in(PqUserLedgerPO::getId, ids)); + } + }); + }else { + userWrapper.in(PqUserLedgerPO::getId, assUserIds); + } + if(queryFlag){ + if(StrUtil.isNotBlank(param.getBigObjType())){ + //对象大类不为空 + userWrapper.eq(PqUserLedgerPO::getBigObjType,param.getBigObjType()); + } + if(StrUtil.isNotBlank(param.getSmallObjType())){ + //对象大类不为空 + userWrapper.eq(PqUserLedgerPO::getSmallObjType,param.getSmallObjType()); + } + if(Objects.nonNull(param.getGdIndex())){ + userWrapper.eq(PqUserLedgerPO::getPowerSupplyArea,param.getGdIndex()); + } + if(StrUtil.isNotBlank(param.getSearchValue())){ + userWrapper.like(PqUserLedgerPO::getCustomerName,param.getSearchValue()); + } + } + return pqUserLedgerMapper.selectList(userWrapper); + } + private List getLines(List lineIds){ + LambdaQueryWrapper lineQuery = new LambdaQueryWrapper<>(); + if (lineIds.size() > 1000) { + List> listLineIds = CollUtil.split(lineIds, 1000); + + lineQuery.and(w -> { + for (List ids : listLineIds) { + w.or(wIn -> wIn.in(PqLine::getLineIndex, ids)); + } + }); + } else { + lineQuery.in(PqLine::getLineIndex, lineIds); + } + return pqLineService.list(lineQuery); + } + + + private void buildResult(UserLedgerStatisticVO result,Map treeMap,Map> userMap,List assList,List eventdetailList, List lineList,List dicTreePOList){ + List innerList = new ArrayList<>(); + Map allTreeMap = dicTreePOList.stream().collect(Collectors.toMap(PqsDicTreePO::getId,dept->dept)); + + treeMap.forEach((tree, obj) -> { + //获取对象大类的用户 + List oneList = userMap.get(obj.getId()); + + if (tree.equals(DicTreeEnum.BJ_USER.getCode())) { + Integer[] count = getEventCount(oneList, assList, eventdetailList,true); + result.setImportNum(count[0]); + result.setImportDevNum(count[1]); + } else if (tree.equals(DicTreeEnum.OI_USER.getCode())) { + Integer[] count = getEventCount(oneList, assList, eventdetailList,true); + result.setOtherImportNum(count[0]); + result.setOtherImportDevNum(count[1]); + } else if (tree.equals(DicTreeEnum.OT_USER.getCode())) { + Integer[] count = getEventCount(oneList, assList, eventdetailList,true); + result.setOtherNum(count[0]); + result.setOtherDevNum(count[1]); + } + + UserLedgerStatisticVO.Inner inner = new UserLedgerStatisticVO.Inner(); + inner.setName(obj.getName()); + + inner.setCount(0); + + List childrenList = new ArrayList<>(); + if(CollUtil.isNotEmpty(oneList)) { + Map> smallMap = oneList.stream().collect(Collectors.groupingBy(PqUserLedgerPO::getSmallObjType)); + smallMap.forEach((key, userList) -> { + UserLedgerStatisticVO.Inner item = new UserLedgerStatisticVO.Inner(); + Integer[] count = getEventCount(userList, assList, eventdetailList, false); + item.setCount(count[0]); + item.setTreeId(key); + item.setParentId(obj.getId()); + item.setName(allTreeMap.containsKey(key) ? allTreeMap.get(key).getName() : "/"); + childrenList.add(item); + }); + inner.setChildren(childrenList); + innerList.add(inner); + } + }); + + result.setInnerList(innerList); + } + + + private void buildResultClone(UserLedgerStatisticVO result,Map treeMap,Map> userMap,List assList,List eventdetailList, List lineList,List dicTreePOList){ + List innerList = new ArrayList<>(); + Map allTreeMap = dicTreePOList.stream().collect(Collectors.toMap(PqsDicTreePO::getId,dept->dept)); + + treeMap.forEach((tree, obj) -> { + //获取对象大类的用户 + List oneList = userMap.get(obj.getId()); + + + if (tree.equals(DicTreeEnum.BJ_USER.getCode())) { + Integer[] count = getEventCount(oneList, assList, eventdetailList,true); + result.setImportNum(oneList.size()); + result.setImportDevNum(count[1]); + } else if (tree.equals(DicTreeEnum.OI_USER.getCode())) { + Integer[] count = getEventCount(oneList, assList, eventdetailList,true); + result.setOtherImportNum(oneList.size()); + result.setOtherImportDevNum(count[1]); + } else if (tree.equals(DicTreeEnum.OT_USER.getCode())) { + Integer[] count = getEventCount(oneList, assList, eventdetailList,true); + result.setOtherNum(oneList.size()); + result.setOtherDevNum(count[1]); + } + + UserLedgerStatisticVO.Inner inner = new UserLedgerStatisticVO.Inner(); + inner.setName(obj.getName()); + + inner.setCount(0); + + List childrenList = new ArrayList<>(); + if(CollUtil.isNotEmpty(oneList)) { + Map> smallMap = oneList.stream().collect(Collectors.groupingBy(PqUserLedgerPO::getSmallObjType)); + smallMap.forEach((key, userList) -> { + UserLedgerStatisticVO.Inner item = new UserLedgerStatisticVO.Inner(); + Integer[] count = getEventCount(userList, assList, eventdetailList, false); + item.setCount(count[0]); + item.setTreeId(key); + item.setParentId(obj.getId()); + item.setName(allTreeMap.containsKey(key) ? allTreeMap.get(key).getName() : "/"); + childrenList.add(item); + }); + inner.setChildren(childrenList); + innerList.add(inner); + } + }); + + result.setInnerList(innerList); + } + + +/* @Override + public Page rightEventOpen(LargeScreenCountParam param) { + Page result = new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)); + + // 1. 获取线路ID + List lineIds = commGeneralService.getLineIdsByRedis(param.getDeptId()); + if (CollUtil.isEmpty(lineIds)) { + return result; + } + + // 2. 获取用户线路关联 + List assList = getUserLineAssociations(lineIds); + if (CollUtil.isEmpty(assList)) { + return result; + } + + // 3. 获取用户台账 + List userLedgers = getFilteredUserLedgers(assList, param); + if (CollUtil.isEmpty(userLedgers)) { + return result; + } + + // 4. 获取事件数据 + List lineUseList = assList.stream() + .map(PqUserLineAssPO::getLineIndex) + .distinct() + .collect(Collectors.toList()); + + Page eventPage = getEventsPage(param, lineUseList); + if (CollUtil.isEmpty(eventPage.getRecords())) { + return result; + } + + // 5. 构建结果 + buildEventDetailResult(result, eventPage, assList, userLedgers); + + return result; + }*/ + + + + + @Override + public Page rightEventOpen(LargeScreenCountParam param) { + Page result = new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)); + List deptLineIds = commGeneralService.getLineIdsByRedis(param.getDeptId()); + if(CollUtil.isEmpty(deptLineIds)){ + return result; + } + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + PqSubstation pqSubstation = null; + if(Objects.nonNull(param.getBdId())){ + pqSubstation = pqSubstationMapper.selectOne(new LambdaQueryWrapper().eq(PqSubstation::getSubIndex,param.getBdId())); + if(Objects.isNull(pqSubstation)){ + return result; + } + queryWrapper.in(PqLine::getSubIndex, pqSubstation.getSubIndex()); + + } + PqGdCompany pqGdCompany = null; + if(Objects.nonNull(param.getGdIndex())){ + pqGdCompany = pqGdCompanyMapper.selectOne(new LambdaQueryWrapper().eq(PqGdCompany::getGdIndex,param.getGdIndex())); + if(Objects.isNull(pqGdCompany)){ + return result; + } + queryWrapper.in(PqLine::getGdIndex, pqGdCompany.getGdIndex()); + } + + if(Objects.nonNull(param.getBdId()) || Objects.nonNull(param.getGdIndex())) { + + if(deptLineIds.size()>1000){ + List> assUserIdsList = CollUtil.split(deptLineIds, 1000); + queryWrapper.and(w -> { + for (List ids : assUserIdsList) { + w.or(wIn -> wIn.in(PqLine::getLineIndex, ids)); + } + }); + }else { + queryWrapper.in(PqLine::getLineIndex, deptLineIds); + } + + List pqLineList = pqLineService.list(queryWrapper); + deptLineIds = pqLineList.stream().map(PqLine::getLineIndex).distinct().collect(Collectors.toList()); + } + + if(CollUtil.isEmpty(deptLineIds)){ + return result; + } + + + + //获取用户监测点关系符合部门监测点的 + List assList = getUserLineAssociations(deptLineIds); + if(CollUtil.isEmpty(assList)){ + return result; + } + List userIds = assList.stream().map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + if(CollUtil.isEmpty(userIds)){ + return result; + } + //获取符合条件的用户 + List pqUserLedgerPOList =getUserLedgers(userIds,param,true); + + if(CollUtil.isEmpty(pqUserLedgerPOList)){ + return result; + } + Map pqMap = pqUserLedgerPOList.stream().collect(Collectors.toMap(PqUserLedgerPO::getId,Function.identity())); + List pUserIds = pqUserLedgerPOList.stream().map(PqUserLedgerPO::getId).collect(Collectors.toList()); + List assListLast = assList.stream().filter(it->pUserIds.contains(it.getUserIndex())).collect(Collectors.toList()); + List lineUseList = assListLast.stream().map(PqUserLineAssPO::getLineIndex).distinct().collect(Collectors.toList()); + + //查询时间段的暂降事件 + LambdaQueryWrapper eventQuery = new LambdaQueryWrapper<>(); + eventQuery.between(PqsEventdetail::getTimeid, DateUtil.parse(param.getSearchBeginTime()), DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime()))) + .in(PqsEventdetail::getWavetype, msgEventConfigService.getEventType()).orderByDesc(PqsEventdetail::getTimeid) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()); + + if (lineUseList.size() > 1000) { + List> listLineIds = CollUtil.split(lineUseList, 1000); + eventQuery.and(w -> { + for (List ids : listLineIds) { + w.or(wIn -> wIn.in(PqsEventdetail::getLineid, ids)); + } + }); + } else { + eventQuery.in(PqsEventdetail::getLineid, lineUseList); + } + Page page = pqsEventdetailService.page(new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)),eventQuery); + List temResultList = page.getRecords(); + if(CollUtil.isEmpty(temResultList)){ + return result; + } + + List ids = temResultList.stream().map(PqsEventdetail::getLineid).distinct().collect(Collectors.toList()); + List dtoList = pqLineService.getBaseLineInfo(ids); + Map lineMap = dtoList.stream().collect(Collectors.toMap(LedgerBaseInfoDTO::getLineId,Function.identity())); + Map> temMap = assListLast.stream().filter(it->ids.contains(it.getLineIndex())).collect(Collectors.groupingBy(PqUserLineAssPO::getLineIndex,Collectors.mapping(PqUserLineAssPO::getUserIndex,Collectors.toList()))); + + List resultList = new ArrayList<>(); + for(PqsEventdetail pqsEventdetail : temResultList){ + EventDetailVO eventDetailVO = new EventDetailVO(); + BeanUtil.copyProperties(pqsEventdetail,eventDetailVO); + List userTemIds = temMap.get(pqsEventdetail.getLineid()); + String objName = userTemIds.stream().map(it->pqMap.get(it).getCustomerName()).collect(Collectors.joining("; ")); + eventDetailVO.setObjName(objName); + LedgerBaseInfoDTO dto = lineMap.get(pqsEventdetail.getLineid()); + eventDetailVO.setBdname(dto.getStationName()); + eventDetailVO.setGdName(dto.getGdName()); + eventDetailVO.setBusName(dto.getBusBarName()); + eventDetailVO.setLineid(dto.getLineId()); + eventDetailVO.setPointname(dto.getLineName()); + eventDetailVO.setEventdetail_index(pqsEventdetail.getEventdetailIndex()); + eventDetailVO.setDevName(dto.getDevName()); + eventDetailVO.setPersisttime(BigDecimal.valueOf(pqsEventdetail.getPersisttime() / 1000).setScale(3,RoundingMode.HALF_UP).toString()); + resultList.add(eventDetailVO); + } + result.setTotal(page.getTotal()); + result.setRecords(resultList); + return result; + } + + + @Override + public Page rightEventOpenForDetail(LargeScreenCountParam param) { + + Page result = new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)); + + //查询时间段的暂降事件 + LambdaQueryWrapper eventQuery = new LambdaQueryWrapper<>(); + eventQuery.between(PqsEventdetail::getTimeid, DateUtil.parse(param.getSearchBeginTime()), DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime()))) + .in(PqsEventdetail::getWavetype, msgEventConfigService.getEventType()).orderByDesc(PqsEventdetail::getTimeid) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .in(PqsEventdetail::getEventdetailIndex,param.getEventIds()); + + Page page = pqsEventdetailService.page(new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)),eventQuery); + List temResultList = page.getRecords(); + if(CollUtil.isEmpty(temResultList)){ + return result; + } + + List ids = temResultList.stream().map(PqsEventdetail::getLineid).distinct().collect(Collectors.toList()); + List dtoList = pqLineService.getBaseLineInfo(ids); + Map lineMap = dtoList.stream().collect(Collectors.toMap(LedgerBaseInfoDTO::getLineId,Function.identity())); + + //获取用户监测点关系符合部门监测点的 + List assList = getUserLineAssociations(ids); + if(CollUtil.isEmpty(assList)){ + return result; + } + List userIds = assList.stream().map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + if(CollUtil.isEmpty(userIds)){ + return result; + } + Map> temMap = assList.stream().collect(Collectors.groupingBy(PqUserLineAssPO::getLineIndex,Collectors.mapping(PqUserLineAssPO::getUserIndex,Collectors.toList()))); + + //获取符合条件的用户 + List pqUserLedgerPOList =getUserLedgers(userIds,param,true); + + if(CollUtil.isEmpty(pqUserLedgerPOList)){ + return result; + } + Map pqMap = pqUserLedgerPOList.stream().collect(Collectors.toMap(PqUserLedgerPO::getId,Function.identity())); + + List resultList = new ArrayList<>(); + for(PqsEventdetail pqsEventdetail : temResultList){ + EventDetailVO eventDetailVO = new EventDetailVO(); + BeanUtil.copyProperties(pqsEventdetail,eventDetailVO); + List userTemIds = temMap.get(pqsEventdetail.getLineid()); + String objName = userTemIds.stream().map(it->pqMap.get(it).getCustomerName()).collect(Collectors.joining("; ")); + eventDetailVO.setObjName(objName); + LedgerBaseInfoDTO dto = lineMap.get(pqsEventdetail.getLineid()); + eventDetailVO.setBdname(dto.getStationName()); + eventDetailVO.setGdName(dto.getGdName()); + eventDetailVO.setBusName(dto.getBusBarName()); + eventDetailVO.setLineid(dto.getLineId()); + eventDetailVO.setPointname(dto.getLineName()); + eventDetailVO.setEventdetail_index(pqsEventdetail.getEventdetailIndex()); + eventDetailVO.setDevName(dto.getDevName()); + eventDetailVO.setPersisttime(BigDecimal.valueOf(pqsEventdetail.getPersisttime() / 1000).setScale(3,RoundingMode.HALF_UP).toString()); + resultList.add(eventDetailVO); + } + result.setTotal(page.getTotal()); + result.setRecords(resultList); + return result; + } + + @Override + public Page rightEventDevOpen(LargeScreenCountParam param) { + Page result = new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)); + List lineIds = commGeneralService.getLineIdsByRedis(param.getDeptId()); + if(CollUtil.isEmpty(lineIds)){ + return result; + } + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + PqSubstation pqSubstation = null; + if(Objects.nonNull(param.getBdId())){ + pqSubstation = pqSubstationMapper.selectOne(new LambdaQueryWrapper().eq(PqSubstation::getSubIndex,param.getBdId())); + if(Objects.isNull(pqSubstation)){ + return result; + } + queryWrapper.in(PqLine::getSubIndex, pqSubstation.getSubIndex()); + + } + PqGdCompany pqGdCompany = null; + if(Objects.nonNull(param.getGdIndex())){ + pqGdCompany = pqGdCompanyMapper.selectOne(new LambdaQueryWrapper().eq(PqGdCompany::getGdIndex,param.getGdIndex())); + if(Objects.isNull(pqGdCompany)){ + return result; + } + queryWrapper.in(PqLine::getGdIndex, pqGdCompany.getGdIndex()); + } + + if(Objects.nonNull(param.getBdId()) || Objects.nonNull(param.getGdIndex())) { + + if(lineIds.size()>1000){ + List> assUserIdsList = CollUtil.split(lineIds, 1000); + queryWrapper.and(w -> { + for (List ids : assUserIdsList) { + w.or(wIn -> wIn.in(PqLine::getLineIndex, ids)); + } + }); + }else { + queryWrapper.in(PqLine::getLineIndex, lineIds); + } + + List pqLineList = pqLineService.list(queryWrapper); + lineIds = pqLineList.stream().map(PqLine::getLineIndex).distinct().collect(Collectors.toList()); + } + + if(CollUtil.isEmpty(lineIds)){ + return result; + } + + List assPOList =getUserLineAssociations(lineIds); + if(CollUtil.isEmpty(assPOList)){ + return result; + } + + List userIds = assPOList.stream().map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + + if(userIds.size() > 1000){ + List> userIdList = CollUtil.split(userIds,1000); + lambdaQueryWrapper.and(ad->{ + for(List ids : userIdList){ + ad.or(o->o.in(PqUserLedgerPO::getId,ids)); + } + }); + }else { + lambdaQueryWrapper.in(PqUserLedgerPO::getId,userIds); + } + + if(StrUtil.isNotBlank(param.getBigObjType())){ + //对象大类不为空 + lambdaQueryWrapper.eq(PqUserLedgerPO::getBigObjType,param.getBigObjType()); + } + if(StrUtil.isNotBlank(param.getSmallObjType())){ + //对象大类不为空 + lambdaQueryWrapper.eq(PqUserLedgerPO::getSmallObjType,param.getSmallObjType()); + } + + if(Objects.nonNull(param.getGdIndex())){ + lambdaQueryWrapper.eq(PqUserLedgerPO::getPowerSupplyArea,param.getGdIndex()); + } + + if(StrUtil.isNotBlank(param.getSearchValue())){ + lambdaQueryWrapper.eq(PqUserLedgerPO::getCustomerName,param.getSearchValue()); + } + + List userList = pqUserLedgerMapper.selectList(lambdaQueryWrapper); + if(CollUtil.isEmpty(userList)){ + return result; + } + List userTemIds = userList.stream().map(PqUserLedgerPO::getId).collect(Collectors.toList()); + + List aassList = assPOList.stream().filter(it->userTemIds.contains(it.getUserIndex())).collect(Collectors.toList()); + List ids = aassList.stream().map(PqUserLineAssPO::getLineIndex).distinct().collect(Collectors.toList()); + List eventdetailList = getEventsInTimeRange(param,ids); + if(CollUtil.isEmpty(eventdetailList)){ + return result; + } + List temLineIds = eventdetailList.stream().map(PqsEventdetail::getLineid).distinct().collect(Collectors.toList()); + List lastUserList = aassList.stream().filter(it->temLineIds.contains(it.getLineIndex())).map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + + Page page = pqUserLedgerMapper.selectPage(new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)),new LambdaQueryWrapper().in(PqUserLedgerPO::getId,lastUserList).orderByAsc(PqUserLedgerPO::getSmallObjType,PqUserLedgerPO::getUpdateTime)); + if(CollUtil.isEmpty(page.getRecords())){ + return page; + } + + List lastIds = page.getRecords().stream().map(PqUserLedgerPO::getId).collect(Collectors.toList()); + List lastAssList = aassList.stream().filter(it->lastIds.contains(it.getUserIndex())).collect(Collectors.toList()); + + List monitorIds = lastAssList.stream().map(PqUserLineAssPO::getLineIndex).distinct().collect(Collectors.toList()); + List pqLineList = pqLineService.list(new LambdaQueryWrapper().in(PqLine::getLineIndex,monitorIds)); + + List pqSubstationList = pqSubstationMapper.selectList(new LambdaQueryWrapper().in(PqSubstation::getSubIndex,pqLineList.stream().map(PqLine::getSubIndex).collect(Collectors.toList()))); + Map subMap = pqSubstationList.stream().collect(Collectors.toMap(PqSubstation::getSubIndex,sub->sub)); + pqLineList.forEach(it->it.setSubName(subMap.get(it.getSubIndex()).getName())); + + Map> objMap = lastAssList.stream().collect(Collectors.groupingBy(PqUserLineAssPO::getUserIndex,Collectors.mapping(PqUserLineAssPO::getLineIndex,Collectors.toList()))); + Map> lastMap = new HashMap<>(); + objMap.forEach((k,vList)->{ + lastMap.put(k,pqLineList.stream().filter(it->vList.contains(it.getLineIndex())).collect(Collectors.toList())); + }); + page.getRecords().forEach(item-> { + if(objMap.containsKey(item.getId())){ + List countObj = eventdetailList.stream().filter(it->objMap.get(item.getId()).contains(it.getLineid())).collect(Collectors.toList()); + item.setEventCount(countObj.size()); + item.setEventIds(countObj.stream().map(PqsEventdetail::getEventdetailIndex).collect(Collectors.toList())); + + if(param.getExportFlag()){ + item.setEventList(countObj); + if(lastMap.containsKey(item.getId())){ + List abList = lastMap.get(item.getId()); + Map lineMap= abList.stream().collect(Collectors.toMap(PqLine::getLineIndex,Function.identity())); + for(PqsEventdetail pqsEventdetail:countObj){ + if(lineMap.containsKey(pqsEventdetail.getLineid())){ + PqLine pqLine = lineMap.get(pqsEventdetail.getLineid()); + pqsEventdetail.setBusBarName(pqLine.getSubvName()); + pqsEventdetail.setStationName(pqLine.getSubName()); + } + + } + } + } + + } + if(lastMap.containsKey(item.getId())){ + List abList = lastMap.get(item.getId()); + item.setSubstationName(abList.stream().map(PqLine::getSubName).distinct().collect(Collectors.joining(StrUtil.COMMA))); + item.setInfo(abList.stream().map(items->items.getSubName()+"_"+items.getSubvName()).distinct().collect(Collectors.joining("; "))); + } + }); + return page; + } + + + + @Override + public List rightImportUser(LargeScreenCountParam param) { + List result = new ArrayList<>(); + List deptLineIds = commGeneralService.getLineIdsByRedis(param.getDeptId()); + if(CollUtil.isEmpty(deptLineIds)){ + return result; + } + + List assPOList = getUserLineAssociations(deptLineIds); + if(CollUtil.isEmpty(assPOList)){ + return result; + } + List userIds = assPOList.stream().map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + + LambdaQueryWrapper userQuery = new LambdaQueryWrapper<>(); + if(userIds.size() > 1000){ + List> userIdList = CollUtil.split(userIds,1000); + userQuery.and(ad->{ + for(List ids : userIdList){ + ad.or(o->o.in(PqUserLedgerPO::getId,ids)); + } + }); + }else { + userQuery.in(PqUserLedgerPO::getId,userIds); + } + userQuery.eq(PqUserLedgerPO::getIsShow,1); + List poList = pqUserLedgerMapper.selectList(userQuery); + if(CollUtil.isEmpty(poList)){ + return result; + } + + List ids = poList.stream().map(PqUserLedgerPO::getId).collect(Collectors.toList()); + List assTemList = assPOList.stream().filter(it->ids.contains(it.getUserIndex())).collect(Collectors.toList()); + + //获取监测id,用于查询暂降表 + List lineIds = assTemList.stream().map(PqUserLineAssPO::getLineIndex).distinct().collect(Collectors.toList()); + + List eventList = pqsEventdetailService.lambdaQuery(). + in(PqsEventdetail::getLineid,lineIds) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .between(PqsEventdetail::getTimeid,DateUtil.beginOfDay(DateUtil.parse(param.getSearchBeginTime())),DateUtil.endOfDay(DateUtil.parse(param.getSearchEndTime()))).list(); + if(CollUtil.isEmpty(eventList)){ + poList.forEach(item->{ + UserLedgerStatisticVO.Inner inner = new UserLedgerStatisticVO.Inner(); + inner.setCustomId(item.getId()); + inner.setName(item.getCustomerName()); + inner.setCount(0); + result.add(inner); + }); + return result; + } + + Map> assMap = assTemList.stream().collect(Collectors.groupingBy(PqUserLineAssPO::getUserIndex,Collectors.mapping(PqUserLineAssPO::getLineIndex,Collectors.toList()))); + poList.forEach(item->{ + UserLedgerStatisticVO.Inner inner = new UserLedgerStatisticVO.Inner(); + inner.setCustomId(item.getId()); + inner.setName(item.getCustomerName()); + List LIds = assMap.get(item.getId()); + List eventIds = eventList.stream().filter(it -> LIds.contains(it.getLineid())).map(PqsEventdetail::getEventdetailIndex).collect(Collectors.toList()); + inner.setEventList(eventIds); + inner.setCount(eventIds.size()); + result.add(inner); + }); + return result; + } + + @Override + public PqUserLedgerPO rightImportOpenDetail(LargeScreenCountParam param) { + List deptLineIds = commGeneralService.getLineIdsByRedis(param.getDeptId()); + if(CollUtil.isEmpty(deptLineIds)){ + return null; + } + + PqUserLedgerPO po = pqUserLedgerMapper.selectOne(new LambdaQueryWrapper().eq(PqUserLedgerPO::getId,param.getSearchValue())); + + PqsDicTreePO pqsDicTreePO = pqsDicTreeMapper.selectOne(new LambdaQueryWrapper().eq(PqsDicTreePO::getId,po.getSmallObjType())); + po.setSmallObjType(pqsDicTreePO.getName()); + List pqUserLineAssPOS = pqUserLineAssMapper.selectList(new LambdaQueryWrapper().eq(PqUserLineAssPO::getUserIndex,po.getId())); + List lastAss = pqUserLineAssPOS.stream().filter(it->deptLineIds.contains(it.getLineIndex())).collect(Collectors.toList()); + + List lineIds = lastAss.stream().map(PqUserLineAssPO::getLineIndex).collect(Collectors.toList()); + List ledgerBaseInfoDTOList = pqLineService.getBaseLedger(lineIds,null); + po.setGdName(ledgerBaseInfoDTOList.stream().map(LedgerBaseInfoDTO::getGdName).distinct().collect(Collectors.joining(";"))); + po.setSubstationName(ledgerBaseInfoDTOList.stream().map(LedgerBaseInfoDTO::getStationName).distinct().collect(Collectors.joining(";"))); + po.setBusbarName(ledgerBaseInfoDTOList.stream().map(it->it.getStationName()+"_"+it.getBusBarName()).distinct().collect(Collectors.joining(";"))); + + return po; + } + + @Override + public List gdSelect() { + return pqGdCompanyMapper.selectList(null); + } + + @Override + public List bdSelect() { + return pqSubstationMapper.selectList(null); + } + + private Integer[] getEventCount(List oneList, List assList, List pqsEventdetailList,boolean devFlag) { + Integer[] count = new Integer[]{0, 0}; + //用户的id + if(CollUtil.isNotEmpty(oneList)){ + List userIds = oneList.stream().map(PqUserLedgerPO::getId).collect(Collectors.toList()); + //获取用户关联监测点 + List lineTemIds = assList.stream().filter(it -> userIds.contains(it.getUserIndex())).map(PqUserLineAssPO::getLineIndex).distinct().collect(Collectors.toList()); + //用户的暂降事件次数 + List eventdetailList = pqsEventdetailList.stream().filter(it -> lineTemIds.contains(it.getLineid())).collect(Collectors.toList()); + count[0] = eventdetailList.size(); + if(devFlag) { + List lastLineIds = eventdetailList.stream().map(PqsEventdetail::getLineid).distinct().collect(Collectors.toList()); + List userLastIds = assList.stream().filter(it->lastLineIds.contains(it.getLineIndex())).map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + long devCount = oneList.stream().filter(it->userLastIds.contains(it.getId())).count(); + count[1] = (int) devCount; + } + } + return count; + } + + @Override + public UserLedgerStatisticVO userLedgerStatisticClone(LargeScreenCountParam param) { + UserLedgerStatisticVO result = new UserLedgerStatisticVO(); + + // 1. 获取字典树数据 + List dicTreeList = getAllDicTrees(); + Map treeMap = getDicTreeMap(dicTreeList); + setResultIds(result, treeMap); + + // 2. 获取线路ID列表 + List lineIds = commGeneralService.getLineIdsByDept(param); + if (CollUtil.isEmpty(lineIds)) { + return result; + } + + // 3. 获取用户线路关联数据 + List assList = getUserLineAssociations(lineIds); + if (CollUtil.isEmpty(assList)) { + return result; + } + + // 4. 获取用户台账信息 + Set assUserIds = assList.stream() + .map(PqUserLineAssPO::getUserIndex) + .collect(Collectors.toSet()); + List userLedgers = getUserLedgers(new ArrayList<>(assUserIds),null,false); + if (CollUtil.isEmpty(userLedgers)) { + return result; + } + + // 5. 获取事件和线路数据 + List events = getEventsInTimeRange(param, lineIds); + List lines = getLines(lineIds); + + // 6. 按用户类型分组处理 + Map> userMap = userLedgers.stream() + .collect(Collectors.groupingBy(PqUserLedgerPO::getBigObjType)); + + // 7. 构建结果 + buildResultClone(result, treeMap, userMap, assList, events, lines,dicTreeList); + + return result; + } + + @Override + public Page rightEventOpenClone(LargeScreenCountParam param) { + Page result = new Page<>(); + + // 2. 获取线路ID列表 + List lineIds = commGeneralService.getLineIdsByDept(param); + if (CollUtil.isEmpty(lineIds)) { + return result; + } + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + PqSubstation pqSubstation = null; + if(Objects.nonNull(param.getBdId())){ + pqSubstation = pqSubstationMapper.selectOne(new LambdaQueryWrapper().eq(PqSubstation::getSubIndex,param.getBdId())); + if(Objects.isNull(pqSubstation)){ + return result; + } + queryWrapper.in(PqLine::getSubIndex, pqSubstation.getSubIndex()); + + } + PqGdCompany pqGdCompany = null; + if(Objects.nonNull(param.getGdIndex())){ + pqGdCompany = pqGdCompanyMapper.selectOne(new LambdaQueryWrapper().eq(PqGdCompany::getGdIndex,param.getGdIndex())); + if(Objects.isNull(pqGdCompany)){ + return result; + } + queryWrapper.in(PqLine::getGdIndex, pqGdCompany.getGdIndex()); + } + + if(Objects.nonNull(param.getBdId()) || Objects.nonNull(param.getGdIndex())) { + List pqLineList = pqLineService.list(queryWrapper); + lineIds = pqLineList.stream().map(PqLine::getLineIndex).distinct().collect(Collectors.toList()); + } + + if(CollUtil.isEmpty(lineIds)){ + return result; + } + + // 3. 获取用户线路关联数据 + List assList = getUserLineAssociations(lineIds); + if (CollUtil.isEmpty(assList)) { + return result; + } + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.orderByAsc(PqUserLedgerPO::getSmallObjType,PqUserLedgerPO::getUpdateTime); + if(StrUtil.isNotBlank(param.getBigObjType())){ + //对象大类不为空 + lambdaQueryWrapper.eq(PqUserLedgerPO::getBigObjType,param.getBigObjType()); + } + if(StrUtil.isNotBlank(param.getSmallObjType())){ + //对象大类不为空 + lambdaQueryWrapper.eq(PqUserLedgerPO::getSmallObjType,param.getSmallObjType()); + } + if(Objects.nonNull(param.getGdIndex())){ + lambdaQueryWrapper.eq(PqUserLedgerPO::getPowerSupplyArea,param.getGdIndex()); + } + if(StrUtil.isNotBlank(param.getSearchValue())){ + lambdaQueryWrapper.like(PqUserLedgerPO::getCustomerName,param.getSearchValue()); + } + List assIds = assList.stream().map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + if(assIds.size()>1000){ + List> userIds = CollUtil.split(assIds, 1000); + lambdaQueryWrapper.and(w -> { + for (List ids : userIds) { + w.or(wIn -> wIn.in(PqUserLedgerPO::getId, ids)); + } + }); + }else { + lambdaQueryWrapper.in(PqUserLedgerPO::getId, assIds); + } + + Page page = pqUserLedgerMapper.selectPage(new Page<>(PageFactory.getPageNum(param),PageFactory.getPageSize(param)),lambdaQueryWrapper); + if(CollUtil.isEmpty(page.getRecords())){ + return result; + } + List userIds = page.getRecords().stream().map(PqUserLedgerPO::getId).collect(Collectors.toList()); + List assPOList = assList.stream().filter(it->userIds.contains(it.getUserIndex())).collect(Collectors.toList()); + List lineTemIds = assPOList.stream().map(PqUserLineAssPO::getLineIndex).distinct().collect(Collectors.toList()); + + List ledgerList = pqLineService.getBaseLedger(lineTemIds,null); + + List pqsDeptslineList = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getLineIndex,lineTemIds).eq(PqsDeptsline::getSystype,sysTypeZt).list(); + Map deptLineMap = pqsDeptslineList.stream().collect(Collectors.toMap(PqsDeptsline::getLineIndex,dept->dept)); + + Map deptTemMap = new HashMap<>(); + List pqsDeptsList = pqsDeptsService.lambdaQuery().eq(PqsDepts::getState,1).list(); + Map deptMap = pqsDeptsList.stream().collect(Collectors.toMap(PqsDepts::getDeptsIndex,dept->dept)); + deptLineMap.forEach((k,v)->{ + deptTemMap.put(k,deptMap.get(v.getDeptsIndex()).getDeptsname()); + }); + + + Map> assMap = assPOList.stream().collect(Collectors.groupingBy(PqUserLineAssPO::getUserIndex,Collectors.mapping(PqUserLineAssPO::getLineIndex,Collectors.toList()))); + + for(PqUserLedgerPO po :page.getRecords()){ + if(assMap.containsKey(po.getId())){ + List temIds = assMap.get(po.getId()); + List temList = ledgerList.stream().filter(it->temIds.contains(it.getLineId())).collect(Collectors.toList()); + po.setGdName(temList.stream().map(LedgerBaseInfoDTO::getGdName).distinct().collect(Collectors.joining(";"))); + po.setStation(temList.stream().map(LedgerBaseInfoDTO::getStationName).distinct().collect(Collectors.joining(";"))); + po.setInfo(temList.stream().map(it->it.getStationName()+"_"+it.getBusBarName()).distinct().collect(Collectors.joining(";"))); + po.setDeptName(temList.stream().map(LedgerBaseInfoDTO::getLineId).distinct().map(deptTemMap::get).distinct().collect(Collectors.joining(";"))); + } + + } + + return page; + } + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/LargeScreenCountServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/LargeScreenCountServiceImpl.java new file mode 100644 index 0000000..9bdcc63 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/LargeScreenCountServiceImpl.java @@ -0,0 +1,1539 @@ +package com.njcn.product.event.transientes.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.*; +import cn.hutool.core.util.IdUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.njcn.common.pojo.enums.common.DataStateEnum; +import com.njcn.common.pojo.enums.response.CommonResponseEnum; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.product.event.devcie.mapper.*; +import com.njcn.product.event.devcie.pojo.dto.*; +import com.njcn.product.event.devcie.pojo.po.*; +import com.njcn.product.event.transientes.mapper.*; +import com.njcn.product.event.transientes.pojo.param.LargeScreenCountParam; +import com.njcn.product.event.transientes.pojo.param.MessageEventFeedbackParam; +import com.njcn.product.event.transientes.pojo.po.PqsDepts; +import com.njcn.product.event.transientes.pojo.po.MessageEventFeedback; +import com.njcn.product.event.transientes.pojo.po.MsgEventInfo; +import com.njcn.product.event.transientes.pojo.po.PqsEventdetail; +import com.njcn.product.event.transientes.pojo.po.*; +import com.njcn.product.event.transientes.pojo.vo.*; +import com.njcn.product.event.devcie.service.*; +import com.njcn.product.event.transientes.service.*; +import com.njcn.product.event.devcie.service.PqsDeptslineService; +import com.njcn.redis.utils.RedisUtil; +import com.njcn.web.factory.PageFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Description: + * Date: 2025/06/19 下午 3:06【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class LargeScreenCountServiceImpl implements LargeScreenCountService { + + private final PqsDeptslineService pqsDeptslineService; + private final PqsDeptsService pqsDeptsService; + private final PqLineService pqLineService; + private final PqSubstationService pqSubstationService; + private final PqDeviceService pqDeviceService; + private final PqsEventdetailService pqsEventdetailService; + private final PqLineMapper pqLineMapper; + private final MsgEventInfoService msgEventInfoService; + private final MessageEventFeedbackService messageEventFeedbackService; + private final CommGeneralService commGeneralService; + private final MsgEventConfigService msgEventConfigService; + private final PqsUsersetService pqsUsersetService; + private final PqsUserService pqsUserService; + private final PqLinedetailMapper pqLinedetailMapper; + private final RedisUtil redisUtil; + private final PqsOnlinerateService pqsOnlinerateService; + + private final PqsIntegrityMapper pqsIntegrityMapper; + + private final PqUserLedgerMapper pqUserLedgerMapper; + + private final PqUserLineAssMapper pqUserLineAssMapper; + + private final PqsStationMapMapper pqsStationMapMapper; + + private final PqsDicDataMapper pqsDicDataMapper; + + private final PqGdCompanyMapper pqGdCompanyMapper; + + private final PqSubstationMapper pqSubstationMapper; + + private final PqDeviceDetailMapper pqDeviceDetailMapper; + + @Value("${SYS_TYPE_ZT}") + private String sysTypeZt; + private final static String NAME_KEY = "LineCache:"; + + + private List lineIds = new ArrayList<>(); + + + @Override + public void initLedger(LargeScreenCountParam largeScreenCountParam) { + lineIds = commGeneralService.getLineIdsByDept(largeScreenCountParam); + } + + @Override + public LedgerCountVO scaleStatistics(LargeScreenCountParam largeScreenCountParam) { + LedgerCountVO ledgerCountVO = new LedgerCountVO(); + //根据用户获取当前部门及子部门id + //lineIds = commGeneralService.getLineIdsByDept(largeScreenCountParam); + if (CollectionUtils.isEmpty(lineIds)) { + throw new BusinessException("部门下暂无监测点"); + } + List pqLineList = new ArrayList<>(); + if(lineIds.size()>1000){ + List> listIds = CollUtil.split(lineIds,1000); + for(List itemIds : listIds){ + List temp =pqLineService.lambdaQuery().in(PqLine::getLineIndex, itemIds).list(); + pqLineList.addAll(temp); + } + }else { + List temp = pqLineService.lambdaQuery().in(PqLine::getLineIndex, lineIds).list(); + pqLineList.addAll(temp); + } + //统计总数 + List allLineIds = pqLineList.stream().map(PqLine::getLineIndex).collect(Collectors.toList()); + List allSubList = pqLineList.stream().map(PqLine::getSubIndex).distinct().collect(Collectors.toList()); + long allSubCount =allSubList.stream().count(); + List devList = pqLineList.stream().map(PqLine::getDevIndex).distinct().collect(Collectors.toList()); + long allDevCount = devList.stream().count(); + + long allLineCount = pqLineList.stream().map(PqLine::getLineIndex).distinct().count(); + //在运总数 + List list = pqDeviceService.lambdaQuery().in(PqDevice::getDevIndex, devList).eq(PqDevice::getDevflag, 0).list(); + List runDevList = list.stream().map(PqDevice::getDevIndex).collect(Collectors.toList()); + long runDevCount = runDevList.stream().count(); + List runSubList = list.stream().map(PqDevice::getSubIndex).distinct().collect(Collectors.toList()); + long runSubCount = runSubList.stream().count(); + List ledgerBaseInfoDTOS = pqLineService.getBaseLineInfo(allLineIds); + List runLineList = ledgerBaseInfoDTOS.stream().filter(temp->Objects.equals(temp.getRunFlag(),1)).map(LedgerBaseInfoDTO::getLineId).collect(Collectors.toList()); + + long runLineCount = runLineList.stream().count(); + + + + ledgerCountVO.setAllSubCount(allSubCount); + ledgerCountVO.setAllDevCount(allDevCount); + ledgerCountVO.setAllLineCount(allLineCount); + ledgerCountVO.setRunDevCount(runDevCount); + ledgerCountVO.setRunSubCount(runSubCount); + ledgerCountVO.setRunLineCount(runLineCount); + + ledgerBaseInfoDTOS.stream().forEach(temp->temp.setRunFlag(runLineList.contains(temp.getLineId())?1:0)); + ledgerCountVO.setAllLineList(ledgerBaseInfoDTOS); + List deviceDTOS = pqDeviceService.queryListByIds(devList); + deviceDTOS =deviceDTOS.stream().distinct().collect(Collectors.toList()); + deviceDTOS.forEach(temp-> temp.setRunFlag(runDevList.contains(temp.getDevId())?1:0)); + ledgerCountVO.setAllDevList(deviceDTOS); + List substationDTOS = pqSubstationService.queryListByIds(allSubList); + substationDTOS.forEach(temp->temp.setRunFlag(runSubList.contains(temp.getStationId())?1:0)); + ledgerCountVO.setAllSubList(substationDTOS); + return ledgerCountVO; + } + + @Override + public AlarmAnalysisVO alarmAnalysis(LargeScreenCountParam largeScreenCountParam) { + AlarmAnalysisVO alarmAnalysisVO = new AlarmAnalysisVO(); + //起始时间 + LocalDateTime startTime; + //结束时间 + LocalDateTime endTime; + if (largeScreenCountParam.getType() == 3) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else if (largeScreenCountParam.getType() == 4) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else { + throw new BusinessException("统计类型有误类型"); + } + + //根据用户获取当前部门及子部门id + + if (CollectionUtils.isEmpty(lineIds)) { + throw new BusinessException("部门下暂无监测点"); + + } + List eventdetails = new ArrayList<>(); + if(lineIds.size()>1000){ + List> listIds = CollUtil.split(lineIds,1000); + for(List itemIds : listIds){ + List temp = pqsEventdetailService.lambdaQuery() + .between(PqsEventdetail::getTimeid,startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .in(PqsEventdetail::getLineid,listIds) + .orderByDesc(PqsEventdetail::getTimeid).list() + ; + eventdetails.addAll(temp); + } + }else { + List temp = pqsEventdetailService.lambdaQuery() + .between(PqsEventdetail::getTimeid, startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .in(PqsEventdetail::getLineid,lineIds) + .orderByDesc(PqsEventdetail::getTimeid).list(); + eventdetails.addAll(temp); + } + + Integer eventCount = eventdetails.size(); + // 告警 + List aLarmEvent = eventdetails.stream().filter(temp -> temp.getEventvalue() < 0.5).collect(Collectors.toList()); + // 预警 + List warnEvent = eventdetails.stream().filter(temp -> temp.getEventvalue() >= 0.5&& temp.getEventvalue() <0.9).collect(Collectors.toList()); + List eventIds = eventdetails.stream().map(PqsEventdetail::getEventdetailIndex).collect(Collectors.toList()); + //通知 + + List msgEventInfoList =msgEventInfoService.getMsgByIds(eventIds); + msgEventInfoList = msgEventInfoList.stream().sorted(Comparator.comparing(MsgEventInfo::getSendTime,Comparator.reverseOrder())).collect(Collectors.toList()); + + + + List lookALarmEvent = aLarmEvent.stream().filter(temp ->Objects.equals(temp.getLookFlag(),1 )).collect(Collectors.toList()); + List lookWarnEvent = warnEvent.stream().filter(temp ->Objects.equals(temp.getLookFlag(),1 ) ).collect(Collectors.toList()); + List handleMsg = msgEventInfoList.stream().filter(temp -> Objects.equals(temp.getIsHandle(), 1)).collect(Collectors.toList()); + + Integer aLarmCount =aLarmEvent.size(); + Integer warnCount =warnEvent.size(); + Integer noticeCount =msgEventInfoList.size(); + Integer lookALarmCount =lookALarmEvent.size(); + Integer lookWarnCount =lookWarnEvent.size(); + Integer lookNoticeCount =handleMsg.size(); + + alarmAnalysisVO.setEventCount(eventCount); + alarmAnalysisVO.setALarmCount(aLarmCount); + alarmAnalysisVO.setWarnCount(warnCount); + alarmAnalysisVO.setNoticeCount(noticeCount); + alarmAnalysisVO.setLookALarmCount(lookALarmCount); + alarmAnalysisVO.setLookWarnCount(lookWarnCount); + alarmAnalysisVO.setLookNoticeCount(lookNoticeCount); + +// +// alarmAnalysisVO.setEventdetails(change(eventdetails,msgEventInfoList)); +// alarmAnalysisVO.setALarmEvent(change(aLarmEvent,msgEventInfoList)); +// alarmAnalysisVO.setWarnEvent(change(warnEvent,msgEventInfoList)); +// alarmAnalysisVO.setNoticeEvent(msgEventInfoList); +// alarmAnalysisVO.setLookALarmEvent(change(lookALarmEvent,msgEventInfoList)); +// alarmAnalysisVO.setLookWarnEvent(change(lookWarnEvent,msgEventInfoList)); +// alarmAnalysisVO.setLookNoticeEvent(handleMsg); + + + + return alarmAnalysisVO; + } + + @Override + public List eventTrend(LargeScreenCountParam largeScreenCountParam) { + List eventTrendVOList = new ArrayList<>(); + //起始时间 + LocalDateTime startTime; + //结束时间 + LocalDateTime endTime; + if (largeScreenCountParam.getType() == 3) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else if (largeScreenCountParam.getType() == 4) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else { + throw new BusinessException("统计类型有误类型"); + } + // List deptAndChildren = pqsDeptsService.findDeptAndChildren(largeScreenCountParam.getDeptId()); + //获取对应监测点id + //List deptslines = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getDeptsIndex, deptAndChildren).eq(PqsDeptsline::getSystype, sysTypeZt).list(); + //List deptslineIds = deptslines.stream().map(PqsDeptsline::getLineIndex).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(lineIds)) { + throw new BusinessException("部门下暂无监测点"); + + } + LocalDate startDate = LocalDate.parse(DateUtil.format(startTime, DatePattern.NORM_DATE_PATTERN)); + LocalDate endDate = LocalDate.parse(DateUtil.format(endTime, DatePattern.NORM_DATE_PATTERN)); + List eventdetails = new ArrayList<>(); + if(lineIds.size()>1000){ + List> listIds = CollUtil.split(lineIds,1000); + for(List itemIds : listIds){ + List temp = pqsEventdetailService.lambdaQuery() + .between(PqsEventdetail::getTimeid, startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .in(PqsEventdetail::getLineid,listIds) + .orderByDesc(PqsEventdetail::getTimeid).list(); + eventdetails.addAll(temp); + } + }else { + List temp = pqsEventdetailService.lambdaQuery() + .between(PqsEventdetail::getTimeid, startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .in(PqsEventdetail::getLineid,lineIds) + .orderByDesc(PqsEventdetail::getTimeid).list(); + eventdetails.addAll(temp); + } + + if (Objects.equals(largeScreenCountParam.getEventtype(), 1)) { + List eventIds = eventdetails.stream().map(PqsEventdetail::getEventdetailIndex).collect(Collectors.toList()); + //通知 + List msgEventInfoList =msgEventInfoService.getMsgByIds(eventIds); + // 使用 for 循环处理日期范围 + for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) { + EventTrendVO eventTrendVO = new EventTrendVO(); + eventTrendVO.setLocalDate(date); + LocalDate finalDate = date; + List collect = msgEventInfoList.stream().filter(temp -> Objects.equals(DateUtil.format(temp.getSendTime(), DatePattern.NORM_DATE_PATTERN), DateUtil.format(finalDate.atStartOfDay(), DatePattern.NORM_DATE_PATTERN))).collect(Collectors.toList()); + eventTrendVO.setEventCount(collect.size()); + eventTrendVOList.add(eventTrendVO); + } + + + } else { + + // 使用 for 循环处理日期范围 + for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) { + EventTrendVO eventTrendVO = new EventTrendVO(); + eventTrendVO.setLocalDate(date); + LocalDate finalDate = date; + List collect = eventdetails.stream().filter(temp -> Objects.equals(DateUtil.format(temp.getTimeid(), DatePattern.NORM_DATE_PATTERN), DateUtil.format(finalDate.atStartOfDay(), DatePattern.NORM_DATE_PATTERN))).collect(Collectors.toList()); + eventTrendVO.setEventCount(collect.size()); + eventTrendVOList.add(eventTrendVO); + } + } + + + + return eventTrendVOList; + } + + @Override + public Page eventList(LargeScreenCountParam largeScreenCountParam) { + Page pqsEventdetailPage = new Page<>(largeScreenCountParam.getPageNum(), largeScreenCountParam.getPageSize()); + + //起始时间 + LocalDateTime startTime; + //结束时间 + LocalDateTime endTime; + if (largeScreenCountParam.getType() == 3) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else if (largeScreenCountParam.getType() == 4) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else { + throw new BusinessException("统计类型有误类型"); + } + //根据用户获取当前部门及子部门id + //List deptAndChildren = pqsDeptsService.findDeptAndChildren(largeScreenCountParam.getDeptId()); + //获取对应监测点id + //List deptslines = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getDeptsIndex, deptAndChildren).eq(PqsDeptsline::getSystype, sysTypeZt).list(); + //List deptslineIds = deptslines.stream().map(PqsDeptsline::getLineIndex).distinct().collect(Collectors.toList()); + if (CollectionUtils.isEmpty(lineIds)) { + throw new BusinessException("部门下暂无监测点"); + + } + List pqLineList = pqLineService.getBaseLineInfo(lineIds); + Map ledgerBaseInfoDTOMap = pqLineList.stream().collect(Collectors.toMap(LedgerBaseInfoDTO::getLineId, Function.identity())); + + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (lineIds.size()>1000) { + List> idPartitions = CollUtil.split(lineIds,1000); + + queryWrapper.lambda() + .between(PqsEventdetail::getTimeid, startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .and(ew->{ + for(List pList: idPartitions){ + ew.or(w->w.in(PqsEventdetail::getLineid, pList)); + } + }).orderByDesc(PqsEventdetail::getTimeid); + + + } else { + queryWrapper.lambda() + .between(PqsEventdetail::getTimeid, startTime, endTime) + .in(PqsEventdetail::getLineid, lineIds) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .orderByDesc(PqsEventdetail::getTimeid); + } + //查询需要发送短息处理的部门反推监测点 + + List noticeLineIds = new ArrayList<>(); + List pqsUserList = pqsUserService.lambdaQuery().eq(PqsUser::getState, 1).list(); + if(!CollectionUtils.isEmpty(pqsUserList)){ + List collect = pqsUserList.stream().map(PqsUser::getUserIndex).collect(Collectors.toList()); + List pqsUserSetList = pqsUsersetService.lambdaQuery().eq(PqsUserSet::getIsNotice, 1).in(PqsUserSet::getUserIndex,collect).list(); + List noticeDept = pqsUserSetList.stream().map(temp -> { + return pqsDeptsService.findDeptAndChildren(temp.getDeptsIndex()); + }).flatMap(Collection::stream).distinct().collect(Collectors.toList()); + //获取对应监测点id + if(!CollectionUtils.isEmpty(noticeDept)){ + List noticeLine = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getDeptsIndex, noticeDept).eq(PqsDeptsline::getSystype, sysTypeZt).list(); + noticeLineIds = noticeLine.stream().map(PqsDeptsline::getLineIndex).distinct().collect(Collectors.toList()); + } + + } + + IPage list = pqsEventdetailService.getBaseMapper().selectPage(pqsEventdetailPage,queryWrapper); + List finalNoticeLineIds = noticeLineIds; + List collect = list.getRecords().stream().map(temp -> { + EventDetailVO eventDetailVO = new EventDetailVO(); + eventDetailVO.setEventdetail_index(temp.getEventdetailIndex()); + eventDetailVO.setTimeid(temp.getTimeid()); + eventDetailVO.setMs(temp.getMs()); + eventDetailVO.setWavetype(temp.getWavetype().toString()); + eventDetailVO.setPersisttime(BigDecimal.valueOf(temp.getPersisttime() / 1000).setScale(3, RoundingMode.HALF_UP).toString()); + eventDetailVO.setEventvalue(temp.getEventvalue()); + eventDetailVO.setLookFlag(temp.getLookFlag()); + eventDetailVO.setNoticeFlag(temp.getNoticeFlag()); + if(ledgerBaseInfoDTOMap.containsKey(temp.getLineid())){ + LedgerBaseInfoDTO ledgerBaseInfoDTO = ledgerBaseInfoDTOMap.get(temp.getLineid()); + eventDetailVO.setLineid(ledgerBaseInfoDTO.getLineId()); + eventDetailVO.setPointname(ledgerBaseInfoDTO.getLineName()); + eventDetailVO.setBdname(ledgerBaseInfoDTO.getStationName()); + eventDetailVO.setGdName(ledgerBaseInfoDTO.getGdName()); + eventDetailVO.setBusName(ledgerBaseInfoDTO.getBusBarName()); + eventDetailVO.setObjName(ledgerBaseInfoDTO.getObjName()); + } + eventDetailVO.setNeedDealFlag(finalNoticeLineIds.contains(temp.getLineid())?1:0); + return eventDetailVO; + }).collect(Collectors.toList()); + Page returnpage = new Page<>(largeScreenCountParam.getPageNum(), largeScreenCountParam.getPageSize()); + returnpage.setRecords(collect); + returnpage.setTotal(list.getTotal()); + return returnpage; + } + + @Override + public List noDealEventList(LargeScreenCountParam largeScreenCountParam) { + List result = new ArrayList<>(); + DateTime startTime = DateUtil.beginOfDay(DateUtil.parse(largeScreenCountParam.getSearchBeginTime())); + DateTime endTime = DateUtil.endOfDay(DateUtil.parse(largeScreenCountParam.getSearchEndTime())); + + List deptslineIds = commGeneralService.getLineIdsByRedis(largeScreenCountParam.getDeptId()); + if (CollUtil.isEmpty(deptslineIds)) { + return result; + } + List allList = new ArrayList<>(); + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + if(deptslineIds.size()>1000){ + List> idList = CollUtil.split(deptslineIds,1000); + for(List ids:idList){ + lambdaQueryWrapper.clear(); + lambdaQueryWrapper.between(PqsEventdetail::getTimeid, startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .in(PqsEventdetail::getLineid, ids) + .and(wrapper -> wrapper.eq(PqsEventdetail::getLookFlag, 0).or().isNull(PqsEventdetail::getLookFlag)); + List eventList = pqsEventdetailService.list(lambdaQueryWrapper); + allList.addAll(eventList); + } + }else { + lambdaQueryWrapper.between(PqsEventdetail::getTimeid, startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .in(PqsEventdetail::getLineid, deptslineIds) + .and(wrapper -> wrapper.eq(PqsEventdetail::getLookFlag, 0).or().isNull(PqsEventdetail::getLookFlag)); + List eventList = pqsEventdetailService.list(lambdaQueryWrapper); + allList.addAll(eventList); + } + + + if (CollUtil.isNotEmpty(allList)) { + List ids = allList.stream().map(PqsEventdetail::getLineid).distinct().collect(Collectors.toList()); + List pqLineList = pqLineService.getBaseLineInfo(ids); + Map ledgerBaseInfoDTOMap = pqLineList.stream().collect(Collectors.toMap(LedgerBaseInfoDTO::getLineId, Function.identity())); + + Map userMap; + Map> assMap; + List assList = pqUserLineAssMapper.selectList(new LambdaQueryWrapper().in(PqUserLineAssPO::getLineIndex,ids)); + if (CollUtil.isNotEmpty(assList)) { + List userIds = assList.stream().map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + List poList = pqUserLedgerMapper.selectList(new LambdaQueryWrapper().in(PqUserLedgerPO::getId,userIds)); + userMap = poList.stream().collect(Collectors.toMap(PqUserLedgerPO::getId,Function.identity())); + assMap = assList.stream().collect(Collectors.groupingBy(PqUserLineAssPO::getLineIndex)); + }else { + userMap = new HashMap<>(); + assMap = new HashMap<>(); + } + + for(PqsEventdetail it : allList){ + EventDetailVO eventDetailVO = new EventDetailVO(); + eventDetailVO.setEventdetail_index(it.getEventdetailIndex()); + eventDetailVO.setTimeid(it.getTimeid()); + eventDetailVO.setMs(it.getMs()); + eventDetailVO.setWavetype(it.getWavetype().toString()); + eventDetailVO.setPersisttime(BigDecimal.valueOf(it.getPersisttime() / 1000).setScale(3, RoundingMode.HALF_UP).toString()); + eventDetailVO.setEventvalue(it.getEventvalue()); + if (ledgerBaseInfoDTOMap.containsKey(it.getLineid())) { + LedgerBaseInfoDTO ledgerBaseInfoDTO = ledgerBaseInfoDTOMap.get(it.getLineid()); + eventDetailVO.setLineid(ledgerBaseInfoDTO.getLineId()); + eventDetailVO.setPointname(ledgerBaseInfoDTO.getLineName()); + eventDetailVO.setBdname(ledgerBaseInfoDTO.getStationName()); + eventDetailVO.setGdName(ledgerBaseInfoDTO.getGdName()); + eventDetailVO.setBusName(ledgerBaseInfoDTO.getBusBarName()); + } + if(assMap.containsKey(eventDetailVO.getLineid())) { + List temList = assMap.get(eventDetailVO.getLineid()).stream().map(PqUserLineAssPO::getUserIndex).collect(Collectors.toList()); + String str = temList.stream().map(its -> userMap.containsKey(its)?userMap.get(its).getCustomerName() + "; ":"/").collect(Collectors.joining()); + eventDetailVO.setObjName(str); + } + result.add(eventDetailVO); + } + } + result = result.stream().sorted(Comparator.comparing(EventDetailVO::getTimeid)).collect(Collectors.toList()); + return result; + } + + @Override + public boolean lookEvent(List ids) { + if(ids.size()>1000){ + List> eventIds = CollUtil.split(ids,1000); + for(List needIds : eventIds){ + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.in(PqsEventdetail::getEventdetailIndex, needIds).set(PqsEventdetail::getLookFlag, DataStateEnum.ENABLE.getCode()); + pqsEventdetailService.update(updateWrapper); + } + }else { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.in(PqsEventdetail::getEventdetailIndex, ids).set(PqsEventdetail::getLookFlag, DataStateEnum.ENABLE.getCode()); + pqsEventdetailService.update(updateWrapper); + } + return true; + } + + @Override + public List mapCount(LargeScreenCountParam largeScreenCountParam) { + List result = new ArrayList<>(); + //起始时间 + LocalDateTime startTime; + //结束时间 + LocalDateTime endTime; + if (largeScreenCountParam.getType() == 3) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else if (largeScreenCountParam.getType() == 4) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else { + throw new BusinessException("统计类型有误类型"); + } + //根据用户获取当前部门及子部门id + List deptAndChildren = pqsDeptsService.findDeptAndChildren(largeScreenCountParam.getDeptId()); + deptAndChildren.remove(largeScreenCountParam.getDeptId()); + //获取对应监测点id + List deptslines = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getDeptsIndex, deptAndChildren).eq(PqsDeptsline::getSystype, sysTypeZt).list(); + if (CollectionUtils.isEmpty(deptslines)) { + throw new BusinessException("部门下暂无监测点"); + } + + + List list = pqsDeptsService.lambdaQuery().eq(PqsDepts::getState,1).list(); + Map stringPqsDeptsMap = list.stream().collect(Collectors.toMap(PqsDepts::getDeptsIndex, Function.identity(), (key1, key2) -> key2)); + Map> collect = deptslines.stream().collect(Collectors.groupingBy(PqsDeptsline::getDeptsIndex)); + + List ids = deptslines.stream().map(PqsDeptsline::getLineIndex).collect(Collectors.toList()); + List ledgerBaseInfoDTOS = pqLineService.getBaseLineInfo(ids); + + collect.forEach((k, v) -> { + List temList = v.stream().map(PqsDeptsline::getLineIndex).collect(Collectors.toList()); + MapCountVO mapCountVO = new MapCountVO(); + mapCountVO.setDeptsIndex(k); + mapCountVO.setDeptsName(stringPqsDeptsMap.get(k).getDeptsname()); + + List temLedger = ledgerBaseInfoDTOS.stream().filter(it->temList.contains(it.getLineId())).collect(Collectors.toList()); + mapCountVO.setLineList(temLedger); + mapCountVO.setLineCount(temLedger.size()); + List deptslineIds = v.stream().map(PqsDeptsline::getLineIndex).collect(Collectors.toList()); + List eventdetails = pqsEventdetailService.lambdaQuery() + .between(PqsEventdetail::getTimeid, startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .in(PqsEventdetail::getLineid, deptslineIds).list(); + mapCountVO.setEventCount(eventdetails.size()); + + List eveIdndex = eventdetails.stream().map(PqsEventdetail::getEventdetailIndex).collect(Collectors.toList()); + List temp = new ArrayList<>(); + if(!CollectionUtils.isEmpty(eveIdndex)){ + temp =msgEventInfoService.getMsgByIds(eveIdndex); + } + List change = change(eventdetails,temp); + + mapCountVO.setEventList(change); + mapCountVO.setNoticeCount(temp.size()); + mapCountVO.setNoticeList(temp); + result.add(mapCountVO); + }); + return result; + } + + @Override + public EventMsgDetailVO eventMsgDetail(String eventId) { + EventMsgDetailVO eventMsgDetailVO = new EventMsgDetailVO(); + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(MessageEventFeedback::getEventIndex, eventId); + MessageEventFeedback messageEventFeedback = messageEventFeedbackService.getOne(lambdaQueryWrapper); + if (Objects.nonNull(messageEventFeedback)) { + BeanUtil.copyProperties(messageEventFeedback, eventMsgDetailVO); + if(messageEventFeedback.getIsSensitive() == 1){ + PqsEventdetail pqsEventdetail = pqsEventdetailService.lambdaQuery().eq(PqsEventdetail::getEventdetailIndex,eventId).one(); + PqLinedetail pqLinedetail = pqLinedetailMapper.selectById(pqsEventdetail.getLineid()); + eventMsgDetailVO.setObjName(pqLinedetail.getObjname()); + } + } + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(MsgEventInfo::getEventIndex, eventId); + List msgEventInfoList = msgEventInfoService.list(queryWrapper); + eventMsgDetailVO.setMsgList(msgEventInfoList); + + return eventMsgDetailVO; + } + + @Override + public List msgSendList(LargeScreenCountParam largeScreenCountParam) { + List result = new ArrayList<>(); + // List ids = commGeneralService.getLineIdsByDept(largeScreenCountParam); + if (CollUtil.isEmpty(lineIds)) { + return result; + } + List allEventList = new ArrayList<>(); + if (lineIds.size() > 1000) { + List> listIds = CollUtil.split(lineIds, 1000); + for (List itemIds : listIds) { + List pqsEventdetailList = pqsEventdetailService.lambdaQuery().in(PqsEventdetail::getLineid, itemIds).select(PqsEventdetail::getEventdetailIndex).list(); + allEventList.addAll(pqsEventdetailList); + } + } else { + List pqsEventdetailList = pqsEventdetailService.lambdaQuery().in(PqsEventdetail::getLineid, lineIds).select(PqsEventdetail::getEventdetailIndex).list(); + allEventList.addAll(pqsEventdetailList); + } + if (CollUtil.isEmpty(allEventList)) { + return result; + } + + List eventIds = allEventList.stream().map(PqsEventdetail::getEventdetailIndex).collect(Collectors.toList()); + result =msgEventInfoService.getMsgByIds(eventIds); + result = result.stream().sorted(Comparator.comparing(MsgEventInfo::getSendTime, Comparator.reverseOrder())).collect(Collectors.toList()); + if (result.size() > 200) { + result = result.subList(0, 200); + } + return result; + } + + @Override + public Page hasSendMsgPage(LargeScreenCountParam largeScreenCountParam) { + DateTime start = DateUtil.beginOfDay(DateUtil.parse(largeScreenCountParam.getSearchBeginTime())); + DateTime end = DateUtil.endOfDay(DateUtil.parse(largeScreenCountParam.getSearchEndTime())); + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(!StringUtils.isEmpty(largeScreenCountParam.getSendResult()),MsgEventInfo::getSendResult,largeScreenCountParam.getSendResult()); + lambdaQueryWrapper.orderByDesc(MsgEventInfo::getSendTime).between(MsgEventInfo::getSendTime,start,end); + return msgEventInfoService.page(new Page<>(PageFactory.getPageNum(largeScreenCountParam),PageFactory.getPageSize(largeScreenCountParam)),lambdaQueryWrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean msgHandle(MessageEventFeedbackParam messageEventFeedbackParam) { + + PqsEventdetail pqsEventdetail = pqsEventdetailService.lambdaQuery().eq(PqsEventdetail::getEventdetailIndex,messageEventFeedbackParam.getEventIndex()).one(); + if(Objects.isNull(pqsEventdetail.getLookFlag())|| pqsEventdetail.getLookFlag() == 0){ + throw new BusinessException(CommonResponseEnum.FAIL,"当前事件暂未处理,请先处理!"); + } + + MessageEventFeedback messageEventFeedback = messageEventFeedbackService.lambdaQuery().eq(MessageEventFeedback::getEventIndex, messageEventFeedbackParam.getEventIndex()).one(); + if (Objects.nonNull(messageEventFeedback)) { + throw new BusinessException(CommonResponseEnum.FAIL,"请勿重复处理!"); + } + MessageEventFeedback po = new MessageEventFeedback(); + BeanUtil.copyProperties(messageEventFeedbackParam, po); + po.setId(IdUtil.simpleUUID()); + messageEventFeedbackService.save(po); + pqsEventdetailService.lambdaUpdate().set(PqsEventdetail::getNoticeFlag,DataStateEnum.ENABLE.getCode()).eq(PqsEventdetail::getEventdetailIndex,messageEventFeedbackParam.getEventIndex()).update(); + msgEventInfoService.lambdaUpdate().set(MsgEventInfo::getIsHandle,DataStateEnum.ENABLE.getCode()).eq(MsgEventInfo::getEventIndex,messageEventFeedbackParam.getEventIndex()).update(); + return true; + } + + @Override + public AlarmAnalysisVO alarmAnalysisDetail(LargeScreenCountParam largeScreenCountParam) { + AlarmAnalysisVO alarmAnalysisVO = new AlarmAnalysisVO(); + //起始时间 + LocalDateTime startTime; + //结束时间 + LocalDateTime endTime; + if (largeScreenCountParam.getType() == 3) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else if (largeScreenCountParam.getType() == 4) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else { + throw new BusinessException("统计类型有误类型"); + } + + //根据用户获取当前部门及子部门id + //List deptAndChildren = pqsDeptsService.findDeptAndChildren(largeScreenCountParam.getDeptId()); + //获取对应监测点id + //List deptslines = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getDeptsIndex, deptAndChildren).eq(PqsDeptsline::getSystype, sysTypeZt).list(); + //List deptslineIds = deptslines.stream().map(PqsDeptsline::getLineIndex).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(lineIds)) { + throw new BusinessException("部门下暂无监测点"); + + } + List eventdetails = new ArrayList<>(); + if(lineIds.size()>1000){ + List> listIds = CollUtil.split(lineIds,1000); + for(List itemIds : listIds){ + List temp = pqsEventdetailService.lambdaQuery() + .between(PqsEventdetail::getTimeid,startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .in(PqsEventdetail::getLineid,listIds) + .orderByDesc(PqsEventdetail::getTimeid).list() + ; + eventdetails.addAll(temp); + } + }else { + List temp = pqsEventdetailService.lambdaQuery() + .between(PqsEventdetail::getTimeid, startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .in(PqsEventdetail::getLineid,lineIds) + .orderByDesc(PqsEventdetail::getTimeid).list(); + eventdetails.addAll(temp); + } + + Integer eventCount = eventdetails.size(); + // 告警 + List aLarmEvent = eventdetails.stream().filter(temp -> temp.getEventvalue() < 0.5).collect(Collectors.toList()); + // 预警 + List warnEvent = eventdetails.stream().filter(temp -> temp.getEventvalue() >= 0.5&& temp.getEventvalue() <0.9).collect(Collectors.toList()); + List eventIds = eventdetails.stream().map(PqsEventdetail::getEventdetailIndex).collect(Collectors.toList()); + //通知 + List msgEventInfoList =msgEventInfoService.getMsgByIds(eventIds); + + msgEventInfoList = msgEventInfoList.stream().sorted(Comparator.comparing(MsgEventInfo::getSendTime,Comparator.reverseOrder())).collect(Collectors.toList()); + + + + List lookALarmEvent = aLarmEvent.stream().filter(temp ->Objects.equals(temp.getLookFlag(),1 )).collect(Collectors.toList()); + List lookWarnEvent = warnEvent.stream().filter(temp ->Objects.equals(temp.getLookFlag(),1 ) ).collect(Collectors.toList()); + List handleMsg = msgEventInfoList.stream().filter(temp -> Objects.equals(temp.getIsHandle(), 1)).collect(Collectors.toList()); + + Integer aLarmCount =aLarmEvent.size(); + Integer warnCount =warnEvent.size(); + Integer noticeCount =msgEventInfoList.size(); + Integer lookALarmCount =lookALarmEvent.size(); + Integer lookWarnCount =lookWarnEvent.size(); + Integer lookNoticeCount =handleMsg.size(); + + alarmAnalysisVO.setEventCount(eventCount); + alarmAnalysisVO.setALarmCount(aLarmCount); + alarmAnalysisVO.setWarnCount(warnCount); + alarmAnalysisVO.setNoticeCount(noticeCount); + alarmAnalysisVO.setLookALarmCount(lookALarmCount); + alarmAnalysisVO.setLookWarnCount(lookWarnCount); + alarmAnalysisVO.setLookNoticeCount(lookNoticeCount); + + + alarmAnalysisVO.setEventdetails(change(eventdetails,msgEventInfoList)); + alarmAnalysisVO.setALarmEvent(change(aLarmEvent,msgEventInfoList)); + alarmAnalysisVO.setWarnEvent(change(warnEvent,msgEventInfoList)); + alarmAnalysisVO.setNoticeEvent(msgEventInfoList); + alarmAnalysisVO.setLookALarmEvent(change(lookALarmEvent,msgEventInfoList)); + alarmAnalysisVO.setLookWarnEvent(change(lookWarnEvent,msgEventInfoList)); + alarmAnalysisVO.setLookNoticeEvent(handleMsg); + + + + return alarmAnalysisVO; + } + + @Override + public Page eventTablePage(LargeScreenCountParam largeScreenCountParam) { + Page result = new Page<>(PageFactory.getPageNum(largeScreenCountParam),PageFactory.getPageSize(largeScreenCountParam)); + //起始时间 + LocalDateTime startTime; + //结束时间 + LocalDateTime endTime; + if (largeScreenCountParam.getType() == 3) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfMonth(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else if (largeScreenCountParam.getType() == 4) { + //起始时间 + startTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.beginOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + //结束时间 + endTime = LocalDateTimeUtil.parse(DateUtil.format(DateUtil.endOfWeek(new Date()), DatePattern.NORM_DATETIME_FORMATTER), DatePattern.NORM_DATETIME_FORMATTER); + } else { + throw new BusinessException("统计类型有误类型"); + } + + //List lineIds = commGeneralService.getLineIdsByDept(largeScreenCountParam); + if (CollectionUtils.isEmpty(lineIds)) { + throw new BusinessException("部门下暂无监测点"); + } + + + + List eventType = msgEventConfigService.getEventType(); + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper + .between(PqsEventdetail::getTimeid,startTime, endTime) + .in(PqsEventdetail::getWavetype,eventType) + .orderByDesc(PqsEventdetail::getTimeid); + if(Objects.nonNull(largeScreenCountParam.getEventDeep())){ + if (largeScreenCountParam.getEventDeep() == 0) { + lambdaQueryWrapper.ge(PqsEventdetail::getEventvalue, 0.5).lt(PqsEventdetail::getEventvalue, 0.9); + } else if (largeScreenCountParam.getEventDeep() == 1) { + lambdaQueryWrapper.lt(PqsEventdetail::getEventvalue, 0.5); + } + } + if(lineIds.size()>1000){ + List> splitList = CollUtil.split(lineIds,1000); + lambdaQueryWrapper.and(ew->{ + for (int i = 0; i < splitList.size(); i++) { + List batch = splitList.get(i); + if (i == 0) { + ew.in(PqsEventdetail::getLineid, batch); // 第一个条件不加 or + } else { + ew.or().in(PqsEventdetail::getLineid, batch); // 后续条件加 or + } + } + }); + }else { + lambdaQueryWrapper.in(PqsEventdetail::getLineid, lineIds); + } + Page page = pqsEventdetailService.page(new Page<>(PageFactory.getPageNum(largeScreenCountParam),PageFactory.getPageSize(largeScreenCountParam)),lambdaQueryWrapper); + result.setTotal(page.getTotal()); + if(CollUtil.isEmpty(page.getRecords())){ + return result; + } + List ids = page.getRecords().stream().map(PqsEventdetail::getLineid).distinct().collect(Collectors.toList()); + List pqLineList = pqLineService.getBaseLineInfo(ids); + Map ledgerBaseInfoDTOMap = pqLineList.stream().collect(Collectors.toMap(LedgerBaseInfoDTO::getLineId, Function.identity())); + + List resultList = new ArrayList<>(); + for(PqsEventdetail pqsEventdetail : page.getRecords()){ + EventDetailVO eventDetailVO = new EventDetailVO(); + BeanUtil.copyProperties(pqsEventdetail,eventDetailVO); + if(ledgerBaseInfoDTOMap.containsKey(pqsEventdetail.getLineid())){ + LedgerBaseInfoDTO ledgerBaseInfoDTO = ledgerBaseInfoDTOMap.get(pqsEventdetail.getLineid()); + eventDetailVO.setLineid(ledgerBaseInfoDTO.getLineId()); + eventDetailVO.setPointname(ledgerBaseInfoDTO.getLineName()); + eventDetailVO.setBdname(ledgerBaseInfoDTO.getStationName()); + eventDetailVO.setGdName(ledgerBaseInfoDTO.getGdName()); + eventDetailVO.setBusName(ledgerBaseInfoDTO.getBusBarName()); + eventDetailVO.setObjName(ledgerBaseInfoDTO.getObjName()); + } + resultList.add(eventDetailVO); + } + result.setRecords(resultList); + return result; + } + + @Override + public DeviceCountVO devFlagCount(LargeScreenCountParam largeScreenCountParam) { + DeviceCountVO deviceCountVO = new DeviceCountVO(); + List pqLineList = (List) redisUtil.getObjectByKey( NAME_KEY+ StrUtil.DASHED+"pqLineList"); + List deptslineIds = (List) redisUtil.getObjectByKey( NAME_KEY+ StrUtil.DASHED+largeScreenCountParam.getDeptId()); + pqLineList = pqLineList.stream().filter(temp->deptslineIds.contains(temp.getLineIndex())).collect(Collectors.toList()); + List devIndexs = pqLineList.stream().map(PqLine::getDevIndex).collect(Collectors.toList()); + + //在运总数 + List list = pqDeviceService.lambdaQuery().in(PqDevice::getDevIndex, devIndexs).eq(PqDevice::getDevflag, 0).list(); + + long onLine = list.stream().filter(temp -> Objects.equals(temp.getStatus(), 1)).count(); + long Offline = list.stream().filter(temp -> Objects.equals(temp.getStatus(), 0)).count(); + deviceCountVO.setAllCount(list.size()); + // deviceCountVO.setOnLine((int) onLine); + // deviceCountVO.setOffLine((int) Offline); + + //临时调整 + deviceCountVO.setOnLine(list.size()); + deviceCountVO.setOffLine(0); + + return deviceCountVO; + } + + @Override + public List devDetail(LargeScreenCountParam largeScreenCountParam) { + DeviceCountVO deviceCountVO = new DeviceCountVO(); + List pqLineList = (List) redisUtil.getObjectByKey( NAME_KEY+ StrUtil.DASHED+"pqLineList"); + List deptslineIds = (List) redisUtil.getObjectByKey( NAME_KEY+ StrUtil.DASHED+largeScreenCountParam.getDeptId()); + +// List deptAndChildren = pqsDeptsService.findDeptAndChildren(largeScreenCountParam.getDeptId()); +// List deptslines = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getDeptsIndex, deptAndChildren).eq(PqsDeptsline::getSystype, sysTypeZt).list(); +// List deptslineIds = deptslines.stream().map(PqsDeptsline::getLineIndex).collect(Collectors.toList()); + pqLineList = pqLineList.stream().filter(temp->deptslineIds.contains(temp.getLineIndex())).collect(Collectors.toList()); + List devIndexs = pqLineList.stream().map(PqLine::getDevIndex).collect(Collectors.toList()); + + + List deviceDTOList = pqDeviceService.queryListByIds(devIndexs); + deviceDTOList = deviceDTOList.stream().filter(temp->Objects.equals(temp.getDevFlag(),0)).collect(Collectors.toList()); + return deviceDTOList; + } + + @Override + public List regionDevCount(LargeScreenCountParam largeScreenCountParam) { + List result = new ArrayList<>(); + List pqLineList = (List) redisUtil.getObjectByKey( NAME_KEY+ StrUtil.DASHED+"pqLineList"); + List deptAndChildren = pqsDeptsService.findDeptAndChildren(largeScreenCountParam.getDeptId()); + if(deptAndChildren.size()>1){ + deptAndChildren.remove(largeScreenCountParam.getDeptId()); + } + List pqDeviceList = pqDeviceService.lambdaQuery().eq(PqDevice::getDevflag, 0).list(); + +// List deptslines = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getDeptsIndex, deptAndChildren).eq(PqsDeptsline::getSystype, sysTypeZt).list(); + List list = pqsDeptsService.getDeptList(deptAndChildren); + list.forEach(temp->{ + RegionDevCountVO regionDevCountVO = new RegionDevCountVO(); + regionDevCountVO.setDeptsIndex(temp.getDeptsIndex()); + regionDevCountVO.setDeptsname(temp.getDeptsname()); + regionDevCountVO.setAreaName(temp.getAreaName()); + List deptslineIds =(List) redisUtil.getObjectByKey( NAME_KEY+ StrUtil.DASHED+temp.getDeptsIndex()); + List collect = pqLineList.stream().filter(pqLine -> deptslineIds.contains(pqLine.getLineIndex())).collect(Collectors.toList()); + List devIndexs = collect.stream().map(PqLine::getDevIndex).collect(Collectors.toList()); + List tempDeviceList = pqDeviceList.stream().filter(pqDevice -> devIndexs.contains(pqDevice.getDevIndex())).collect(Collectors.toList()); + //在运总数 + //TODO 零时调整 + //long onLine = tempDeviceList.stream().filter(pqDevice -> Objects.equals(pqDevice.getStatus(), 1)).count(); + //long Offline = tempDeviceList.stream().filter(pqDevice ->Objects.equals(pqDevice.getStatus(), 0)).count(); + regionDevCountVO.setAllCount(tempDeviceList.size()); + + regionDevCountVO.setOnLine((int) tempDeviceList.size()); + regionDevCountVO.setOffLine((int) 0); + result.add(regionDevCountVO); + }); + return result; + } + + private List getUserLineAssociations(List lineIds){ + LambdaQueryWrapper assQuery = new LambdaQueryWrapper<>(); + assQuery.in(PqUserLineAssPO::getLineIndex, lineIds); + + if(lineIds.size()>1000){ + List> lineList = CollUtil.split(lineIds, 1000); + assQuery.and(w -> { + for (List ids : lineList) { + w.or(wIn -> wIn.in(PqUserLineAssPO::getLineIndex, ids)); + } + }); + }else { + assQuery.in(PqUserLineAssPO::getLineIndex, lineIds); + } + + return pqUserLineAssMapper.selectList(assQuery); + } + + private List getUserLedgers(List assUserIds){ + LambdaQueryWrapper userWrapper = new LambdaQueryWrapper<>(); + if(assUserIds.size()>1000){ + List> assUserIdsList = CollUtil.split(assUserIds, 1000); + userWrapper.and(w -> { + for (List ids : assUserIdsList) { + w.or(wIn -> wIn.in(PqUserLedgerPO::getId, ids)); + } + }); + }else { + userWrapper.in(PqUserLedgerPO::getId, assUserIds); + } + return pqUserLedgerMapper.selectList(userWrapper); + } + @Override + public List substationCount(LargeScreenCountParam largeScreenCountParam) { + LocalDateTime startTime = largeScreenCountParam.getStartTime().atStartOfDay(); + LocalDateTime endTime = LocalDateTimeUtil.endOfDay(largeScreenCountParam.getEndTime().atStartOfDay()); + + List deptslineIds = commGeneralService.getLineIdsByRedis(largeScreenCountParam.getDeptId()); + if(CollUtil.isEmpty(deptslineIds)){ + return new ArrayList<>(); + } + //查询暂态事件 + List eventdetails = new ArrayList<>(); + if(deptslineIds.size()>1000){ + List> listIds = CollUtil.split(deptslineIds,1000); + for(List itemIds : listIds){ + List temp = pqsEventdetailService.lambdaQuery() + .between(PqsEventdetail::getTimeid,startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .in(PqsEventdetail::getLineid,itemIds) + .orderByDesc(PqsEventdetail::getTimeid).list(); + eventdetails.addAll(temp); + } + }else { + List temp = pqsEventdetailService.lambdaQuery() + .between(PqsEventdetail::getTimeid, startTime, endTime) + .in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .in(PqsEventdetail::getLineid,deptslineIds) + .orderByDesc(PqsEventdetail::getTimeid).list(); + eventdetails.addAll(temp); + } + + if(CollUtil.isEmpty(eventdetails)){ + return new ArrayList<>(); + } + + List lineIds = eventdetails.stream().map(PqsEventdetail::getLineid).distinct().collect(Collectors.toList()); + //List pqLineList = (List) redisUtil.getObjectByKey( NAME_KEY+ StrUtil.DASHED+"pqLineList"); + //pqLineList = pqLineList.stream().filter(temp->lineIds.contains(temp.getLineIndex())).collect(Collectors.toList()); + + List assPOList = getUserLineAssociations(lineIds); + List userIds = assPOList.stream().map(PqUserLineAssPO::getUserIndex).distinct().collect(Collectors.toList()); + + Map> lineAssMap = assPOList.stream().collect(Collectors.groupingBy(PqUserLineAssPO::getLineIndex,Collectors.mapping(PqUserLineAssPO::getUserIndex,Collectors.toList()))); + + List pqUserLedgerPOList = getUserLedgers(userIds); + + List subStationCountVOS = new ArrayList<>(); + List ledgerBaseInfoDTOS = pqLineService.getBaseLedger(lineIds,null); + //Map ledgerBaseInfoDTOMap = ledgerBaseInfoDTOS.stream().collect(Collectors.toMap(LedgerBaseInfoDTO::getLineId, Function.identity())); + + List subIndexs = ledgerBaseInfoDTOS.stream().map(LedgerBaseInfoDTO::getStationId).collect(Collectors.toList()); + List pqsStationMapList = pqsStationMapMapper.selectList(new LambdaQueryWrapper().in(PqsStationMap::getSubIndex,subIndexs).eq(PqsStationMap::getState,1)); + Map stationMapMap = pqsStationMapList.stream().collect(Collectors.toMap(PqsStationMap::getSubIndex,dept->dept)); + + Map> substationDTOMap= ledgerBaseInfoDTOS.stream().collect(Collectors.groupingBy(LedgerBaseInfoDTO::getStationId)); + + Map> collect = eventdetails.stream().collect(Collectors.groupingBy(PqsEventdetail::getLineid)); + + substationDTOMap.forEach((k,v)->{ + LedgerBaseInfoDTO ledgerBaseInfoDTO = v.get(0); + SubStationCountVO subStationCountVO = new SubStationCountVO(); + subStationCountVO.setStationId(k); + subStationCountVO.setStationName(ledgerBaseInfoDTO.getStationName()); + subStationCountVO.setGdName(ledgerBaseInfoDTO.getGdName()); + + if(stationMapMap.containsKey(k.longValue())){ + PqsStationMap pqsStationMap = stationMapMap.get(k.longValue()); + if(Objects.nonNull(pqsStationMap.getLongItude())){ + subStationCountVO.setLongitude(pqsStationMap.getLongItude()); + }else { + subStationCountVO.setLongitude(0); + } + + if(Objects.nonNull(pqsStationMap.getLatItude())){ + subStationCountVO.setLatitude(pqsStationMap.getLatItude()); + }else { + subStationCountVO.setLatitude(0); + } + }else { + subStationCountVO.setLongitude(0); + subStationCountVO.setLatitude(0); + } + List tempLineIds = v.stream().map(LedgerBaseInfoDTO::getLineId).collect(Collectors.toList()); + subStationCountVO.setLineCount(tempLineIds.size()); + List tempEventList = eventdetails.stream().filter(temp -> tempLineIds.contains(temp.getLineid())).collect(Collectors.toList()); + subStationCountVO.setEventCount(tempEventList.size()); + v.forEach(item->{ + String obj = ""; + if(lineAssMap.containsKey(item.getLineId())){ + List userIndex = lineAssMap.get(item.getLineId()); + obj = pqUserLedgerPOList.stream().filter(it->userIndex.contains(it.getId())).map(PqUserLedgerPO::getCustomerName).collect(Collectors.joining(";")); + } + item.setObjName(StrUtil.isNotBlank(obj)? obj:"/"); + item.setEventCount(collect.get(item.getLineId()).size()); + }); + subStationCountVO.setLineEventDetails(v); + subStationCountVOS.add(subStationCountVO); + }); + return subStationCountVOS; + } + + @Override + public Page eventPage(LargeScreenCountParam largeScreenCountParam) { + Page pqsEventdetailPage = new Page<>(largeScreenCountParam.getPageNum(), largeScreenCountParam.getPageSize()); + LocalDateTime startTime = largeScreenCountParam.getStartTime().atStartOfDay(); + LocalDateTime endTime = LocalDateTimeUtil.endOfDay(largeScreenCountParam.getEndTime().atStartOfDay()); + + List deptslineIds = commGeneralService.getLineIdsByRedis(largeScreenCountParam.getDeptId()); + + List ledgerList = new ArrayList<>(); + List pqUserLedgerPOList = new ArrayList<>(); + List assList = new ArrayList<>(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper + .between(PqsEventdetail::getTimeid, startTime, endTime) + .gt(PqsEventdetail::getPersisttime,msgEventConfigService.getEventDuration()) + .le(PqsEventdetail::getEventvalue,msgEventConfigService.getEventValue()) + .orderByDesc(PqsEventdetail::getTimeid); + + if(Objects.nonNull(largeScreenCountParam.getEventtype())){ + queryWrapper.eq(PqsEventdetail::getWavetype,largeScreenCountParam.getEventtype()); + }else { + queryWrapper.in(PqsEventdetail::getWavetype,msgEventConfigService.getEventType()); + } + + if(Objects.nonNull(largeScreenCountParam.getEventDurationMin()) ||Objects.nonNull(largeScreenCountParam.getEventDurationMax())){ + queryWrapper.gt(Objects.nonNull(largeScreenCountParam.getEventDurationMin()),PqsEventdetail::getPersisttime,largeScreenCountParam.getEventDurationMin()); + queryWrapper.lt(Objects.nonNull(largeScreenCountParam.getEventDurationMax()),PqsEventdetail::getPersisttime,largeScreenCountParam.getEventDurationMax()); + } + + if(Objects.nonNull(largeScreenCountParam.getEventValueMin()) ||Objects.nonNull(largeScreenCountParam.getEventValueMax())){ + queryWrapper.gt(Objects.nonNull(largeScreenCountParam.getEventValueMin()),PqsEventdetail::getEventvalue,largeScreenCountParam.getEventValueMin()); + queryWrapper.lt(Objects.nonNull(largeScreenCountParam.getEventValueMax()),PqsEventdetail::getEventvalue,largeScreenCountParam.getEventValueMax()); + } + + if(StrUtil.isNotBlank(largeScreenCountParam.getSearchValue())){ + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.select(PqUserLedgerPO::getId,PqUserLedgerPO::getCustomerName); + lambdaQueryWrapper.like(PqUserLedgerPO::getCustomerName,largeScreenCountParam.getSearchValue()); + List lineTemUserIds = new ArrayList<>(); + pqUserLedgerPOList = pqUserLedgerMapper.selectList(lambdaQueryWrapper); + if(CollUtil.isNotEmpty(pqUserLedgerPOList)) { + List userIds = pqUserLedgerPOList.stream().map(PqUserLedgerPO::getId).collect(Collectors.toList()); + assList = pqUserLineAssMapper.selectList(new LambdaQueryWrapper().in(PqUserLineAssPO::getUserIndex, userIds)); + List assIds = assList.stream().map(PqUserLineAssPO::getLineIndex).distinct().collect(Collectors.toList()); + lineTemUserIds = deptslineIds.stream().filter(assIds::contains).collect(Collectors.toList()); + } + + ledgerList = pqLineService.getBaseLedger(deptslineIds,largeScreenCountParam.getSearchValue()); + lineTemUserIds.addAll(ledgerList.stream().map(LedgerBaseInfoDTO::getLineId).collect(Collectors.toList())); + if(CollUtil.isEmpty(lineTemUserIds)){ + return new Page<>(); + } + if (lineTemUserIds.size()>1000) { + List> idPartitions = CollUtil.split(lineTemUserIds,1000); + queryWrapper.and(ew->{ + for(List pList: idPartitions){ + ew.or(w->w.in(PqsEventdetail::getLineid, pList)); + } + }); + } else { + queryWrapper.in(PqsEventdetail::getLineid, lineTemUserIds); + } + + }else { + if (deptslineIds.size()>1000) { + List> idPartitions = CollUtil.split(deptslineIds,1000); + queryWrapper.and(ew->{ + for(List pList: idPartitions){ + ew.or(w->w.in(PqsEventdetail::getLineid, pList)); + } + }); + } else { + queryWrapper.in(PqsEventdetail::getLineid, deptslineIds); + } + } + + IPage list = pqsEventdetailService.getBaseMapper().selectPage(pqsEventdetailPage,queryWrapper); + if(CollUtil.isEmpty(list.getRecords())){ + return new Page<>(); + } + List pageLineIds = list.getRecords().stream().map(PqsEventdetail::getLineid).distinct().collect(Collectors.toList()); + List pageLedger = pqLineService.getBaseLedger(pageLineIds,null); + Map ledgerBaseInfoDTOMap = pageLedger.stream().collect(Collectors.toMap(LedgerBaseInfoDTO::getLineId, Function.identity())); + + List assLastList = pqUserLedgerMapper.getUserByParam(pageLineIds,null); + Map> mapObj = assLastList.stream().collect(Collectors.groupingBy(PqUserLineAssPO::getLineIndex,Collectors.mapping(PqUserLineAssPO::getUserName,Collectors.toList()))); + + List collect = list.getRecords().stream().map(temp -> { + EventDetailVO eventDetailVO = new EventDetailVO(); + eventDetailVO.setEventdetail_index(temp.getEventdetailIndex()); + eventDetailVO.setTimeid(temp.getTimeid()); + eventDetailVO.setMs(temp.getMs()); + eventDetailVO.setWavetype(temp.getWavetype().toString()); + eventDetailVO.setPersisttime(BigDecimal.valueOf(temp.getPersisttime() / 1000).setScale(3, RoundingMode.HALF_UP).toString()); + eventDetailVO.setEventvalue(temp.getEventvalue()); + eventDetailVO.setLookFlag(temp.getLookFlag()); + eventDetailVO.setNoticeFlag(temp.getNoticeFlag()); + if(ledgerBaseInfoDTOMap.containsKey(temp.getLineid())){ + LedgerBaseInfoDTO ledgerBaseInfoDTO = ledgerBaseInfoDTOMap.get(temp.getLineid()); + eventDetailVO.setLineid(ledgerBaseInfoDTO.getLineId()); + eventDetailVO.setPointname(ledgerBaseInfoDTO.getLineName()); + eventDetailVO.setBdname(ledgerBaseInfoDTO.getStationName()); + eventDetailVO.setGdName(ledgerBaseInfoDTO.getGdName()); + eventDetailVO.setBusName(ledgerBaseInfoDTO.getBusBarName()); + eventDetailVO.setObjName(ledgerBaseInfoDTO.getObjName()); + } + String objName ="/"; + if(mapObj.containsKey(eventDetailVO.getLineid())){ + objName = String.join(";", mapObj.get(eventDetailVO.getLineid())); + } + eventDetailVO.setObjName(objName); + return eventDetailVO; + }).collect(Collectors.toList()); + Page returnpage = new Page<>(largeScreenCountParam.getPageNum(), largeScreenCountParam.getPageSize()); + returnpage.setRecords(collect); + returnpage.setTotal(list.getTotal()); + return returnpage; + } + @Override + public Page devicePage(LargeScreenCountParam largeScreenCountParam) { + TimeInterval timeInterval = new TimeInterval(); + log.info("开始查询:"+timeInterval.intervalMs()+"ms; "+timeInterval.intervalSecond()+"s"); + LocalDateTime startTime = largeScreenCountParam.getStartTime().atStartOfDay(); + LocalDateTime endTime = LocalDateTimeUtil.endOfDay(largeScreenCountParam.getEndTime().atStartOfDay()); + Page pqsEventdetailPage = new Page<>(largeScreenCountParam.getPageNum(), largeScreenCountParam.getPageSize()); + List pqLineList = (List) redisUtil.getObjectByKey( NAME_KEY+ StrUtil.DASHED+"pqLineList"); + + + List deptslineIds = commGeneralService.getLineIdsByRedis(largeScreenCountParam.getDeptId()); + + + if(Objects.isNull(largeScreenCountParam.getGdIndex())){ + pqLineList = pqLineList.stream().filter(temp->deptslineIds.contains(temp.getLineIndex())).collect(Collectors.toList()); + }else { + pqLineList = pqLineList.stream().filter(temp->deptslineIds.contains(temp.getLineIndex()) && Objects.equals(temp.getGdIndex(),largeScreenCountParam.getGdIndex())).collect(Collectors.toList()); + } + if(CollUtil.isEmpty(pqLineList)){ + return new Page<>(); + } + + List devIndexs = pqLineList.stream().map(PqLine::getDevIndex).distinct().collect(Collectors.toList()); + log.info("完成从redis获取信息:"+timeInterval.intervalMs()+"ms; "+timeInterval.intervalSecond()+"s"); + //在运总数 + + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + if(StrUtil.isNotBlank(largeScreenCountParam.getState())){ + + if(largeScreenCountParam.getState().equals("0")){ + return new Page<>(); + } + } + if(StrUtil.isNotBlank(largeScreenCountParam.getDevName())){ + lambdaQueryWrapper.like(StrUtil.isNotEmpty(largeScreenCountParam.getDevName()),PqDevice::getName,largeScreenCountParam.getDevName()); + } + lambdaQueryWrapper.in(PqDevice::getDevIndex, devIndexs); + + List pqDeviceList = pqDeviceService.list(lambdaQueryWrapper); + + log.info("完成设备查询sql:"+timeInterval.intervalMs()+"ms; "+timeInterval.intervalSecond()+"s"); + List runDevList = pqDeviceList.stream().map(PqDevice::getDevIndex).collect(Collectors.toList()); + if(CollUtil.isEmpty(runDevList)){ + return new Page<>(); + } + + List pqsDicDataList = pqsDicDataMapper.selectList(new LambdaQueryWrapper().eq(PqsDicData::getDicType,"cbb2de8a-87da-4ae9-a35c-aaab999c7bc7")); + Map pqsDicDataMap = pqsDicDataList.stream().collect(Collectors.toMap(PqsDicData::getDicIndex,Function.identity())); + + List bdList = new ArrayList<>(); + if(StrUtil.isNotBlank(largeScreenCountParam.getSearchValue())){ + List substationList = pqSubstationMapper.selectList(new LambdaQueryWrapper().like(PqSubstation::getName,largeScreenCountParam.getSearchValue())); + bdList = substationList.stream().map(PqSubstation::getSubIndex).collect(Collectors.toList()); + } + + Page page = pqDeviceService.lambdaQuery().in(CollUtil.isNotEmpty(bdList),PqDevice::getSubIndex,bdList) + .in(PqDevice::getDevIndex,runDevList).page(new Page<>(PageFactory.getPageNum(largeScreenCountParam),PageFactory.getPageSize(largeScreenCountParam))); + log.info("完成设备部门查询:"+timeInterval.intervalMs()+"ms; "+timeInterval.intervalSecond()+"s"); + // pqsEventdetailPage = pqDeviceService.selectDeviceDTOPage(pqsEventdetailPage,largeScreenCountParam.getSearchValue(),runDevList); + log.info("完成设备分页查询sql:"+timeInterval.intervalMs()+"ms; "+timeInterval.intervalSecond()+"s"); + + List deviceDTOList = page.getRecords(); + if(!CollectionUtils.isEmpty(deviceDTOList)){ + + //临时处理 + deviceDTOList.forEach(it->it.setStatus(1)); + + List devIds = deviceDTOList.stream().map(PqDevice::getDevIndex).collect(Collectors.toList()); + log.info("在线率查询sql开始:"+timeInterval.intervalMs()+"ms; "+timeInterval.intervalSecond()+"s"); + List onlineList = pqsOnlinerateService.lambdaQuery().in(PqsOnlinerate::getDevIndex,devIds).between(PqsOnlinerate::getTimeid, startTime, endTime).list(); + log.info("在线率查询sql结束:"+timeInterval.intervalMs()+"ms; "+timeInterval.intervalSecond()+"s"); + + List inteList = pqLineList.stream().filter(it->devIds.contains(it.getDevIndex())).collect(Collectors.toList()); + Map> lineMap = inteList.stream().collect(Collectors.groupingBy(PqLine::getDevIndex,Collectors.mapping(PqLine::getLineIndex,Collectors.toList()))); + List inteIds = inteList.stream().map(PqLine::getLineIndex).collect(Collectors.toList()); + + Map inteDevMap = new HashMap<>(); + log.info("完整性查询sql开始:"+timeInterval.intervalMs()+"ms; "+timeInterval.intervalSecond()+"s"); + List pqsIntegrityList = pqsIntegrityMapper.selectList(new LambdaQueryWrapper().in(PqsIntegrity::getLineIndex,inteIds).between(PqsIntegrity::getTimeID, startTime, endTime)); + log.info("完整性查询sql结束:"+timeInterval.intervalMs()+"ms; "+timeInterval.intervalSecond()+"s"); + lineMap.forEach((dev,lineList)->{ + double rate = pqsIntegrityList.stream().filter(it->lineList.contains(it.getLineIndex())).mapToDouble(it->it.getReal()*1.0/(it.getDue())).average().orElse(0.0); + inteDevMap.put(dev,rate); + }); + + List deviceDetailList = pqDeviceDetailMapper.selectList(new LambdaQueryWrapper().in(PqDeviceDetail::getDevIndex,devIds)); + Map devMap = deviceDetailList.stream().collect(Collectors.toMap(PqDeviceDetail::getDevIndex,Function.identity())); + + List gdIds = deviceDTOList.stream().map(PqDevice::getGdIndex).collect(Collectors.toList()); + List pqGdCompanyList = pqGdCompanyMapper.selectList(new LambdaQueryWrapper().in(PqGdCompany::getGdIndex,gdIds)); + Map gdMap = pqGdCompanyList.stream().collect(Collectors.toMap(PqGdCompany::getGdIndex,Function.identity())); + + List bdIds = deviceDTOList.stream().map(PqDevice::getSubIndex).collect(Collectors.toList()); + List substationList = pqSubstationMapper.selectList(new LambdaQueryWrapper().in(PqSubstation::getSubIndex,bdIds)); + Map bdMap = substationList.stream().collect(Collectors.toMap(PqSubstation::getSubIndex,Function.identity())); + + List lineList = pqLineList.stream().filter(it->devIds.contains(it.getDevIndex())).collect(Collectors.toList()); + List deptslineList = pqsDeptslineService.lambdaQuery().in(PqsDeptsline::getLineIndex,lineList.stream().map(PqLine::getLineIndex).collect(Collectors.toList())).eq(PqsDeptsline::getSystype,sysTypeZt).list(); + + Map pqsDeptsMap = pqsDeptsService.lambdaQuery().eq(PqsDepts::getState,1).list().stream().collect(Collectors.toMap(PqsDepts::getDeptsIndex,Function.identity())); + + Map map = deptslineList.stream().collect(Collectors.toMap(PqsDeptsline::getLineIndex,Function.identity())); + Map temMap = new HashMap<>(); + map.forEach((lineId,deptline)->{ + String deptName = pqsDeptsMap.get(deptline.getDeptsIndex()).getDeptsname(); + temMap.put(lineId,deptName); + }); + + lineList.forEach(it->it.setDeptName(temMap.get(it.getLineIndex()))); + Map> pqLineMap = lineList.stream().collect(Collectors.groupingBy(PqLine::getDevIndex)); + + List result = new ArrayList<>(); + for(PqDevice pqDevice : deviceDTOList){ + DeviceDTO dto = new DeviceDTO(); + dto.setDevId(pqDevice.getDevIndex()); + dto.setDevName(pqDevice.getName()); + dto.setIp(pqDevice.getIp()); + List tempList = onlineList.stream().filter(temp -> Objects.equals(temp.getDevIndex(), pqDevice.getDevIndex())).collect(Collectors.toList()); + if(!CollectionUtils.isEmpty(tempList)){ + double asDouble = tempList.stream().mapToDouble(temp -> (double) (temp.getOnlinemin() * 100) / (temp.getOfflinemin() + temp.getOnlinemin())).average().getAsDouble(); + dto.setOnLineRate(new BigDecimal(asDouble).setScale(2, RoundingMode.UP).doubleValue()); + } + dto.setIntegrityRate(inteDevMap.containsKey(pqDevice.getDevIndex())? BigDecimal.valueOf(inteDevMap.get(pqDevice.getDevIndex()) * 100).setScale(2,RoundingMode.UP).doubleValue():0); + + PqDeviceDetail pqDeviceDetail = devMap.get(pqDevice.getDevIndex().longValue()); + dto.setManufacturerName(pqDeviceDetail.getManufacturer()); + dto.setStatus(pqDevice.getStatus()); + dto.setRunFlag(pqDevice.getStatus()); + dto.setThisTimeCheck(pqDeviceDetail.getThisTimeCheck()); + dto.setNextTimeCheck(pqDeviceDetail.getNextTimeCheck()); + dto.setUpdateTime(pqDevice.getUpdatetime()); + dto.setGdName(gdMap.get(pqDevice.getGdIndex().longValue()).getName()); + dto.setStationName(bdMap.get(pqDevice.getSubIndex()).getName()); + dto.setLogonTime(pqDevice.getLogontime()); + dto.setDeptName(pqLineMap.get(pqDevice.getDevIndex()).get(0).getDeptName()); + + if(pqsDicDataMap.containsKey(pqDeviceDetail.getManufacturer())){ + dto.setManufacturerName(pqsDicDataMap.get(pqDeviceDetail.getManufacturer()).getDicName()); + } + result.add(dto); + + } + pqsEventdetailPage.setRecords(result); + pqsEventdetailPage.setTotal(page.getTotal()); + } + log.info("所有程序结束:"+timeInterval.intervalMs()+"ms; "+timeInterval.intervalSecond()+"s"); + + return pqsEventdetailPage; + } + /* @Override + public Page devicePage(LargeScreenCountParam largeScreenCountParam) { + LocalDateTime startTime = largeScreenCountParam.getStartTime().atStartOfDay(); + LocalDateTime endTime = LocalDateTimeUtil.endOfDay(largeScreenCountParam.getEndTime().atStartOfDay()); + Page pqsEventdetailPage = new Page<>(largeScreenCountParam.getPageNum(), largeScreenCountParam.getPageSize()); + List pqLineList = (List) redisUtil.getObjectByKey( NAME_KEY+ StrUtil.DASHED+"pqLineList"); + List deptslineIds = (List) redisUtil.getObjectByKey( NAME_KEY+ StrUtil.DASHED+largeScreenCountParam.getDeptId()); + + + if(Objects.isNull(largeScreenCountParam.getGdIndex())){ + pqLineList = pqLineList.stream().filter(temp->deptslineIds.contains(temp.getLineIndex())).collect(Collectors.toList()); + }else { + pqLineList = pqLineList.stream().filter(temp->deptslineIds.contains(temp.getLineIndex()) && Objects.equals(temp.getGdIndex(),largeScreenCountParam.getGdIndex())).collect(Collectors.toList()); + } + if(CollUtil.isEmpty(pqLineList)){ + return new Page<>(); + } + + List devIndexs = pqLineList.stream().map(PqLine::getDevIndex).distinct().collect(Collectors.toList()); + //在运总数 + List pqDeviceList = pqDeviceService.lambdaQuery().in(PqDevice::getDevIndex, devIndexs).eq(PqDevice::getDevflag, 0).list(); + List runDevList = pqDeviceList.stream().map(PqDevice::getDevIndex).collect(Collectors.toList()); + + pqsEventdetailPage = pqDeviceService.selectDeviceDTOPage(pqsEventdetailPage,largeScreenCountParam.getSearchValue(),runDevList,largeScreenCountParam.getState()); + List deviceDTOList = pqsEventdetailPage.getRecords(); + if(!CollectionUtils.isEmpty(deviceDTOList)){ + List devIds = deviceDTOList.stream().map(DeviceDTO::getDevId).collect(Collectors.toList()); + List list = pqsOnlinerateService.lambdaQuery().in(PqsOnlinerate::getDevIndex,devIds).between(PqsOnlinerate::getTimeid, startTime, endTime).list(); + + List inteList = pqLineList.stream().filter(it->devIds.contains(it.getDevIndex())).collect(Collectors.toList()); + Map> lineMap = inteList.stream().collect(Collectors.groupingBy(PqLine::getDevIndex,Collectors.mapping(PqLine::getLineIndex,Collectors.toList()))); + List inteIds = inteList.stream().map(PqLine::getLineIndex).collect(Collectors.toList()); + + Map inteDevMap = new HashMap<>(); + List pqsIntegrityList = pqsIntegrityMapper.selectList(new LambdaQueryWrapper().in(PqsIntegrity::getLineIndex,inteIds)); + lineMap.forEach((dev,lineList)->{ + double rate = pqsIntegrityList.stream().filter(it->lineList.contains(it.getLineIndex())).mapToDouble(it->it.getReal()*1.0/(it.getDue()+it.getReal())).average().orElse(0.0); + inteDevMap.put(dev,rate); + }); + + + for (DeviceDTO record : pqsEventdetailPage.getRecords()) { + List tempList = list.stream().filter(temp -> Objects.equals(temp.getDevIndex(), record.getDevId())).collect(Collectors.toList()); + if(!CollectionUtils.isEmpty(tempList)){ + double asDouble = tempList.stream().mapToDouble(temp -> { + return Double.valueOf(temp.getOnlinemin()*100) / (temp.getOfflinemin() + temp.getOnlinemin()); + }).average().getAsDouble(); + record.setOnLineRate(new BigDecimal(asDouble).setScale(2, RoundingMode.UP).doubleValue()); + record.setIntegrityRate(inteDevMap.containsKey(record.getDevId())? new BigDecimal(inteDevMap.get(record.getDevId())*100).setScale(2,RoundingMode.UP).doubleValue():0); + } + + } + } + + + return pqsEventdetailPage; + }*/ + + @Override + public Page userEventList(LargeScreenCountParam largeScreenCountParam) { + Page pqsEventdetailPage = new Page<>(largeScreenCountParam.getPageNum(), largeScreenCountParam.getPageSize()); + + List eventIds = largeScreenCountParam.getEventIds(); + if (CollectionUtils.isEmpty(eventIds)){ + return new Page<>(); + } + + + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (eventIds.size()>1000) { + List> idPartitions = CollUtil.split(eventIds,1000); + + queryWrapper.lambda() + .and(ew->{ + for(List pList: idPartitions){ + ew.or(w->w.in(PqsEventdetail::getEventdetailIndex, pList)); + } + }).orderByDesc(PqsEventdetail::getTimeid); + + + } else { + queryWrapper.lambda() + .in(PqsEventdetail::getEventdetailIndex, eventIds) + .orderByDesc(PqsEventdetail::getTimeid); + } + IPage list = pqsEventdetailService.getBaseMapper().selectPage(pqsEventdetailPage,queryWrapper); + List indexIds = list.getRecords().stream().map(PqsEventdetail::getLineid).collect(Collectors.toList()); + List pqLineList = pqLineService.getBaseLineInfo(indexIds); + Map ledgerBaseInfoDTOMap = pqLineList.stream().collect(Collectors.toMap(LedgerBaseInfoDTO::getLineId, Function.identity())); + + List collect = list.getRecords().stream().map(temp -> { + EventDetailVO eventDetailVO = new EventDetailVO(); + eventDetailVO.setEventdetail_index(temp.getEventdetailIndex()); + eventDetailVO.setTimeid(temp.getTimeid()); + eventDetailVO.setMs(temp.getMs()); + eventDetailVO.setWavetype(temp.getWavetype().toString()); + eventDetailVO.setPersisttime(BigDecimal.valueOf(temp.getPersisttime() / 1000).setScale(3, RoundingMode.HALF_UP).toString()); + eventDetailVO.setEventvalue(temp.getEventvalue()); + eventDetailVO.setLookFlag(temp.getLookFlag()); + eventDetailVO.setNoticeFlag(temp.getNoticeFlag()); + if(ledgerBaseInfoDTOMap.containsKey(temp.getLineid())){ + LedgerBaseInfoDTO ledgerBaseInfoDTO = ledgerBaseInfoDTOMap.get(temp.getLineid()); + eventDetailVO.setLineid(ledgerBaseInfoDTO.getLineId()); + eventDetailVO.setPointname(ledgerBaseInfoDTO.getLineName()); + eventDetailVO.setBdname(ledgerBaseInfoDTO.getStationName()); + eventDetailVO.setGdName(ledgerBaseInfoDTO.getGdName()); + eventDetailVO.setBusName(ledgerBaseInfoDTO.getBusBarName()); + eventDetailVO.setObjName(ledgerBaseInfoDTO.getObjName()); + } + return eventDetailVO; + }).collect(Collectors.toList()); + Page returnpage = new Page<>(largeScreenCountParam.getPageNum(), largeScreenCountParam.getPageSize()); + returnpage.setRecords(collect); + returnpage.setTotal(list.getTotal()); + return returnpage; + } + + private List change(List list,List handleMsg){ + List result = new ArrayList<>(); + if(CollectionUtils.isEmpty(list)){ + return result; + } + List lineidList = list.stream().map(PqsEventdetail::getLineid).distinct().collect(Collectors.toList()); + + + List pqLineList = pqLineService.getBaseLineInfo(lineidList); + Map ledgerBaseInfoDTOMap = pqLineList.stream().collect(Collectors.toMap(LedgerBaseInfoDTO::getLineId, Function.identity())); + + result = list.stream().map(temp -> { + EventDetailVO eventDetailVO = new EventDetailVO(); + eventDetailVO.setEventdetail_index(temp.getEventdetailIndex()); + eventDetailVO.setTimeid(temp.getTimeid()); + eventDetailVO.setMs(temp.getMs()); + eventDetailVO.setWavetype(temp.getWavetype().toString()); + eventDetailVO.setPersisttime(BigDecimal.valueOf(temp.getPersisttime() / 1000).setScale(3, RoundingMode.HALF_UP).toString()); + eventDetailVO.setEventvalue(temp.getEventvalue()); + eventDetailVO.setLookFlag(temp.getLookFlag()); + eventDetailVO.setNoticeFlag(temp.getNoticeFlag()); + if( temp.getEventvalue()< 0.5){ + eventDetailVO.setEventSeverity(1); + }else{ + eventDetailVO.setEventSeverity(2); + } + eventDetailVO.setMsgEventInfoSize(handleMsg.stream().filter(msg->Objects.equals(msg.getEventIndex(),temp.getEventdetailIndex())).count()); + if(ledgerBaseInfoDTOMap.containsKey(temp.getLineid())){ + LedgerBaseInfoDTO ledgerBaseInfoDTO = ledgerBaseInfoDTOMap.get(temp.getLineid()); + eventDetailVO.setLineid(ledgerBaseInfoDTO.getLineId()); + eventDetailVO.setPointname(ledgerBaseInfoDTO.getLineName()); + eventDetailVO.setBdname(ledgerBaseInfoDTO.getStationName()); + eventDetailVO.setGdName(ledgerBaseInfoDTO.getGdName()); + eventDetailVO.setBusName(ledgerBaseInfoDTO.getBusBarName()); + eventDetailVO.setObjName(ledgerBaseInfoDTO.getObjName()); + } + return eventDetailVO; + }).collect(Collectors.toList()); + + return result; + } + + + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MessageEventFeedbackServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MessageEventFeedbackServiceImpl.java new file mode 100644 index 0000000..62ef8b4 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MessageEventFeedbackServiceImpl.java @@ -0,0 +1,16 @@ +package com.njcn.product.event.transientes.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.MessageEventFeedbackMapper; +import com.njcn.product.event.transientes.pojo.po.MessageEventFeedback; +import com.njcn.product.event.transientes.service.MessageEventFeedbackService; +import org.springframework.stereotype.Service; + +/** + * @Author: cdf + * @CreateTime: 2025-06-26 + * @Description: + */ +@Service +public class MessageEventFeedbackServiceImpl extends ServiceImpl implements MessageEventFeedbackService { +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MsgEventConfigServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MsgEventConfigServiceImpl.java new file mode 100644 index 0000000..c6b4531 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MsgEventConfigServiceImpl.java @@ -0,0 +1,104 @@ +package com.njcn.product.event.transientes.service.impl; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.MsgEventConfigMapper; +import com.njcn.product.event.transientes.pojo.po.MsgEventConfig; +import com.njcn.product.event.transientes.service.MsgEventConfigService; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import javax.annotation.PostConstruct; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * @Author: cdf + * @CreateTime: 2025-06-27 + * @Description: + */ +@Service +@Lazy(false) // 确保服务在启动时立即初始化 +public class MsgEventConfigServiceImpl extends ServiceImpl implements MsgEventConfigService { + + /** + * 暂降类型 + */ + public List eventType = Stream.of("1","3").collect(Collectors.toList()); + + /** + * 暂降残余电压阈值 只查询小于0.7的暂降事件 + */ + public Float eventValue = 0.7f; + + /** + * 暂降残余电压阈值 只查询大于50ms的暂降事件 + */ + public Integer eventDuration = 50; + + + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean eventConfig(MsgEventConfig msgEventConfig) { + this.remove(new LambdaQueryWrapper<>()); + msgEventConfig.setId(IdUtil.simpleUUID()); + String tem = String.join(StrUtil.COMMA, msgEventConfig.getEventTypeList()); + msgEventConfig.setEventType(tem); + this.save(msgEventConfig); + eventType = msgEventConfig.getEventTypeList(); + eventValue = msgEventConfig.getEventValue(); + eventDuration= msgEventConfig.getEventDuration(); + return true; + } + + @Override + public MsgEventConfig queryConfig() { + MsgEventConfig msgEventConfig = this.getOne(new LambdaQueryWrapper<>()); + msgEventConfig.setEventTypeList(Arrays.asList(msgEventConfig.getEventType().split(StrUtil.COMMA))); + return msgEventConfig; + } + + + @PostConstruct + public void init() { + System.out.println("------------------------------------------------------------------------------"); + MsgEventConfig config = this.getOne(new LambdaQueryWrapper<>()); + if(Objects.nonNull(config)){ + if (StrUtil.isNotBlank(config.getEventType())) { + eventType = Arrays.asList(config.getEventType().split(StrUtil.COMMA)); + } + if(Objects.nonNull(config.getEventValue())){ + eventValue = config.getEventValue(); + } + if(Objects.nonNull(config.getEventDuration())){ + eventDuration = config.getEventDuration(); + } + } + System.out.println(config); + } + + + @Override + public List getEventType() { + return eventType; + } + + @Override + public Float getEventValue() { + return eventValue; + } + + @Override + public Integer getEventDuration() { + return eventDuration; + } + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MsgEventInfoServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MsgEventInfoServiceImpl.java new file mode 100644 index 0000000..579bd56 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/MsgEventInfoServiceImpl.java @@ -0,0 +1,39 @@ +package com.njcn.product.event.transientes.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.MsgEventInfoMapper; +import com.njcn.product.event.transientes.pojo.po.MsgEventInfo; +import com.njcn.product.event.transientes.service.MsgEventInfoService; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-06-25 + * @Description: + */ +@Service +public class MsgEventInfoServiceImpl extends ServiceImpl implements MsgEventInfoService { + @Override + public List getMsgByIds(List ids) { + //通知 + List msgEventInfoList = new ArrayList<>(); + if(!CollectionUtils.isEmpty(ids)){ + if(ids.size()>1000){ + List> listEven = CollUtil.split(ids,1000); + for(List pList: listEven){ + List temp = this.lambdaQuery().in(MsgEventInfo::getEventIndex,pList).list(); + msgEventInfoList.addAll(temp); + } + }else { + List temp = this.lambdaQuery().in(MsgEventInfo::getEventIndex,ids).list(); + msgEventInfoList.addAll(temp); + } + } + return msgEventInfoList; + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqDevicedetailServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqDevicedetailServiceImpl.java new file mode 100644 index 0000000..ee3e5f7 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqDevicedetailServiceImpl.java @@ -0,0 +1,19 @@ +package com.njcn.product.event.transientes.service.impl; + +import com.njcn.product.event.devcie.pojo.po.PqDeviceDetail; +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.PqDevicedetailMapper; +import com.njcn.product.event.transientes.service.PqDevicedetailService; +/** + * + * Description: + * Date: 2025/06/19 下午 1:47【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqDevicedetailServiceImpl extends ServiceImpl implements PqDevicedetailService{ + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqUserLedgerServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqUserLedgerServiceImpl.java new file mode 100644 index 0000000..20aba8b --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqUserLedgerServiceImpl.java @@ -0,0 +1,65 @@ +package com.njcn.product.event.transientes.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.PqUserLedgerMapper; +import com.njcn.product.event.transientes.pojo.param.PqUserLedgerParam; +import com.njcn.product.event.transientes.pojo.po.PqUserLedgerPO; +import com.njcn.product.event.transientes.service.PqUserLedgerService; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +/** + * @Author: cdf + * @CreateTime: 2025-07-28 + * @Description: + */ +@Service +public class PqUserLedgerServiceImpl extends ServiceImpl implements PqUserLedgerService { + + @Autowired + private PqUserLedgerMapper ledgerMapper; + + @Override + public boolean addLedger(PqUserLedgerParam ledgerParam) { + PqUserLedgerPO ledger = new PqUserLedgerPO(); + BeanUtil.copyProperties(ledgerParam,ledger); + ledger.setId(UUID.randomUUID().toString()); + ledger.setCreateTime(LocalDateTime.now()); + ledger.setUpdateTime(LocalDateTime.now()); + return ledgerMapper.insert(ledger) > 0; + } + + @Override + public boolean updateLedger(PqUserLedgerParam ledgerParam) { + PqUserLedgerPO ledger = new PqUserLedgerPO(); + BeanUtil.copyProperties(ledgerParam,ledger); + ledger.setUpdateTime(LocalDateTime.now()); + return ledgerMapper.updateById(ledger) > 0; + } + + @Override + public boolean deleteLedger(List ids) { + // 物理删除(直接删除记录) + return ledgerMapper.deleteBatchIds(ids) > 0; + } + + @Override + public PqUserLedgerPO getLedgerById(String id) { + return ledgerMapper.selectById(id); + } + + @Override + public Page pageList(PqUserLedgerParam param) { + Page page = new Page<>(); + Page pageResult = ledgerMapper.selectPage(page,new LambdaQueryWrapper()); + return page; + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsDeptsServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsDeptsServiceImpl.java new file mode 100644 index 0000000..2959b0c --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsDeptsServiceImpl.java @@ -0,0 +1,32 @@ +package com.njcn.product.event.transientes.service.impl; + +import com.njcn.product.event.devcie.pojo.dto.PqsDeptDTO; +import org.springframework.stereotype.Service; + +import java.util.List; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.PqsDeptsMapper; +import com.njcn.product.event.transientes.pojo.po.PqsDepts; +import com.njcn.product.event.transientes.service.PqsDeptsService; +/** + * + * Description: + * Date: 2025/06/19 下午 3:57【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqsDeptsServiceImpl extends ServiceImpl implements PqsDeptsService{ + + @Override + public List findDeptAndChildren(String deptId) { + return this.getBaseMapper().findDeptAndChildren(deptId); + } + + @Override + public List getDeptList(List deptIds) { + return this.getBaseMapper().getDeptList(deptIds); + + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsDicTreeServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsDicTreeServiceImpl.java new file mode 100644 index 0000000..9021e98 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsDicTreeServiceImpl.java @@ -0,0 +1,31 @@ +package com.njcn.product.event.transientes.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.PqsDicTreeMapper; +import com.njcn.product.event.transientes.pojo.po.PqsDicTreePO; +import com.njcn.product.event.transientes.service.PqsDicTreeService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @Author: cdf + * @CreateTime: 2025-08-01 + * @Description: + */ +@Service +public class PqsDicTreeServiceImpl extends ServiceImpl implements PqsDicTreeService { + + + /** + * 获取字典树 + */ + + @Override + public List getDicTree(String code){ + List pqsDicTreePOList = this.getBaseMapper().selectChildrenByCode(code); + return pqsDicTreePOList; + } + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsEventdetailServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsEventdetailServiceImpl.java new file mode 100644 index 0000000..76dea48 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsEventdetailServiceImpl.java @@ -0,0 +1,22 @@ +package com.njcn.product.event.transientes.service.impl; + +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.pojo.po.PqsEventdetail; +import com.njcn.product.event.transientes.mapper.PqsEventdetailMapper; +import com.njcn.product.event.transientes.service.PqsEventdetailService; + +/** + * + * Description: + * Date: 2025/06/20 上午 10:06【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqsEventdetailServiceImpl extends ServiceImpl implements PqsEventdetailService{ + + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsOnlinerateServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsOnlinerateServiceImpl.java new file mode 100644 index 0000000..590bb92 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsOnlinerateServiceImpl.java @@ -0,0 +1,19 @@ +package com.njcn.product.event.transientes.service.impl; + +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.PqsOnlinerateMapper; +import com.njcn.product.event.transientes.pojo.po.PqsOnlinerate; +import com.njcn.product.event.transientes.service.PqsOnlinerateService; +/** + * + * Description: + * Date: 2025/07/29 下午 6:40【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqsOnlinerateServiceImpl extends ServiceImpl implements PqsOnlinerateService{ + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsUserServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsUserServiceImpl.java new file mode 100644 index 0000000..7c9df0b --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsUserServiceImpl.java @@ -0,0 +1,19 @@ +package com.njcn.product.event.transientes.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.PqsUserMapper; +import com.njcn.product.event.transientes.pojo.po.PqsUser; +import com.njcn.product.event.transientes.service.PqsUserService; +import org.springframework.stereotype.Service; + +/** + * Description: + * Date: 2025/06/27 上午 9:46【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqsUserServiceImpl extends ServiceImpl implements PqsUserService { + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsUsersetServiceImpl.java b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsUsersetServiceImpl.java new file mode 100644 index 0000000..8f375b0 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/service/impl/PqsUsersetServiceImpl.java @@ -0,0 +1,21 @@ +package com.njcn.product.event.transientes.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njcn.product.event.transientes.mapper.PqsUserSetMapper; +import com.njcn.product.event.transientes.pojo.po.PqsUserSet; +import org.springframework.stereotype.Service; + + +import com.njcn.product.event.transientes.service.PqsUsersetService; +/** + * + * Description: + * Date: 2025/06/26 下午 2:27【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Service +public class PqsUsersetServiceImpl extends ServiceImpl implements PqsUsersetService{ + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/utils/JwtUtil.java b/event_smart/src/main/java/com/njcn/product/event/transientes/utils/JwtUtil.java new file mode 100644 index 0000000..fc6bcff --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/utils/JwtUtil.java @@ -0,0 +1,85 @@ +package com.njcn.product.event.transientes.utils; + +import com.njcn.product.event.transientes.security.MyUserDetails; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +@Component +public class JwtUtil { + + private final String userId = "userId"; + private final String userName = "userName"; + private final String deptId = "deptId"; + + + private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); + private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 1000000000L; // 100000小时 + + // 生成JWT令牌 + public String generateToken(MyUserDetails userDetails) { + Map claims = new HashMap<>(); + claims.put(userId,userDetails.getUserId()); + claims.put(userName,userDetails.getUsername()); + claims.put(deptId,userDetails.getDeptId()); + return createToken(claims, userDetails.getUsername()); + } + + private String createToken(Map claims, String subject) { + return Jwts.builder() + .setClaims(claims) + .setSubject(subject) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .signWith(SECRET_KEY, SignatureAlgorithm.HS256) + .compact(); + } + + // 验证令牌 + public Boolean validateToken(String token, UserDetails userDetails) { + final String username = extractUsername(token); + return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); + } + + // 提取用户名 + public String extractUsername(String token) { + return extractClaim(token, it->it.get(userName).toString()); + } + + // 提取用户ID + public String extractUserId(String token) { + return extractClaim(token,it->it.get(userId).toString()); + } + + // 提取用户部门 + public String extractDepartment(String token) { + return extractClaim(token, it->it.get(deptId).toString()); + } + + // 提取过期时间 + public Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } + + private T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + private Claims extractAllClaims(String token) { + return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); + } + + private Boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/websocket/WebSocketConfig.java b/event_smart/src/main/java/com/njcn/product/event/transientes/websocket/WebSocketConfig.java new file mode 100644 index 0000000..e8fb7f6 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/websocket/WebSocketConfig.java @@ -0,0 +1,41 @@ +package com.njcn.product.event.transientes.websocket; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; +import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; + +/** + * Description: + * Date: 2024/12/13 15:09【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Configuration +public class WebSocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } + + /** + * 通信文本消息和二进制缓存区大小 + * 避免对接 第三方 报文过大时,Websocket 1009 错误 + * + * @return + */ + + @Bean + public ServletServerContainerFactoryBean createWebSocketContainer() { + ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); + // 在此处设置bufferSize + container.setMaxTextMessageBufferSize(10240000); + container.setMaxBinaryMessageBufferSize(10240000); + container.setMaxSessionIdleTimeout(15 * 60000L); + return container; + } + + +} diff --git a/event_smart/src/main/java/com/njcn/product/event/transientes/websocket/WebSocketServer.java b/event_smart/src/main/java/com/njcn/product/event/transientes/websocket/WebSocketServer.java new file mode 100644 index 0000000..c643e48 --- /dev/null +++ b/event_smart/src/main/java/com/njcn/product/event/transientes/websocket/WebSocketServer.java @@ -0,0 +1,147 @@ +package com.njcn.product.event.transientes.websocket; + +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Description: + * Date: 2024/12/13 15:11【需求编号】 + * + * @author clam + * @version V1.0.0 + */ +@Slf4j +@Component +@ServerEndpoint(value = "/ws/{userId}") +public class WebSocketServer { + + private static final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap lastHeartbeatTime = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap heartbeatExecutors = new ConcurrentHashMap<>(); + private static final long HEARTBEAT_TIMEOUT = 60; // 60秒超时 + + @OnOpen + public void onOpen(Session session, @PathParam("userId") String userId) { + if (StrUtil.isNotBlank(userId)) { + sessions.put(userId, session); + lastHeartbeatTime.put(userId, System.currentTimeMillis()); + sendMessage(session, "连接成功"); + System.out.println("用户 " + userId + " 已连接"); + + // 启动心跳检测 + startHeartbeat(session, userId); + } else { + try { + session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "用户ID不能为空")); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @OnMessage + public void onMessage(String message, Session session, @PathParam("userId") String userId) { + if ("alive".equalsIgnoreCase(message)) { + // 更新最后心跳时间 + lastHeartbeatTime.put(userId, System.currentTimeMillis()); + sendMessage(session, "over"); + } else { + // 处理业务消息 + System.out.println("收到用户 " + userId + " 的消息: " + message); + // TODO: 处理业务逻辑 + } + } + + @OnClose + public void onClose(Session session, CloseReason closeReason, @PathParam("userId") String userId) { + // 移除用户并取消心跳检测 + sessions.remove(userId); + lastHeartbeatTime.remove(userId); + ScheduledExecutorService executor = heartbeatExecutors.remove(userId); + if (executor != null) { + executor.shutdownNow(); + } + System.out.println("用户 " + userId + " 已断开连接,状态码: " + closeReason.getCloseCode()); + } + + @OnError + public void onError(Session session, Throwable throwable, @PathParam("userId") String userId) { + System.out.println("用户 " + userId + " 发生错误: " + throwable.getMessage()); + try { + session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, "发生错误")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void sendMessageToUser(String userId, String message) { + Session session = sessions.get(userId); + if (session != null && session.isOpen()) { + try { + session.getBasicRemote().sendText(message); + } catch (IOException e) { + System.out.println("发送消息给用户 " + userId + " 失败: " + e.getMessage()); + } + } else { + System.out.println("webSocket用户 " + userId + " 不在线或会话已关闭"); + } + } + + private final Object lock = new Object(); + + public void sendMessageToAll(String message) { + sessions.forEach((userId, session) -> { + System.out.println("给用户推送消息" + userId); + if (session.isOpen()) { + synchronized (lock) { + try { + session.getBasicRemote().sendText(message); + } catch (IOException e) { + System.out.println("发送消息给用户 " + userId + " 失败: " + e.getMessage()); + } + } + } + }); + } + + private void sendMessage(Session session, String message) { + try { + session.getBasicRemote().sendText(message); + } catch (IOException e) { + System.out.println("发送消息失败: " + e.getMessage()); + } + } + + private void startHeartbeat(Session session, String userId) { + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + heartbeatExecutors.put(userId, executor); + + // 定期检查心跳 + executor.scheduleAtFixedRate(() -> { + long lastTime = lastHeartbeatTime.getOrDefault(userId, 0L); + long currentTime = System.currentTimeMillis(); + + // 如果超过30秒没有收到心跳 + if (currentTime - lastTime > HEARTBEAT_TIMEOUT * 1000) { + try { + System.out.println("用户 " + userId + " 心跳超时,关闭连接"); + session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "心跳超时")); + } catch (IOException e) { + System.out.println("关闭用户 " + userId + " 连接时出错: " + e.getMessage()); + } + executor.shutdown(); + heartbeatExecutors.remove(userId); + } + }, 0, 5, TimeUnit.SECONDS); // 每5秒检查一次 + } +} \ No newline at end of file diff --git a/event_smart/src/main/resources/application-dev.yml b/event_smart/src/main/resources/application-dev.yml new file mode 100644 index 0000000..2c95ddb --- /dev/null +++ b/event_smart/src/main/resources/application-dev.yml @@ -0,0 +1,53 @@ +spring: + application: + name: event_smart + + datasource: + dynamic: + primary: master + strict: false # 是否严格匹配数据源,默认false + druid: # 如果使用Druid连接池 + validation-query: SELECT 1 FROM DUAL # 达梦专用校验SQL + initial-size: 10 + # 初始化大小,最小,最大 + min-idle: 20 + maxActive: 500 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + testWhileIdle: true + testOnBorrow: true + testOnReturn: false + # 打开PSCache,并且指定每个连接上PSCache的大小 + poolPreparedStatements: true + maxPoolPreparedStatementPerConnectionSize: 20 + datasource: + master: + url: jdbc:oracle:thin:@192.168.1.51:1521:pqsbase + username: pqsadmin_bj + password: pqsadmin + driver-class-name: oracle.jdbc.OracleDriver +# salve: +# driver-class-name: dm.jdbc.driver.DmDriver +# url: jdbc:dm://192.168.1.21:5236/PQSADMIN?useUnicode=true&characterEncoding=utf-8 +# username: PQSADMINLN +# password: Pqsadmin123 + + + + redis: + database: 10 + host: localhost + port: 6379 + timeout: 5000 + lettuce: + pool: + max-active: 8 + max-wait: 8000 + max-idle: 8 + min-idle: 0 + + diff --git a/event_smart/src/main/resources/application-prod.yml b/event_smart/src/main/resources/application-prod.yml new file mode 100644 index 0000000..334d60d --- /dev/null +++ b/event_smart/src/main/resources/application-prod.yml @@ -0,0 +1,55 @@ +server: + port: 18093 +spring: + application: + name: event_smart + datasource: + dynamic: + primary: master + strict: false # 是否严格匹配数据源,默认false + druid: # 如果使用Druid连接池 + validation-query: SELECT 1 FROM DUAL # 达梦专用校验SQL + initial-size: 10 + # 初始化大小,最小,最大 + min-idle: 20 + maxActive: 500 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + testWhileIdle: true + testOnBorrow: true + testOnReturn: false + # 打开PSCache,并且指定每个连接上PSCache的大小 + poolPreparedStatements: true + maxPoolPreparedStatementPerConnectionSize: 20 + datasource: + master: + url: jdbc:oracle:thin:@192.168.10.34:11521:pqsbase + username: pqsadmin + password: Pqsadmin_123 + driver-class-name: oracle.jdbc.OracleDriver +# salve: +# driver-class-name: dm.jdbc.driver.DmDriver +# url: jdbc:dm://192.168.1.21:5236/PQSADMIN?useUnicode=true&characterEncoding=utf-8 +# username: PQSADMINLN +# password: Pqsadmin123 + + + + redis: + database: 10 + host: localhost + port: 16379 + password: "Pqsadmin@#1qaz" + timeout: 5000 + lettuce: + pool: + max-active: 20 + max-wait: 8000 + max-idle: 8 + min-idle: 0 + + diff --git a/event_smart/src/main/resources/application.yml b/event_smart/src/main/resources/application.yml new file mode 100644 index 0000000..aebfa05 --- /dev/null +++ b/event_smart/src/main/resources/application.yml @@ -0,0 +1,72 @@ +#当前服务的基本信息 +microservice: + ename: 12345 + name: 12345 +server: + port: 18093 +spring: + application: + name: event_smart + profiles: + active: dev + + +#mybatis配置信息 +mybatis-plus: + mapper-locations: classpath*:com/njcn/**/mapping/*.xml + #别名扫描 + type-aliases-package: com.njcn.product.event.**.pojo + configuration: + #驼峰命名 + map-underscore-to-camel-case: true + #配置sql日志输出 + #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + #关闭日志输出 + log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl + global-config: + db-config: + #指定主键生成策略 + id-type: assign_uuid + + +SYS_TYPE_ZT: 1cfcd6e2-c5fe-4b15-988a-32b90f1170c1 +SYS_TYPE_WT: 983f9dfe-4f9a-4c96-89d8-7d425a1f1d6c +db: + type: oracle + +#文件位置配置 +business: + #处理波形数据位置 + wavePath: D://Comtrade + #wavePath: /usr/local/comtrade + #处理临时数据 + tempPath: D://file + #tempPath: /usr/local/file + #文件存储的方式 3.本地存储 + file: + storage: 3 +#oss服务器配置 +min: + io: + endpoint: http://192.168.1.13:9009 + accessKey: minio + secretKey: minio@123 + bucket: excelreport + #华为obs服务器配置 +huawei: + access-key: J9GS9EA79PZ60OK23LWP + security-key: BirGrAFDSLxU8ow5fffyXgZRAmMRb1R1AdqCI60d + obs: + bucket: test-8601 + endpoint: https://obs.cn-east-3.myhuaweicloud.com + # 单位为秒 + expire: 3600 +#线程池配置信息 +threadPool: + corePoolSize: 10 + maxPoolSize: 20 + queueCapacity: 500 + keepAliveSeconds: 60 +WAVEPATH: D:/Comtrade + + diff --git a/event_smart/src/main/resources/logback.xml b/event_smart/src/main/resources/logback.xml new file mode 100644 index 0000000..e972d91 --- /dev/null +++ b/event_smart/src/main/resources/logback.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + UTF-8 + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + + + ${logHomeDir}/${log.projectName}/debug/debug.log + + + + + DEBUG + + ACCEPT + + DENY + + + + + + ${logHomeDir}/${log.projectName}/debug/debug.log.%d{yyyy-MM-dd}.%i.log + + 10MB + + ${log.maxHistory:-30} + + + + + + + + + + ${log.pattern} + + UTF-8 + + + + + + + INFO + ACCEPT + DENY + + + ${logHomeDir}/${log.projectName}/info/info.log + + + + ${logHomeDir}/${log.projectName}/info/info.log.%d{yyyy-MM-dd}.%i.log + + 10MB + ${log.maxHistory:-30} + + + + ${log.pattern} + + UTF-8 + + + + + + + + ${logHomeDir}/${log.projectName}/error/error.log + + + ERROR + ACCEPT + DENY + + + + ${logHomeDir}/${log.projectName}/error/error.log.%d{yyyy-MM-dd}.%i.log + + 10MB + ${log.maxHistory:-30} + + + + ${log.pattern} + + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/event_smart/src/main/resources/template/test.docx b/event_smart/src/main/resources/template/test.docx new file mode 100644 index 0000000000000000000000000000000000000000..3c7d4aefb48d1542d684932c7b0b0d1ed8016a43 GIT binary patch literal 1023599 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
?&_ya=}{ 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

6LLCc%S$4gFlvV2rOi8;Yi4&E*P9# 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%0A#~-u1v%`gzW!}1Vll6-iS-o(9<59u)-+#}& z@4n;3-VtB?^V=LK4v(cu)TZ8;YpvDTQ9i#&O5AMTxT?y@bJ+S zo<4ida5O=jWwF=g&f9Nu_rniZSl!_F|8$S7t({P%Po_YZc+JvH3ULi|ySKN`?sAW{ z#YKL2@e(&0Qz(TfO-p5Tbmo=MPM5Y;K{*UL{Y2QQCRBq{`o)+kD&P{PI56*c${tU% zV!<9ke3Y>G-<8zhlowi|GNAYzH#HdL^U`D@WEQmX2Bi>^p9jc55btV_FK{G5DUV9O zXhC%D6TAneYnAuPbJ0G?LQkV2^;6;bpnRpaox|zq5jVXq>Hm~J zB%aDjEi}M~TE+XKz@d>_Nqkt0mrv2~amo8M>UE|v9?%W#NnA6cFvGuFNP8q1`&Lln zG0qs;HU$KSu)VnZO!5$f5Z0ywxD=F57XV`y=*Af9l`4YQN8*PPhaURm+Z7jpqND&v zqa_$)VZZyw>VDRWjZ6EW7Mh_Fu+$2!#J1~M2>^VppvG##HQ?1yLR}4})mQDUT92^? zsp7p&&YL`f17C}4n=}bG=ib;x`mVuaoA<^K#LwMuIOO4jhkW?{2W(v2@VU`YyxiGl zYwH=GeEc?t$0gQ!54#5s9-q(NkB6&zDq200ROjYU}VX>%u{S-v^TUf?Q%Om8q!8%6oYjuh3d+ z{UL{h{H{dcCMaB$3t&tLJtO8>3f^cb_pT(e*I86#8wws#Nhi>>4Y_v1`rCSsdCxYN z_+g@rU{p!N2y_jF&_VKANqK##jkm;y!#-E77BBbr_|32X0b3bXmX;Zx4l&~R?)!Tv zr75ct=N!vROYHCN^ZmUCY`u7iNOs1Mjq?CdRTV3%E4=^iUD{dBfBTESU}<5I&p!E# zot+)N`syqCg8@ZRfWVc_O_qATz}>Y??+tugGgfYO z*3YN^sha0?Q@hJe?WTEG(!LNI*4}~`XjP83A@Bua8ctePSj;qQ9Zgp(+DZcOocLlMLz+evoq(=rlMsXswz1e4tQQpcqti$QdFiy#tyAi@SqX262yuJGPTb^r7KN0 zR12OAhg4-13?)Bq+u(7|_yP;LC~R4xTfPv25+TnsGOcl?MJ8TO)Smg?h{+U~s`7GL zE40!fXQ-!1trU5lqqC6jP)rE-b(lh!WDeUs;zFVLWO0$zeIawR^8#j4QVt6;rzxB! z@Ag>RSm$tmm+J5k#2*`uM-!Cuhsj5W`zX_+8jl&B_L0hv8_SZdxU;-W2bfrcRd`t~ zl4PfM=JGm2p0yZQM_+`A5{?IbS}LQP=Pc%$p7qt)U66q`$h)Xd`U4xA05xNSADIMd z456#m7;MxfhC^TSQBRn`5{ZjM4D8J%Pv$4nx^+9a&`}*)op@Om@QALn?E`*wY;bLC)z!z=o ziT7$UXiBZ9B2RjaUE2I?*5;VCedgJkZ(?Unw-FX$rp!rpVWF5S?*rf>!SOFib)pP7Sb2UBq- zuBm6%xI)}hO8jA!4W7>4A;<4wrPWjlq%-5N(Xm1(unaTBC=&`%D5reBYN>4oJ1H<_ ziHXx1wxc-b?Xh6nCC~F&TqN*F7j*iVL9FI2P#WWlgS8gGdHu4gOgN-%%!Sls;&7yF zkFL4?%+N-p$7o-N!=HGF7nrhh&f7LN)`6+@FCo~n61-N!8YEjb=#xUo!yE>vl%P?5 zuB>G^8B^#E8#?1qjnAyj)2*lc`e@83YthzSa@{7kFmRUc>Iy5pjLR1n=u9*^%gH43 z#hGj^H>`0~CKQ--o+np9zj8JdiW8{;Ok*jk5(ue31RoU{Wgz%``aw^1VQ!-A@lHuL ztXrfpuh+%=^u;2 z7HFII_nD0|(?+xM(!4&->)YAQn7&LuMVe=<{{NH>WRAdk-8Jvz?hkqXO|E&Td#}3h z4*?+a}GwSJpKmfoarl3(BBN3Zey`{_q0X+F@t+h?Nz`Xfj4=#ly!> z7>`D@T5X&Ni%ZKaE-mxc?c2Qn-n;DV9q{FEe$V#q9(#Mc6vc#-Z@!~Hu6Xy|cX{{j zdvtq?JpbVtJ3D*))i3`R5h%+sRaK$0jCQ9>mglUmZ*X$j=kmo>R#&dE&~0<=)-9es zf61@D{4IO?2en{r$j0;M%0(;*QG&6CVp8H36%z+z2V>{?LMKyTD;$=}IkGA|>kGo; zwC2=#5U)_0(i#j7m1l_3HMrUkW>E)?)_$!bNczlQ!DtNLCPER6hI$;zdG84A94Z_s z2_Dc?0Sr)qy(v+?Vw!Pj4*XaoG0oJ~;2$3ua`gPl#Gj;j;iTdqh^JK%>{Qx&occ6R zA@@!Q^UE@oo{23e!5hy(K*mB9;_x1=xk{dF(HocuV$CQ({`=eu>xSo7tt8e}Xccuz zGm)k-7CWXApc0Ln=;dG&0^`z}MV;4$MRp-?V+sXN9;~&oXRY^K0-x8~n9#QPlTdH8 zNh1f2_lk{Pvrb$ZLw(}sIjW^DG8YxXIJ8uVBPooY=8}ReDuhDYdYVI+bUe3hdf!Ns zbZIm#r1qo1oaVTB`4=>Vc0&aY4b@Z@g?a!0fd79~I+`!_-=z?%#u)3KL|S)Ap^h^X z0kT%R&4mkV^twGn6wAvi+_-*&S-N?S5C61aePNVVD<^Mx@T{sT%F-m-Q(913 zgHniSDxgexi^5)X4sjM!RutnBTUGr9=Kug807*naR2ppQ!>z1!m{<_TIdIk=1SFlp z_r*GkF@~}zJs{zHxUB<9Ybj%%vdSdBQkErE>BGH@F;s=cRt^#4txqNB;05L^NfGvY zCCaO)1QPNe`xT5{<069A8F`k~^cQI|cxWq8AqQ#$j(4!TzsE0r@eA5{3+;RktzuH} zo8SG0ukU@6#wa4>Sq2FE2ZtP;9HX@_ATqsoXn2=7oHY#k1NxQ07A0@Jd!4*O^n2OQyfNR&Tk!2af(TM&11A4t4*REZo)9J9Yv%{n)$TCfq zD^s75ohW6oY7hpR>-7FIf4ZgzUCW-TTJej|cd zOWRs*FD&!!)ipY92&IFOxSVm3}6SKtz}^{p<@MQz@QoHjDFT) ztUSMzuwSjIa0q#((c(cetrc0O+1R+m`=5VCS(W_uAAZZx(IFB-?0mSJU@8-2zpv6R z*7^`Y8-;FsXbl%~(C!&!q+9_X>cr3axE8tn_rOm14EqK`F)Y z@o_K+JpmhW7{;_lmQCB?Czo%tG#;F((kjj{I{1NoGeIT?WZKYbwL%CbwDSyS43#Z; z`rsP|M?=i;go9^~(YIMoKwMK&XuOu z>G5JbLKL*JHiPk)gxaJT4a5D#8GxB0e8YLYp2+oGgho-%#lS&yu;CP6hK5w+1 zWP8I-eVyaWdDp9)K;rlFoOov2j&m^Yoqvc^4p8U6o3X#n16*I{1aqD({>GqXLoP(! zGWCnN3iHS4Bsd+Y45c$Bw!#>Lmq2k|lSjgoZ9(H#*Z44XJJC|p>Nwk*X;3ok#Rk^l zqERE$Hdg{ELxZ!&*GVol^iWCn);*Q__WDQCHD?{A+|>A|MyJ%Y<4#-`r6{arTvb%g z(TaNeIct*IO~!H1NkmbJU`1o`c1{e%Czw1j+Yvh-o_RNQG-#R(p5);F0PJ1u8H11J5T1%f~_o7Q6gIV7^a|0H?aZ-{H;2f8h@5@ zX1N-{pLb0$|5>IR|8!^XKhN{A`PrLZ|0BTMJOFjx_p|rS^AvnlU(OSBvs09q<7?V^ zRlEOO;Ba31v+c$AG%FI%zVB7S_f7ggd*56z_zmYd-Pc&ha6@1)4Y3uzQli5tR703K zLg=jH;gc6^?;m-kpNw{^g9#NGb*91j+&_7qBf$Q_KKF5K?;WtSf5_g!f!~C9MmL%i z-246>FScHA{l-nKag=36QB>^g?jr(PIe!29uleD{OExZUu(-HFyVIsWIOV&053sgi z^YSHzlL`0lKjFcnClr+-fHD|O|FTEEES%$RQWOUntWP=AdQ}osbH3ipm03l1!oM3_`ENMpj5~Us!5YB!82B8 zd0l8CC|VI@@CtYc!4MS+P7pjg>+}LHPBtE?gFDp3Wo;>RRP6TNsMpN;Do z>8|#sipso8pGOjmYZn!DAGTO~%S82}W~Vg}?`gaSQIU`&9mtjDU~dmoR*Z%t)|Qu` zEYPL!!^0PpWySj9GTR7czt8&SRhE_(dG`1b55B(#z{2t(rDS}4|1s6koP)p(6?L#&Tt)$Cb4-Hm#nf9!>kmdZ@+8S$_W~tj|>txRt%~O($GA}nA8=IH7e(eU{(|rB-DCUsl;iRs$S0TfT~#^nWvQ|T&ksd##8FlyS2s8Lqo4g5rza=;@|VA4 zTug}R9ISZyDh9g@CcuRY=yG^gA9f|9qoJbm_ro#)TEv~iK!ci-XB z!-u?lxyzG#zo)mf!n?OOIUNjnxqIL9?D2?PCMX<>qPU==sM^Q34A0RK042n zUF}vzQdm^bzr*_bf*?(>$|a5t{;)MZufp@r2B6b(F5h$^K8O51sCJ@^K6&%0L5u1p!%LfGq35-Jex1g= zt#`H|h@g(Y=NvOuq`^$6-@7_iu{k zNBw2mX3fZ-hP_nZK(n=USKXl3>64qOnrg#A?I{}WkJ(0?tM87{RZW}1H8;b|h^FVY zu?IM7r#p$E%u%-#oG8ZDGP0JEP+%t66P$Z_xfk@LQ1HlFht)9>EfRbh`ouQ!0r<_jH;R&Rkj@4e63#dY#L#~MSw->2K{@cHMT)9?4WvUwF#RqXti|3WEy z-pL`_QDIPn@iAhOm>es-P@@}W7cY1D=G*%WhZ82HudtRIrzyIrhvQ<1odI%kuo12{~~HEj7^HP{zBmyMbgEG4Am*8DT(MHTH(ERpq?M{= z@6_4{@7jhZYgi#9grInOcHvO29kwkUL_s7mXVq5!yvR%o61^^*vedZXLtV0CH# z*~B;eyHe#$=M!=pYf-J1=PNOcBp8Ph`d5cYgnGXr@wd;l=0lldsG-B*l&|~>KF_JG z{0U{E@0hPygwh$RSjZ=Y*SUsK8do*~wBC!*ro5CQDx9d|`{A571%jM$)~2u^E#4fr z&cS%hqi?=t=jk)DEC*w0Iatgb&;ROg+4_ax#4;HVSvWXkcXuD2{y^2~@n~yq4|@8rBflnPSt26n zEJt$ht!gWSDPmQ6>x~KpHWwz7F~9%2zvub=`?N&?ChTpmmuE`Rc7hK1$HZ8a)+y|^ za!?@@TBuZ@Jv6vb%{cI+7}l!Ad#^{@_50Pad$h+d|Nd|3564W3Nyyb_F$RYH0pET7 ztuIE?ACTo4d;9y8lY$5LzxVvD;U!kfwOTU#<8S}L1Jc%@B^(e$`i^@u8d(y(K%y5i zf&N`;QxeM*tW)eA9`kZ*m#owEpk2thj5u1EVo@sAdOcrYrBv9lLm7=$8P;f|KcQ1h zDBA`xXZ4~^rn#}aKvxTyayV!3F%AYNx$lv9UUmEJJN&2r@E@tFg7J7lx6?&y#dtEI z+v%{du!M7#pZ@93`1JG7(MnSkMKC-qzx?Gd`Rc2$=yp0>x^#(dr-J}RIp)3h?(&nL z{DjMwudsh`#OCG|^1K!Ji=_%Vl21=h8JzYp){^TCon=UpjgD4JapUGq{>>l%F-1}E z-Tm+R;fJkoFpNJ8a|Y!q#2JJNTa{CC3cuyKetDITKmLf%KKmGJ;cx!B;+?l{@#7zT z&c(Gg{_}tSKRG%)=Ju@{eE8|dT)%#uU;NeI@cj8hFK=+@kXO`&OxFFfVrx9%NHXtP zgbV328=;KOp+*xV%c*ol={!F#i;D5cae^f)4PsrAM_wKA^skap9}X;u6_aBeUNr2Z z>}}Gtzr2o9+h>TYbDjnsh(M~-F#Pa*dXgk9E|q1MNAQ zHesVL=b~<}zWz{0k@K8(ns&*w;|L^O_Z9ZpxsE{pj4?Iao^9`Sc3X-J@}JP``&oM~ z?uMC9UupOGUE~BZ3yV&Z+1eWL%BS|J4niJZn>=+Jp1$dyu+IW8=>Qkf*3>zx8>5NO zIY_xgXz0p~{TTmtLC=cYx(SBP?uTS&#MiiKy7srs&QEA1_2tZj23xe|CBawP&xca! z5v>$?*2ai1G(||u6W<_b+n#=nj}ma=3!@Tr3cOvFX1vLtn&SywcTF3Nri><~VQEk% zi_1%V^vNgOxOv@!nbxtpv%`1ae#_te-QTmkvclbW-(z=Ymy>=!$^O|H^kb;^Qqwsh z(njLN&ei!{8+}Q9q_Kt&(-1W6R;Jo>jeCKFW6bukX^*9lZ4oMG=v}?U^3`Qd2U{>x z0I0pbz_2Lk^m^DWp_GE6G*nt>bp`d_2BRBYw);DfWelw5`t8f?3`ZP0%jl?JqADcM ze8gi|EE7^Pxr9l@G(KU3F7n(EP9N^`o|d)ut^ZCXCmba{s>tiQF6pHz9InOOrAql6 zrSTw2i6B}SnCU`_+0^XKxn@s;09tvDQfjIaWo+k7Pk-hDfHNT3yf$WkpXV*vB-Yt? zobi}!fUMnam#V6mOeSaAc@^`W=e0Qd`@Hcqz2utrZQUHwU#atgz0URfO#fc9rvJZQ z{}cN%&Hgz@s0Xk_{E0vtpnJm2ljU^kkrECI;A%H$qBmUlTXe{Y5vt!A2P(R{h$Ggi ztOM<$+?zd}t<^Dtz#%Fu62VoWC=KJHpJ-UA3{fyCD#jD<*&@*cj>wA7&czwvI0)R7 zyDNPsXaiL-Aybx{H?Og}zQUtNzW-%a(CM|f^WnP~G9>f!GaL?i{P;1eYirzn|9zGh zmssd^{g_k~unw?O`efRp5=CzEqagxN380Qfrnsc+KmY&(x=|553;h+nH2n}8UTbmB zH|7Ui?Zp$7SvP`qGvO=fR<>LA}olb{`4bdDwX?@^X%vmM%T(~%%#&~OYO*f?nPt&rsXK zPm8f+PSDl^d|HiCqHc`@FH=9CDhgiZXIjPFurUv-%CL<>B*q$F?Jz023fGnZ_~KK* zxmxduN<{MXh`DQ2+ASbHADdCZb2TVpK81|WXJkY8b<8mt*`Bc`oxb|2q2Z82dG9H6 zdg|$KGU3wt1>U`MiF`1^4#qfBK~>j=;HcAv3et+oT%_(6oxAKf`Cr5|F z0~HiE33;rP_DZ8OmP}_zC}yFwCeKXgTUWVnm&UY5lhUUs2kUW3>6pKrFM%Al_wsiRP#n67N0R|B#LzD)X0M}i!r$&~b44^CFSfh~ zaxx&Iaq9Qz%a_~yRIS{50UL^<;Onoy=6C<_ z8#XsK`O%MlM6cJQm=vj!u88B>wQKYim$=Z%`RSkj)aT=Ngo6T$DGT;ryhJ~KfpZn6 zs?KP-N`$qQMZWm_6F&arWA1-{pQUb>Pd@#aFaGEYj*gCT&akx9<%>W1jDPp%|DNG! z#Ft-wNv`k*6%x3Cv%VOe3N)z{Bc(CPD2;kl_)n2&&;xF&HpKC%0Lr|sO&)o1JX)R><*zG#F-kF?m&Gd9)CpUrlr99Cy;oU*Z8 zoVqxU=@-1IDe@-3-t09CI=;>hY<5`W)$ck3WKu7G(+BKz1Dt1f&%35Qf9p#rMV4iO zC)hLhG~0gF8(!6iSWHiV+&LB`!==MUKiXcehgx5UG8{y@n*OKt`rz$qW8T&v^P^6x zbkH-R+8Nd^7NHEqx{w;2A?R0API}1(i@c(_Ipd+XYuIXaG*YwAbw6xs%Wv&^!=It| z1`WlAq8E28dYQaI@%n0}y-s3^%14t(IMA@B_T8AbZqy!mu^<{+rGv|~4ADDr&OVaH z-=5B9-*NWp_xt?%*I)9@H(#gXI-}8u-Q8W**4FsRAOD1-qeFi4+uv|?X(`% zNVF8^U8a7{evXjxnRIGfk3JHNHo=UK4R zUQci8J(`pr|HpjdozqSnM>L}Z;owri{*}oqpZ(YCpMNcvL)>8uyO|agK&>=37_?G~ zcDwDpc1`V;Zd^k}l4&KQZtpC>9SsxPput2hA_;7W`L3e3u8(L{M9gYdnV8*QbHVh; zY|{H`TC~P691hss+hy24W%c5EDyVSUKjHp^`}~`q{RxtJMHdlXZf#>K%i6|8hNB@5 z9zWsb)^8XF~oLyMW)mS2P7bM&dP~2 zLZTXq;*4^gz7v1Yv$DR%h1Cn(zIB_0g&xO)KJ8A2m6cT{#U8M!dY%VYmCrX~46VFHSydb# zAA68c>3Sry$^{XKbm_C$_b9?^FAGVM%F|#7B@bYRi`Cd&Jh2Y1j7E*By(?5hPSlCt z<7#-t&}9*riW{l1FB2`-q5f%^9E1|P0bukXMzbhxEpJV|+M!JwJT?6|yoZjm4sE@F z)WMrGV!T>upL?~jf1)xe;c&wbx1r26Y0!G$Re~3!=#Ym~T&Rldyr+ip&EB9d=Rats^5I?V?yi%n*IailE z-0Eg@I*X{~Hf@3GWJGy%il8_xU`098WmcF(ewk>7usH%$P)&k~eKvo(qXF1mw zx~Qt)WLR+OV1(kdtT@JT+ENq(d9LU=g%pMYC^h67+J*54hQ|85kRdNuZDItF9#6k< zAw57A_?iWa381$_@|?;AZ|dl=o@FQ}sFao?xc#_zfac|qb}o3Vg3WXKy*3BE7DicO z)j9!A1p321TPFicF^_qMjS*!tl)3kwT;{@G_NEiIAfIa!|LtR>4b5TV_%jK^bN3}{mD?YH0Y z`0-<3F;@wLL7&HuA9Ap`$mXR>eDL{aTv)%r!-o%f{`>_OF08S%yv*+I9w(=#DRg=? z8Zntna7xHpE!w>vonDW;-Nre`>gp=D-?~M&+vDKykdu=WR#q?2=`ALnYn;Ut1u`+5 zKHmX}hr~0wyD=Q>?{ajwPp%bM%jxkEqmvUPfU7viVASV_AD*#xVTJK{gkaIi`D*M4 zI1jMqI`erSt;Gr$0TaQ7rf!h>B?Q z_>#B)25P7ikTazdrM6~_>U3aNTg~A)RlhZT--up<0CqyE4iVy9O06kf!$Abu0TFW&#=S5uRnnuRlM zzY^SR{x0e8A|(u*bFd5 z$@@KQSE3|D>eO_Seh;~#(;;uvag*&&5ssI2W07*naRHLcO3D}f|er}@<66n!|gSkL95nN(wzU#J=`W(L2 zy)Nxm8yOA(AMK%mR%I!TB^U3B*UmJZ;G{BlWvuase(`WCS#OPlz!_TpLw3h`*JLu` z*|TS7Fq_TIO}_Zz$82m|VrOTEe!uV2^Sl8qpLd;yfz7+`b^CN0ypt)u4YM;9`W8Od zTKkkPA#w_(+1c6SXnah+cNQNy-aTck+6=1$hDQTd+dWn;bZGS~N5>PMJ$%ecu|lf{ zJ3B}8j|&R31uaec!Xl@}fH*R#gAuKhWYZ+$Iu0O)MK;GG%^Q+PO2hZ}N&QFAwd>s} z%_q}jM9d`RRCbPP%0s+S%{(>nDq#`stxSclu--J*>KmrUGt*R)Va%TXyb3XY-Ss9y ztNGq$-{Z66@%UWuboTkVAW>j0Z_@i&@HO(`+QRzR>wnheoP*N(1M&dcG{qefgMz{( zKC~iJigI?d)vZnm52!b$ZP4|ZJdRT>H2YZR#HbGvqQcTtoac;f=J&J8Ol)KNIcqAc zI4-QOap&%PEM2&WsGO@;uky~FJ3PK`dGEtd(Cr08FezVvD1b6Yt%Xl=@)oQ;$6f@oAYnHN%c+9ZdqFRFD0P8PZUiXBg}3-ma%}>dn)8 zJG`$67!!fg_`c}nB`hp1^1%lm((86;wc2E@oJ*HC+1S`%dwU1xpxf*E!X|mf<%<_t zURh=^7;s^Ek?Ys4Q90rGxXo;!EYUPZ_6N<86JRWm-^D=ps z^PAuNhLe*Mnmu(Pzi6Bb@AXOmL|l+jGEGQPTSL-%`Fwgp%+2c3_uj~bbNbBtX7d)u#5QY zKsU)Dyk$m_Of19&Bhfa({2P<&oUiJQPZ6PP$dwiVwK{ic$OY^00BBH#y?3l4R@j~@ zMisp^n}wVjos=+lUO~4pV-SLnY3~tejY+zT4>j~5TwP#ebr}iC_*G?5&XOxZ+8Jh2 zAr^+NV5hW{F5}QCbWwz=jD&n{4wWf-y)MIILjT|p?eOUeqQ{Vs!Zs1{-qcZUBfbz9 zJ_nr;_p~IsaZ06pgSY?^TVI02uR<_tq|R+;gXSfw)(x!SbM!#UO_(rjhfg;k-m6!X zFMMRcd+z}`f{x+e4MRVs(x|d&Uea7M>q6ckOE6@-x2?1I@cSqOjPdyyt-)3mRaw%` z6?QzvP6}*)gzTRHMnSp0l9gjJV-RDp&X8$MZ?(mOvRt{)LtKR`d=5%GcIdK2X+qL> z*W#!x43;(dCpUOfZgF~Y%BMHqrQF&>4+Oh)#I0V3^`$k6Ny$(YN0sB{$$*#RAt$Rn zP83{NTH;35que^;w5Kqe3tZ4G7GIpAh9#~r$i!eui!;t2oMnR5KDB}|7Hb^E#20hI zd2HF>!CtGO6oYB!47zQOoMSYu7>q|~t+=qzW6?PDaUW$YrGiSpSw|J}>AI>!ijtMm zk{OF}P>7?{u9*M>5&zzcmp8a{?Is_6^f|hn^Yrh2$z(L*)}6a-T)NEDFaM6w50+0p z{gi)P{xv5YHg~2^Cy{9?BRCrm#b+2+TOr*4uCM^vM%;wzsLO3K1a- z2NQXo@%fK_#AH(N?Adb$gF(s*yuGu-;o$*y-+GJBKl_Y@rA3}Uf6lMJ{F1V)xN+kK z)>^*(?pt2GctKf|WFa-fix)4btYLd+hrj&AUwT71?8Og0_<;A`e~(_T$Gv;sv$wlP zr`@6`OD2;Er~N*=d%KMKLk9bYESD8ZOPw#WapkR?>({Sw^~x2r)?B=}&ZnP#%9X2E z(OR>xu)sTa?{ai}$jw_f5P?po&BcrBbUSVOWeHfE_`@hI;?q~ysH>|a3#YG|2gF>I z2}lpT!;2V|Lal3g5Cw!wf5tF88-bSDw~6N~QI1XNJdJ;+2oG212~6}6eujQFR2>L_ ziIObcI|{2DrK#|6$js$}?yBmb_1H0?VPDPx5T}|6e>d}u zyEISd+o)%N(LWGSJ5PU}_xE|vH|@&lngh+4tutGDN>6`N05;tG2aoAIp!aq2qm-i4 z=`bFjt)~1h1^(9U_?+UU^{E`R1Imiw;Zcef@@j)g`chQFl1?D?KJW*=O=l8_`x@#U zCXsg2ey_(Kc5=!gpKt`{k{;ng+9d?9AArxR5!(#!^HCm(q$CF24+%~bp=}$$xj3F? zbW%Drn87uo-jsB^PTLns8&6Qqk!c_9+siUKS&LpTl2pGOZe(}q%t@p#Pde)o^uzyAP$ot>QovcC>w{6lc2+5Pe+ z!2CSEG0*0g#{JDcMNl^JLg47|2&2Zdm*({G)G*#3QWh0m}7IpG;@B;L*eHMvAN)5827)_CG!^HU$1}hOYi_e7D8!=n=^dtw~Y!49*8Ka zs-iy_G;((}ujuJEt!PD|iK`g_!HA9hNcSb77;a*Q6lRpUd1UwIr%~Z~7r`49^3xg0 zs>1ZgJbwBEBdch4yG)9b!Eng&U_gIV^7YsE(K_ew@Q6;ggQ+S+`JxpIy#+QmH(6R* z}53l}c%>8GFZ*MIZZoc0GCpPumDz3-^KJG`QEbaVtb27^J}H|vN+h{E6!;C5zg zjlZS&xi@wEiUhCB`o6Fxu9h9HL8DUwx-sQ0&|IXy)Q|9hae)9Z;#QRe!hp2ozi^Avi zv@V40hcNvRh<9cVoweS}H7dpu6$-&QpO-bRP4c>y;0;KKG)M|YLzcPtiD!V$R1HPg zze9-vTcHFx#?sy0#~vTk8IQ;-M=R2TH7KVz9mDulKco-h_Nv5&R97OW-k)q5Fj56*VtTJ9PZ<(Zhq&nB3uM82#M4#K zU9?Vl@3hurc}Cu9QBDeWpKVcL`QdP%m*WAC`iE={4_GiI*vfa)S{6reOZ8YD_NW#t zsuId!1rtZ6T8PduMZsuX@M7<*iKYhmHSZGN`S9iRBasYBm zV=)dgSjwWnSVtxvd~Ykq%E}T?4-S0TI+AkVMg_Z-X|BJ0hoAi~|B+j7z0J$5 zmn=NrPlGy`VhLD3i`ki2l>Mx5nmAzyEv z=X5$9mY0{4@l#cmry*)Fi>%)LOL8_}RbxIq$#!K2Bh9X^Ag> z{6~~!$<-^HD4p>i{=Ch}$q7YKpta`Fqep!6?YD^16lKZ5!C}&)1h{tfss|X9u(rO= zl`B{1b~=<*MO9U7z1(7JYl{aD9+H)YMJq@yI6dEx)k<@CaKtxXeZzP>A#1le8l3Xr z;X|x-^twIPSJ!yw?RVJS-NRVN+S(euUXOmipMZr}pe{Na#vHw~hsCpMlvl~7oh~Hg zYo4diB_0*!K~R9|cF(h00xxaYQV~vZgp@AS=~W_GB(ovuv64J%WNJJr@yBGK#(#sH z^VxliZC{1w&hE@efBt?o^PSh5RhoyS9YlLh*K;^MsjAkWV`F&TL-2i80TYQp z=REMQ31lX`B7UcQdK;$>7#F24?5G8eqtnXiv^p$1N3Imd{ZppPs!U&37nf*Vy3EqT z0x!1rFcQ-1NE~1sS7UF_quDusH|OWPy*W$2gTa8|a2WJxm1N*79nZsen%C=i!FkWk zI}A7vw~Bivt=XJ+H2YCibr^L_OZ4$4pV3`#e6#-zus`+go%ND_@7QA$*72-;z@z*g zO2NtHQz}#Oy?TnP3`ZLS#!I1)U+9Z$ipK(6r0J%Nc>R@nKJNiJ`$xl!YWx;05}Dc$ zNjVQUi@?!pG4+b9{{HM~;k+vmV48uzN28)J14={#Vw!NzGPYR(aNbGXc}#MafaYzI zcu(`RY+jr3gk|1)n*D9wF*|p!+LX=BcIH&*O)2MJum7P-TwKH8{X}C^JBUDL{6{R9 zs!WPv1cpfWM;dlCYCqS;bL0N_djm6SFoNjiM-X6YDx4u1wS<%2h9ExgN(Tuvlwe#? zRD<`GXf&E~Tv7E$9De(N?;ku%IXR6n6yt)w{;R(s%UUSy3%rcRW59BFxKF#=MO4nX zC^$Yk^f}n1F7y-mKnzEcq*=RqF9gL*8vRxwcV3;#vQ|30cWpzpkjBfRAs{nfPwnk! z4Mcd(1qCC9r_~HL{+a-)$nqTth#H)p@=yQxdp`Q;BbJwzn2g4BTW!jsV0e1U2OoaO zgZuaCpPq1ia}(z@lhK$=XJmPshYuce)IVkGhvzIWF4F0C0jR1Ha7>0HUwvF83Gma? zQ_8aR-otrbgM$s4YtrlN)ucTdytoN!*2bJ~Xo?@EUz#4XvB{`z3N#XpN1BbmRa_UZ z!nwwpHS-yHh0%tvJ(HC9xbJ8ff3aiDy&>s`&kL#3_a6L;P(tt`u4QLXlBX+GHsa+Q zcPWp^rf+KL>npkX{p_62kLZ;4pj@~oE&~x601sqVOnWXlh{manfsRtQfd#7rEr()6 zX%c7FD=t9Xe*!)PD;Tn##vP&zPsas^gE3iI(vncbB^1EWI%5p%wlhxij4I10M6j`_ zPE|JYv1&s33A7TFkyNeJcP+@~25%E@se8aL_DKjHv@VU?dn?7KjAxPIvuMg#*I4&B zrC}bd@oVe7DTB8~;#2iA$t%j$RA8K?3}B|u$z10!jq@1!4IW`hIWl9|j^DnL)+f%H zjs2I%(;`5uK*92*HF_&6-rLhS1{g-la;OTPw=6x$!XPcmYC6U-um#5tU-IoEOSdqz z2Zq*Ip(cW|J_qM++wo1?vL%MS(jZmI_IONptDZ+WQF(CO+b$V-EA?>bB5WNqw$2};fQK7p<3*rMRRZW3A#T-i6VE3ys${= zff~s)Wv(bP1KWznrzebCIaU;P?nJ*9rFDFB?>;~J;tTG6*ugl*%bk5z*AMyRN6QS$ z5`$v*=!pOJi~r8ijU#Ar=4!%0F^?@lMn-B)#-Tu2RXlzAl#3f1v|26N?KVqGOI+T( zjB}Rl?HxckIy&Ofqenb{{=CVo5mA(7$)iV481{!g2WsZiPb@4fP*oL+-40qShJyhj zLY8F|MM0Kl)VnE31v;%3S1w(m+i7!he9V(4Pgq@Dq1Wqja&*F-J9qft{SUZ*{{dh9 z{wt)aKxL?0<8n16s{xA1gs;E)C!RfiLMw0MBF%@~mr69F;fUd&&;Rki{qLwuBU*FX zA98ebO!PI3Mf!p$o94}luY~z->I>%qV$ONm6bas5!K*%08lNg&+E(x6kM63kFWvQg~Xo5%?n%&mxNLSPsnp9`m?I z9wCDFFb;N0Z97P^VO)4d0uP8Oe4;*@95YM}WJUeA9hxL}tFhH;0YW`Z7`~cQmgZD7e2wLP_Q?M_5pMN^MfGI(|39ItOaK&_ zQB;H(LF^el2~_V_`F-`4qS$POQXqpSqj3R77n***zhgm+TA+KO{iEKi-rH@#57%G4 zH}GEh0V7F4K^^@y5R+xcH+wUk|9t&NToT-7#V3Zqga}9>NV?w+0U`(jAjD)dIt2fh zB874AQZ4s~3ie)cUZVmM1-T%diB&;}_RnH`qp7JW>Wv9D);B_?XdjzN6^x9CVNxVe z3)RHN6bGk;@LReb1o6{@p@AtY<>Oy98k6))4JU?vH^%{q_HplK>(i*d!*$w38_ zrZqp0ssJh@sjJe6V4W8%83jVZ8Y#xB+>he%I2A`!N^n_$)?%%zK+>CjT~ka5rDJ~O*06b4a3N5i5M)@&c+ z95oSYlZs@*gR4PlZ zU1jZYm%bBvqUg7KtUq|jfddCwR*sb^!-c1xq$ypNww9?+G)T|^{0*R@3rx<%c@6XA z#IGj-sl>r0?i&Q@cEKb#t?4R5Np?Z`?B{+QtcD(n zUM&fVZ_L~(fk*k3Wh6m~CaKq%nwc#??IcN=nx3WIy-PRij+E}=9*-do;z3%$w}!wA z7l5>0uScF`nme*eU9ZD%)e)-$1t*y~)`q;~!hxzr}>-Bm2 z?YH^X3*X|2v*&1cT73B72TV^-bK>MNHa9o;?Q6d$Nm5RqKF#9dA~$Z_D0W3;Dhe=K zarW#Pp1$%76ZHwMUi*OGy!IOXUXLWv%+Jnq=+GgK1S?JJ97Y7C9Hfd|*%1RH^g3;_ zPPecc@rl?x;2SA|h~w_9o1UI2jnf7~*6(6%C>7{xspph(dnuyFe#K|eozuVL<%xQ| z&Say3QJQAEMZ42NDVUm?A#FxZ}xh<9iY+I0gUzo0xSICq_H{mEa$?hQ#?5{?~Nk> zRrZDlGNp)btpkB@*5PtnSV)RE zpBT}FpW&HUSGc^#{+1wHJd~Hh>Ru_YZ|2b>F4XH~GFRv-Q6^SGI0%=b4@kudG|E+h zEbEY%uozbntj)qTTDWy}iYZvlwg9C^{9Z+u>`m)8@w32F_Y~Ku;xM&6P5H z+{G0lw&$FG(`!Ef{qXfo>A*K_|5(Gg5j4i@RY4k81&75tXtz7G9<-Q9Ed9CvoKt1^ zO2D75Mu8cs;`Gp^@JUmsPRI^2y~CxpWVzw?2=uxN8Y<<33>#wxGN$jaX50nfthRB~ zVjBK-9Ev_%XJsgg#aOCLJFkK7MhG)i#d};CCR04~)@<>DKoOW&Rfxs_URsqXA~P*z z&!EdzJ}7=f;V`td{A@>Cl?*c2;S*-1s^1OIPbs)WRdLk1E7}>$s0jGg!73C@3G25~ z)b(#~({EJVBJfNh!C|aVprN$SoS`MgYSRn~558Wpaa^i%A5Wgk{WGADhj?*?wO{y9 zz$?v_wA)H6Y}TWX!$gN$i5V)r@E%VRC1}$^Sr!sb*@J^@*w{rYm4q1%2~8Y+FsyUT zA3e-Z|MD;BPtTEI$O2Q{T3qJQXVKN+R!gQ@cO)GLEWGciT3kraB&SxnV z@ywc#CD;XP%#f)w#*p&?rFAJ(IEcYBf}sHxRRVO%yWht7aN-axA%2?wsRF{ODnL|R zoVFOtFpB{+h~pN;gi^gxM8G&;o^hnn0gTw%*yR29-s9HI8?3A>lO_ps^9wwFyhOL% zqPgu8o;Qx{m6o2B3B(%jf$bA5vk-hZDoN$IwloP6?0&YV5V($XTW zW}CE@qP4+_u(`d>ljqNK_{b67c;ii$9zPE2F8~shO3)z1D%V{p6sq!GMPU)|Pv)&b zfd&NINd8^b?TC!I5Ze}PI4$<0@g8vMiW~<7W zi|>C4-bQ*7fP7+^Augx@&iejoZ;KyeP9hY_ObLLxa_NM&#F(bO{;B{k#X?YcU^&Lo zD<-URxKv$Xsz9+LL+}&n!K_Msr~;%dRz`Rx1xVxFi=t(z7KL$&nofK?O?>9%gh>8` zo>Am!68aQfS*0j5y8e3$G()O{`yYKtIs@|yb(U{DMB0jWZh5%6jnJiLGL-GmHj269 zhgn))MP)*EXOoFegYIIN{#uJ#tS4=Mbv_hSL51kn{5QlTe=d5)uB zGFB<*y0fl`xr!3{PRMbXT2g!)i*$iU6&YZ~$IFa+H15voHvltpv$#aDxU@vC*XQKP zlN>s9kWQyVmS@Z_%yaP2L0)*_1%4qnSn6*M@4{kH>j3a05!8j4(LC;pBZrUh%6GrR zvsa#BVPS#KKfBJ-;$s?(37oU!dB%f>51E;nVRmkgr=NL-R=dO1tJhdsTq1Y=Ky2aQ zL0f5gJV0v~+v0go0JIeh3K);iYKH@JTN3%>Wg@A2$&&(Z00aN=mS+bk_D z7n@NV!^x8;`Q8t{$LVutSzX)UKZ0Vk<5B_uPNk& zD!tYS6XLCNC#<8PHO5+EiaQ^x*3fm1mOw{ovT!(591=;$pQAj(SD6n1Az_TEhMfv? z;Ur{$SKcB`YmHHwiF%#WXHN0L^Urhq*fIM3jL$#+jQbDn@zlkqm|Hl=-~7$rk>_Q+ zGZ`EEU6}v?AOJ~3K~&%?gVzpaT6R9)xlRVxUS;K@Dg;JBabVwQG?<;4;nc}fJagq4 zZrr#*b9 zi;>eudQ^;OwY7VI4Wo3e;SP?FR(93*#*ZD9IoYZA?bXied;7P}9HdqIGMu60*#mm> z(EC+MjWM9iHvxNgYhTs%^*D#Xi%z(#y~w9*)@NOV$Q1=d>stq!p&lf>%^+8?B(TBD03 zfaT%7M13{tX)fwMG2D<(7T9+-#4-*>CgKWX4l5q4RaRldk>{4o`oka>bUv|`tluM# zx|VgtLA8kWW#&1#IBb$|;NU@OwHmp#^bjP=s39aOp|{JDX=}y0RybvF3UXD^haydm zG@+1rcV5GWY`d+A{j9UEW8}8ON^%TN^mVRbTx!2<9jLpgzDoN%Af1WO?)EwT#2K8; z*vgl_aa;dWy^>Pt^8-2@PqKQiIz=O`Dv2zuRN%@DU4A!wjXFiz-rg=xOokSR*1Cv4 zPz6I{J4Y=R6;4&EeT(l_8PjeHdRXunIBkvJsQr-tX4sRQN+xdC_y>TDl8x& z`!V=Tw#90)AzPo(p?nXbDg|1_JrH3$A9vAZIjyw~?mvD^_sC&fQbV-Qm~4A3Y_~(Z zwM~Z4@~jk~9Y(`KeSxF%^El;j#=A?Z#CWSSt&!Z4rU^;-ze*1jV4MlqL6~nRNai4S zs9bQF#kPA$FDLKx$TLU3pVRNOA#(kGI zux063OK)onh34+f+x+J5{+?#5#rpCRWErcE9^#yk^?H1D>lTmh-S>g_l`3MQIp+b} z=Jpo##sq6?8;G?iW6G1}LZOO#$bEEL13ym+BCh&?(D${QmQRiF3|}QPQR26343q%zOzh zB>E`}jUZg4bBd70IRG|Q9w-H^A0=?9@JSKRC(>=@Lh>%n`6r&_N3Z^XV<%2<`_5hR z*AnIz=J=C8`xB0xJVB?^=k|jKXq|HO$U%-R%yRkS1vYzKUVHb0;AXB1*DxT&%?wni zGXBE{QOF>lE%|vES&5b}{_iyPu5i76PA)zigTW^WFv_A$LO;*(pizRvxeFsz0`I;V z4jX8Fa9Il$Wds_oz$cEe7Hu>UCRNI3f{Z-P6^XdweyMaY{)ltuq&!3n!Se@Xn&|pg zln{YCe*k22>-mCsP_6_ZiX~R=7zLRNikgbqiG%WFG&q6<9Dt*i)_MAgvn-rSS-Ss# z)%6YNI9$I^vz3t;%M0JWz=5fZW^I#`Pn_rPf3e2KhGB-3pFaOG?|t?ui;p&_Q{&+m z4?GZ`gDtG?y>jG=K`O1xBWN9&r4R$k2U)a|I8VYtDZCd{nUGG6=YNZJmetiYTCFz!@DIOcH*Ur>{K2^DjI{eWJnL z`w!@J+N`au@ppgs_q_7Tce#A|BHPVv);HGaH=E2%PxHN3Ug6ar{*VI;3w-?PC;a}+ z*O{K1@<_L6ru|>u(i>olT5N!Yj9tL<*ZNJ2sX@@bw25paf<25 zNsK7(y%3pK2-Tp>kyaI?RGhI=n$t*pGF~EzI>WXmoCt5 zw>Wgqd()=vE#h^?z{Z-r~d{0ywAeI0iDNRJw)+(#Zg?%E4HsOqSr>Kq0*hRfZp%z-wB z_pAD6*-seK#s>b5;JqKp!MQBfL&5J$JvJuCiv}gbMnQXB`vG2t-`NjfH0oY;ZU=Pc zf{yRJi~yZ3D_`6~jKLX=4f|CA3DI5Lg@kA-I(Y>)T4ED1?j%8PiwE{PCi+vQS5IiQ z{|>J=MC(dzOexQ66M)U2S4BBmm0gNpsWGO+N|Y+x^|ex%B*E0u;(ij6E&6YR=Z5w= zV4@ACR;MipKVPPSkO^V{Z1**fo)9Loe|mQjp7mc3Uj%<8whl{K!-<2IFNG^L_4Zn2N6r_^iq zV4|`7SW{GJ0v(PPQ$^dVKP8Cb)iLopgI5U44pqtz7HhH6f#!{9Ye~f^#1N$hJ}sSo zqTN&F@6Z`l;RvcyZY-zx8Gi|Vi0|!Cj)$%+w^(I(^6Zlw>i2O;LNZZDrQQ}Y6N}S; z@eC`d^*Af!)}MT(XmnptJxgz+Mc(Nn*80rT!P*d|Na`tOGNqqDFBP&xg_tGYGO6mG zV;$tV#oAyYC>mu2&^Qe?Xt06@+pM)%my=r`$B1A&=7Co!vS7y(x)^8C=rAu1jI|_6 zFiO+g>hQ_i*H}9=Pd}|;l}1_1p@~W65GLm4sMie2f;fv4i&nxm!p71fTb-u2gd9*w zyn-2H<0VG#*q2%q%HlH)iz0DQvx=lAq@t;rgnCjZF$P3vBsFFyr!lkhK0C73=*c=R z#U~NaKoB%=LSjLz#p~u_L2Dq@SgpL(kJsDD#RfM=Rk(m!@d;Ss|Ddo==x3Ryci@$8 z>s?N4ZppKpXcpoeUKt9Cv(jM6>D$a(g(yR}(`ND7HL~0>Z!>1o)MHadktfj4yGYjd zL5>OT<<=D-OSidAUu){dptV5Ktt6Zwma2NcEmZ4n!;v{du^!Ucf4%#yAE>n)WdiBOUCtB3`m!-HsL zr%?m+Gtvs*=BP;ecM7{ciJ8bX+6fY^#=@BBN}H632PQ>|HpF-YQE-kMBF=>t-!JW? zLXuT69+9p~#ogIaj$?(Z?4WQ1d`1O}N9A@rA9t`QVa@u8qK?})Zt#OwzC)5GIDyPs zwzjwU@ap?KasC{=yiYgluyEu6-~Ztc>8`DjBnbzm4lz4Fi!+{oC(0inpfwl|a7K<+ zRV7BXX@F*gg{w`l*L6Oo;?!h=?|$cHUVQOe+_`&?U;plPR+d*#;+Sqs@YA2X%K3{I z`QXD(_|5NrM=!GwVraQ=c%jrFoT38X6cHvT8eF<`iNl8v^XX@wa_{~b^X^%`kfqu5}s( zaAhbedq-5@s|?98t^^jObzvnhq4N9pAJDzr+W}@qx_~%b?%gGeH9?HX?CDOPTkhX~$Zvl8JHEPe zhlh_IktQjp&YZ$J%ZDF+$}fKL3zk<_>G%8m?)Sgv^Dn;Oz`_E{%PVwxebAb2zt8;# z54d{m10FoM#}{9GL9JHfPyX~zdFflyXHkGL33;Az_3BlA z_q*S6;^YZ_^5Y*P<)<&PwY9~UUw%oi*JFNuo(t#C^W~Q}`0|VE%+Jj-J3j|jxbbL->8UBECL3%w zw^&(OW}?wxetsUEreq-obFmemvPTEovm1agt`9o^+CBQc+xPMLsCUNce$_-B!{Ehm zKjOPu59%<5JW{GM;oDe$yrT#8p4k2{e@;;dRPlKMnvNKk;r{LReE8ZAz&U*F*Ftq1 zs6DzJK%n4~XP!RfdO)j)9v<pIXQX&Rn??U?sCKu}4_@e5B=n>))MyfS_g51qp^D*Y|HN;H1-`?}nqew`1lnc;azM8$L^RiSa` zVi?CTT(J5|l9b%q0Y<%F@N&0$;x!Osa>TVCCK=zWF0ATe9CbQ4t}cPnav*HzB3ttw z{jP#_JD%S#xP4A&;$dJS?O+7 zQIMhsDp)r8r>?q4D>aRao=Y@2DHLy!K6ZxI9viGx=3bIB((`yY_a&*U40>S zP!og7y{oNtxp!1|pc9RmPRX^<7cbl^1dJw66dCI^<9>^_ZNM3bh_I}TUQW~Q*i7^;7NusGI38et)ft#3hS!shul6~FA zq$p#8yMM5Z)Y>OR5h2g|fL~KC#E_HV(qgSwe2c@Mz!$aAA%;)bKU(|vKH~F&3wR6E zJSWS2omtoiajs%K&OqJBtu3-@Q+?0k0eDJ~G;a84g-D3w<>L$q(MLMcU8SQ??}EFH zD=nuAmtF$>^L=ni!ZCvfnN?6m;{l0N5-d1JxUYpZSIEDjLV_8;KMc`!xumvAVs4*! z5W^0^Bl3bEpW>-j1n#PI$^}5w$KZ{LO$u9dKh0QtyqrQL3_^%1;F=)(6q0#0!>(rv zUcMb+-ADz%u3QoL22CwZn4Itj2};4_M4h>*89LjWDA8emY9^;9SzX^?X>kdi^>Iqm zXiTwi;6PEYj@d-D4z#u;4N>7J#&yIM`KD5E{@gh(U%rennog%hYkQkCNw{?3JZDZH zr(UnoY<5^(T}5)Ajs4Qo7ddnGJW3fhx3^hYT_-O*JYaQom5uE!58ee(Q)_RzK2fjp z%;ih`{O3RC;K75;&P>s6ZnM6!%*7|p@L&DKU!anNfAK&4&nzxJCefODEu~hk(dqT* z^m-oGkBR{0A)jBx6+x|-zj9p_;H1*QELp<1?F8&_{=PPbG)Yn5vwTLLt32(Votfs` znKQij(o4)Q9OR$>^M64tNm-bm<=hkJICkVX@4x>6D=RCUKmP=$&z+_*)!^viSw4Ng z!}H(yE}OkNj~CZywKJN{9?B}V*4y+CIgX#2#TcPh^YqRC?r5Zj8VSy6lz8P&Gbj_4 z`y>d;5<%geO2&}*;sH%5>65QO1+(a&U`geWRibTmAnHMos_vKS0eBR+hxd#_Au)cM z_K9=`68TWw<0CE!s!LD`UFCl4ip3v-+`M_4d-om`D@dfx8*jeJC!c&mx0A84u}Nlg zA6nEo9z1x&g9neiJ7(B){VeC>k3Zw{&%Yq+XE?D;&&;s7xy9n*61Q*P;q7 zbIYT}MOJ_HtH6gG{VW6E(W6D)dh0E^-7eR!U+3}iicc&9I2^gPbbCEMyMCQ}cklA) zC!djJnO~^lsMS+UVpwZ!`$QfU3b5I3@y4g0GpRwJJ*W2dN zCm-|1`X*~hf-)&8se2hr`MBSSb94oc_PXQ=xO{}thPQm}cYFNRU;P#JTAegaLSi3B zyWPPU&9P(0nVz2J>eZ{udPgc%?H~EQ9xq z5n*d{ldbJ7w9%Y9cMhc#n;V;~EHBe;Zu9W|J!?9=gO;l^=Z_4s_MMs0NdfR_G67U?%EC5sk}uoOb}>x z^pJ=wp!5v#f{fQZ3XrC%3&+B$02R@SQVJO=uH-1!0i~ygsurb)A;UtXoX}1c^hK|K z_(Svv`-k!aGU>gNss>xWKR!IPDtS>Hr;1FV0(fM@E0!p$?tlmbpcy!SD8q{QLqnrU zl;*v6-sbv;AJM4SsZXRFI+`3u`9T^OMTU{VjHcsyH3`A7jRLJ zzu|@MJvP+9B2(qpBXGN`+G#apma~gUeGv;o_&1D%z=HlYzoLt2$mq-KEP% zAjPpG1)r$4Ez+}S30ftxqRLHFjk&3$gix!toCDcx!+^!LswD!SadSp~RpoNSqX8X+ zFO{X{O^-s5Xn&IKEeJJ9r^s0f&Z2?o#uRf4b2Qs6dfhgvZ&6vsWRfyBJ4eel>Gry$ zNlK$WNvGdrtJx-ZHkdhCpM@2{n3xUHE6j0Lm4#Xk^;}UCP2HpAR^^F1O`k1oFuI2!iA=?SOxJpeTsGFAH+3=Dr5o=I4~GN z(U+|-8dTyFT9{%TS1j^G%KaokgopyC4WMftTnvl$`x*>V?jd64=io{!9#|Ifps1%g zF+q_EK(`K-tzdM5G6c(7A=QSONl3NEW*Ov`n7LU~CQMP4HUPmYNMUqXU#=8`eV!B* z+C!18x|V2kn)-JVjY&fJVSfA?^>K$J3X&D2UW-C0Q&>!S0nc05YUN|Gda%v6CC~E! zW-4s%aG9SuqctvfWW5ZR=iYT!oL5*ghs|>GzC|SlNi;b^9`=LQ8tZ~Zt@73k4#;$P zN4y(wyC*rM! zjX}9Urxo~Eh!(^aN}*AzjFlLK>j=KPkm#ZeY!K*QL8~KekFAdM)CNs3o$FU zw_=UYV`*A>!oaFyH^)E6mK!;A~F2 z)nt2fi-ox+F-gkVC!XZLIeMJjI(q#cS)QYn=FPX? z;aC6g4?a$}QBl&QW;7SipXU#M^drumI>qDVWtJW-;_{4olF-nG`ROTK7_*6bjq~Tv z@$}P|Id=RQ>l>TA_3nFI|MG^n><@j6ZdyeKxj38gWHSCeAhfx15Tf;Xkl_@i@)g&I z(b~uF3)YT>XlE^FPMqM6fA%xZKXrjtyNkty{t2?;O#!9~*gJny{y5v!}~Xf?_GyUS!&@#GW7(dr;h`dFQ!b%VL7 zDQZU1&H{)l1}B1vF5U>yYPUQf*(pDc6nLY|8WmqPB(_Idtb!7O@;oH~z!k<+SauJK zf<;@RQdez5pecwHbj}}yx}EQixICNphcix5N#Gz9F-?_1oTcCEqD?Gc*xKBpxz+UJ z7Oe6SA3DLra-w&zUuf7o!{%A2LD+6?^Y_2}1+TyUI<01tPPZHAp>IpS+oK>BVd*_y zT;i82ze1tNLn4bPkF}dk-hA^-K6w9qTAeoiZqEa}PAcQ)c}~j)-cXU(x+uQVikfrC z$`*M$=dd{HS%z`edx#_(^h}Lg0v~p^Sx-}(NpafvLoN}Fb?APdeyzd5V<&M&JeXOP zmn-~SDaFS6Mxign!`phj&dV>q%q!o0g)}kr`h8|+=9!wB@%+#v%+Afy?zD&2jw)#0 zj&-z8#d|j$s``8YvzkV#J$8(_xp`jw(T`YKT4sH1 zo$tQ#3MbE<}1-5d1U`R=&- ztM|r@-EMm3C~Mk6T@Es#2Q>Fq!mC3eoNN8|xjK21=mK*hDOu`!b9?zf(8XmqZbi=~ip^SJPZFI`9k^qWQ%CAzH zn-~BeKhEM!Vgs!#`GQp1Q5leer&oW(!`G;*#Ih?&cQL1tISYgHj4FSTN;~6$TKR4S ziYOZx4(zeN1cuhe>dv=YFXom z@HYW4NA)G1iVYPoemMj|scLmNAl<}1jN1f3kWB!bpqwKyUXhQ=qN;3&XntR0d6od; zC{uY|F zVyGrj`1G|>3M1lSB%>8c>Nlq%E0j5-bjY;qm1dQr&prLT-JC!o_;EUA_FcJ%V!lENg-@huToHm z;R*v#oP!e^5*7u;SuBZ3yb|XuT6_#1tRQ*r+h&4wqBs<}2c%qHxJe_?R6p(j6+l=D zAkPm(SoHzGh%Tm45G(+XptQxY*NG<81{3y`iXu{|yQU2Zh9rb8*MeL&vTgtXAOJ~3 zK~#_9qrJ)#du-_aVYLmb<%4z1YWW=jCpR55qOc(!}EPLE`wM!(ag z*X)Gs)C$|p$=VsxcRr)M(S_xchzINX{mcX8Mnh`IfL_RmoQWe>Lf<)hxee<(gaN<< zvsQduR*bh`%q`@3k51mh8XwwVZeog=bOL29IYQUv^qr$-40Uaap!kIH@=$MJij9p; zR#zY6dL33DFY($h|AD?vSX_L}>GMzdIucWC7I9M0D+wE0#D%er7O>?t5bDM`KKke* z9xp9%`0x?x^@Oa~rPFHjtrwr=dGn~eS96Ge9WrNjV+#k_8Bf*zRc#v7R$>k zbo)KJ-7Z>d>Jt-m+nrJ-ao*)zE4Xm}JU{>0&p3bnJW45U-M-EB>(}Y^dZbB0mi0ZQ zGKwo#E^_eTVV-;bS?1>ESzq@t>KgS1i7`ch{wR#^5T2tOR-}L9u~Y z(FJRXD?GZQp*#&qFsv;t)7frPOHdqxtCb$1E+i z@o_B#CkamZn8*GQ#`uGYaF~^Z_@GfOmBOUAl=(`9G|;g~x0OPykZ8r!)Fjihvvj*1 zdfgtKZWognCMFuR+8wM76d(v61%(`=phSK&46GJ1W)+aA%5=NPpix#g462{+;MGQONiOJNCS8V|A5Hx8j#!Jd7*)ucC

W03?C4I3(q4TNhrgf&Lr|I{sFcI#yV_ffv>H$LW@%bjWdFZ1o!z=5#MG zHS)SDkg`{sqxB#RfddTckr6c8FBmlL-f-JTwrNBi!(+4?ShSx@2cR*KG=GmuOw7>@ zF6Dzlnv=i-OHerKuB8ktg>FKiSzaHgfY^WCli zT2e5v5zj`{l}G_(4Q|k?9`Ao0-I%C|sQ|X^Mg&JTc(?#ELCwBA*?mw4h^g#s=csEO zQyWKwMinIuF36l*+ME z1``EhW5{rHY@a+3JaLX(ENBT9Zy`}b#hOeTjS}x7>csk(c~!+Tv{wf=R)xblA1~0D zB(%x*!G?r5VRw4P3U38wasEUY$P3?vRUN+VRvf|OmL#6?D~NVhPHUW2MN*c5 z`A1=uhiYPGZbZPS!op9`KC4}#IDGsV-+tvqo_h8fvRoq{eN1L8PhEV1AHDiLj-NYC zo~Ar7C`Ds(d{B2vt|7END)(n8{S8cuoT zR4ec5=~RfvCmI6KsKUH>Wj`>uFpkkZ!*?xMyaYxo662`l7;O-pV2jt|*y$pSK}jXN zRbEtTJor4gj|ywaNZe&mdGeic#gg{|g(?`b1cn>1G_M%o3fDwmMr>(3n=Qo|r(*m> zgHuu@m#Oxl5E25)EJef1x=Sf0HoH78i!IF^!P*gOQw`3XIm6t{9ILC3>393g%`dR{ z_%Z!{#<9akS>Ifz)!b%gYKkOH+1T6!rKqRgEwtP1`aR|hy)37nXVhw`2YMoCiXbk` zrSa=ng7D+42Lhdoa9Nn2>Kr-e*S}N6))7ZxSxE8I^9UXGL{y};iSGtWLj{t8D34ky zqzI61T@F@ARM>OQfy?~Cj8T=?kczyOK}(8k-Gy=yfG;gR6hkUPs=S4^4&Y4)mx@vW z_|-8ygCnUWq_qU2V*)Rq)z)YqTzw*~QFAb%OaOj_L>X#nN^NqI- z>6~fMvmFvKsOu}Zj~_BouhE^KVD)T+-dvsfdXM^28$!}CLD5SRHfoxOs*jyYnXoz8 z`X*VPv(@r$sD~RhrbLjQpmP-xaAo8OJuTRNMsu}^?Q2dQJ<3ZbkFfc83B9~d`~D(5 z+hqYoojw~bquc57>DBk?W{y0m)41>ywHVu0h21F{g$cl+fWrggk$w&ETF-yHWoy{s zBAhsJf>WnXljnVGo>5Cv4j(yAtCh34*}~?Qt=1O3UXLuxxqbgWPh7anojZ59dhIIB zR*O!j%i5Yx&XPHA-JGT=&RMh;t~_;~zx>Ppj)g;q(8kd1^?1Cv#Olfl{a%lWdYwZj zP9kZI`GtA@=|B5th$!0aHn0EwEq?RbYkc+9U98}*BBighYq68`oK$`}L|4f2d##SZ@(sbBekaln$N;^$GG$ht75zEk3TZ(wbDK z^!hzcJ#~^FzWO76`McMdKl20XjpMw2?f+)}aDo!Wfdfx)=FllV|NL_@*XGGnFYw9d z_qg)hpKUw?XB=LZ_WZSw0b>l`$g9FnR{*HRWK{(4_;ShP zN2xLrc!|y}N#Bv`)E^RQND_xw6=VnT3a}OwhLGaVCkYEH!nwfLqHBN_5RDunCDF91}QU!%_F8J7MDgd_efUU4J4k1n?tXnG%5rv8#N`-`I-aXtOCTfHPg$lf0DIHupy`_4LgB&3drDFJp zaKM7WA>6!qgD=0ljyQ)&42em2>utYhmC@M1Q~Z+g@{PA58`QI$@&|k3N_oN$v;RGM zk_{Va?4@Yx5bZbuB`fP2{PNeYaqRFBoP$S?9&+c-9X|f#lcJ9Q<$wL(5P@UIj`91~ z-{kYpKjY@jo77Cg{X1XL+uYztyGvs8f>r~*4y6m=B?3a#e(d+E_H7&(Fb)LV3w+xR z$gJEf5e3A{G(ocT-aiA{aVTF`J|CQ;uM4c~INXZkSbZn_VJdBq@UDmqcrwN|?bq6@ z`hINB@V!y``ECI6UiZhfq4=&^#?Dg1d=0fBmJ_7p^3|x3sPdk#kZ5$^?^l6^F?#wy zJy5Bppu5FW8U8%Zs|2>7N;+NXL}A1b=xb5p&{`i@l>>1FWgyNd47s6K*7~3hHh!%P zjPuaiDtX30CDqmq?8o7Cx!b-jl28wQKMungw~mL)|0dU{_KoY$e(M_CSHrTO*JG7Z zh_#4oqP4+V$^?-BSf3axcu#5t+G!+&y72&g(6?2YL|bdMqy6I!Erw~qUbJOg|Eu*3 zjA#5uRSqLYVK^h|OazKk)T&(V?^7tv#Uc$sU*o&ckYJQe;xH55t1p*tm6iab?EVJdD+yP)H>S)8WV(2YyQ3_P!jXt$O2IRS4)g6N&LcLb zz12k8eN65F8|?%o%4alHn#^WoVo?(*Qcti(;fz8h9)RridfsJHJP6`)2bo2S2STlN zxGcw6M=m+eC~_m@TCiGUUHC4u)Ko%EH!u!d&xXVbKA@{;O{NrG?XXIb>j;bn@Gxwu z97|5Xlmt*d!Hsnx4wJ(JX(B}yZ&4oL%*EoAMoWrVL%xHY`}cEI6+}wrq?8ZBYyqQ$ z+~wSPaEJHauW|J3F(#%Ck~v4uS{^*S&(h+3PM$f2Hg)<~KK|r7_m@|A>f)1-CTw+@ zbg~|~4#KPmD2FITq7uKqlngRK0VTc-q9b7BgV{@o+7=8q?p4r1NG}x>57bGqm!r~w zihDM=*##xXzaMFVe_panZ;cn%rz^CiQpk!dISRIgun^uXJkWuFoDp7$Gz^JMe1sqX zg2Vbaml230DxVSXE}9jT25+4b*GjQx2W3Xz1{H=eCR?gXEUHS!Dwa24{VCBjZG#dCa~6e*HL_|OcUaQWg#Jv0z~P`JX6W^ zs7pC5X2jH$F+dgTD$>}>QVZ{wmOAmB(%nUTytsmh#nG++(BS{B@R2zAUf@hBvn^q6 zvPM>5V6?&5y&S*?sap}3UEVkEab+(v4Hc1VG2_Rr!V8Zs$3sanV{z;Cc zDe2lakCzs4v$LE%b&2-hUZZwv8QrLnq&3nsA#F5}$&_v?2qhqnR;$ZqGh@5u==H(bgsw_CI(vv_Ca~T0{6tEiL&^xK6dBps$%BK)=*fli zWxV*(OI*5m5$CLTqwKWNM$zf?Ie6#@hYlYt60$^|+wN|1;o=jVd-5C#O}#!ryVK#d z-~Nu*-*|(q=C)s*TJ!CfUgV$rmw(FS^fZqiK45xwj@h|+jvqh9tFOMwg$w67cwm7O zCr&UkGlR93dtcq>*RTDSrR5ctmLAjZ_E?x-Aa~BY>^fV-MD*l7_AIRS>e{Vd#r`N2 zhf)zMU(0iE87z3^yN|^h7l0YUo^$94&7~Kg=fH^*Y;SeQJ2^l7`A>Q9@B!a``9+-M zOxEjMzW5YNj~=o7xWiKyFEBGVN3)yLY}c8YnMce5=Po{v(=&W^`wrU=x7q3_>a`j2 zbb(jErqgO+dwp!4bNAj|E-{>`1!C7Wk?}^SP=Pd zFRUeh$RpydevOpYl=09ICrC(O;R5gV;d1=DPD73q?Tdh;5}$Qc;}66Q&RgsIY;hxp z#SN@=1%Yo#A#^B2G*u4|75Q?A>93u)cz4>+cNQ1;ry)YZzHA-lwF*WE7A@hxzlcGs zi?A*@RqQ#jrG=J=CHTXHJ~&JefIPsdf~8a(r=5!OD3ES11yZWxs6rw{6pPD`Sz1{R z-+LLs1`Bj?UID)I)mPlQb&DiTFUfWDmUAZ6ac@UviDQx@Ou1z11PPQQ0mVv@VCHYgF;vJU@AiSi9fD1T7_V$ z;`3)nNK~rI^;xgieQ20H!Y)>?QHLAj=*ezlz1!Ru@`l<;%f_`Q*>kZ}s6dnz;?^rp zFpGQN{Q5cr8GnvF1seK$RJ@aMW$#6h_j?T^&g074Yct172ed~iO{#!iAJ^~l=+Q&g zHn%WI4He=d>X0c`nGnlQ2jR{+bTag*dGhDlyoQo!J_ExxoxTTmT`Q!)Zk(7Mta` ze(q!Ii9?x?*aSbPR-i9FZ3#+|B?+0)bhK~K&3=oHk_Zm-4K+A8Otc!Gt6IZmED zMV1S|1~Q0 z@%|c(>l^EXT?}i)A4m;lnO6f-w_`2|>w1`S#h~YL zJ^IXvUa5~RDAgE%#Jv`2UAgNjbkBb$gv3h&QzXT~hE}Tl9Z=$mII30n$CDSG%Xen*4A}O((kjmvPRN(WWkj*CL==x z7ZV3cv1`LJEZ0lPC;hW}t;WpsG)gNTJ$T43e(?*ImX?C>qkS@8G*Q^6|$X zv%0#9)(J^s&{{J!ImOH0d5P0!PjT${A<~*hi$nzHEMI+fmv^pSrM=x`duy8?|Kumk z&CTOPXt&!mn@#$`RsGVXOSD=o{_Vf}_uRa7+poPmXJKZB7hm=XcHX^umCwKUf_|Qb zbzJU)xM@qlZJ4I30Y#&#>jkPFx4U<7Rb>bov5r*0h(n1**&M|1;fEjc`2GVXbltn1 zudJc69`82)mQUyA$c$!XV-wfzkXpF@(I;$dY;y0`U8YVvi_)5!Q%K*kt#ax|j&NwU zL9^K=bK9(To6H_LN}oF9Q`Dr7(w2-Ci2|1hjmZWpn+BU1l$m6*F-_jd(c(CG`Y`om z3h4@o_GaTs=fr~%BY-%p%W%C8-Byb_hMF;`sYw!(u)O$)*MI#R=H_Skum9VBN}lDs z{ni^yO-*v}yWhsXhl_^zmu7n-5sCp^< z0j;2@SvX&xXL>FrpPwSDL1rytp`T?~>kuore&&HFqj3hT5uD9^5}g3bL^t(#Y+wPa zz5BMsd#G3uvVMk09;_5KCP~Re=tplFspz|S>w2B|c*351#=tefYENsd_#_p11o)g! z$Y%V+;jAUiER9-@Xd#nqr{YPR1B%^J(uJsb1ZG{8u_eT{4*n`iNg+e)4C-hmbBEJ_ z3h#tDE#^z`UQ5PX0r#9mYvXZ`ATP)G$I+V!rGkR#A%R~RgBBI!LG7*ImGMRex%0rdF0w8=Z^bE|PkM{fWPr!+c8vmQ_Uh}` z1s(QltTO7kNbnGVLD_}zy6tW{=zgE=1?Udwki|cP&-b#Hin{1Gv7p+~hJC*Gb)F4h zqrjp4wtcsL?ggChyeGx|uM5-+m-kIUuCKeMcWcM+*o-?U`HuzUhA$Old;eR>2=_a0 zv8}sdGy7`Cm_4^w8}{3->hBS_-!Jx5ZQsDXvZg}D0~NgKB#MOOt0vWp z>UC^uG=SO9HE!*U>&vbf-ClF33Jr8*oulR|2~Q|a^JGwos1nEF21F~MsPIO`f7Ime z2Z7)1glg=Gg&JB!k#LXrem~bJuynXxqmbg_o*J@R8Y=VuVeieFEIG0}zn{BD#NBew z%FNnV6i`3`4G@h6x{2v-a@b9BhQl#wY&4{eq-T8)eE_{uW-^mxGA%EZW{hn1Ot0*f z-Pm`a3QMh7S(W?U?~Mp|*MqyqjeBz~aLi^gh5?nC5gs1C96x@}-_NpNn?Jb7NS?=X!#u3_PN|t{f5KvMewQ-h|LZJ{YxWABvAa8{rYigH( z!h%o=lSph;fj_ZVVIdjoT%n~?6eHoBU8J$2Ow!havLObe5JW-}02gT>(GUrV(1v7S zs0=|F<$^#fORg*;7RXA3oT(7aB^c$z)|!e?^$PPVOAHr>{QCV5xtC?$)wne7*14uT z(8^+E7-8Q=A1_V{sHm6==g#xeOW&X|wE!aKm9M_UaJxyIrd+!C6f&Lyneg(k_oFp6DiN9C*sjLHhN44V`MU}%sdWxF8eQai$D zzEyV%xD5~*oA1R}9tjhf^Q7T?EU+go0eym0t zv?bGqT;~|A5!x^ur1X0|q!3I^&+zuU?{ne&1+HCthWFopkNVUMwZ;@XyDgGRl~@1K zKcdxYa_ZzsHXf~0sU(14I7}I)8LJN-kR(;|+^x%DI;4_RiQ@>JYiI2d+BP=zBIx11 zU!%?!F(ZW-8MWC(FY~jN|h>@+Kwcf3K^r13c z3!(f0f^$c-!AjW}h(X3NnNjT21z&yQ5?{3Mk*kK^$0;WxL$4B1f94WZ z-C+CPI%4RoQiZTY3sp|Oc!{00F{DHp zx9%%eGCR=BWty{Cq~8;a0X)5DC^iQ@CnvaDxng3r~YW2s*tkt#*^UcklAaC!f%6x5q3?o_z92&YU?znx_2h zXFub;_uc~pN=b}1%*@Pi@zOcYoj>E~$Hyji`?%!M!$8quoXN5@udh@;{ERnh}0>5!VyR0jhu27Z64itpGe*1xhKBHiV%GD`LD6vxx?+%kLmY2WM;tT!~0AvJpp-&-t5ra$RUY2 z`NSzUA2cytO(io3Gr*FgB83%-94C(m>#S(|trkE0{=XxVkvmip&ZXY!oC_Du^PNBZ zF7;ZS*MIdJKK=A2Uw!F!5J(c4FkPz?DS-z)o8O1Tt~1^s15|W98=6hb|DY zL~Cr7mL zaoYa4AlPAZ@<08Lf4PTr|C6}(XWftY{@?dH&iDVG-+7$Z52C5DG6f$FR_~PhG2%ak zqvQd7JP1EIF3>!=UXEJdUzRsX>8$p()=ED@czlqV z1G_KxYO;;{@rB2=*gTBCAD5@Nx{ePBTWg6%!M_hJdFaM7d3V1~h5O}3?*|C(cO4fH z^@R#F;-C|Wz1`b?zz?#VEN={xbx3e-Fb7Oz0ooQy(@2;qe|t~2q;#$>HV0|RZAzA< z2qb+e=*1Dalo%DFP&&#D;z~)*BPc~e2$TmerBaAUdEwWfbxvjsLdGarbunXfj_{|p zE`F0j7*oVJLxB|r>EJA+R2XY8#yHm^8T#+t3h~wm1{ZYLIyWC_oI9CFfVh;+w6s{o zP{4*pZ)vnJNGw$pvr2MheHk-Se=pQJ#sUI zp`{uns3<~NXYnAdAeK((A7(k)LM2X+!dnC6eT=ZgRH%eFT3$&BE_M!RbX>~G_TBeQMlKRvXcURYV{mr-8+U>BtwZnz;7fGsBa-(_g{rAz@@YJb0nev>To`!Kwee(`fBf?`_Cx~Dwh0YfQW z(t$w#0U#)7h$29{3CYi}J{zVT(V$mX9VA=k?=tkV5Ek$*`_`LgigLS3<7!G{q`@oS zKI6NGEUgcN;PwJ1h!o*midc7GQD_0F$dOv`;cwp|-R*PR+@ZbNWJb(z=Z!l|EjH-Q zW^~#q)!8}b8;cD38O;Z~APm-Mq)t)wn3}B-kx;G8u&^@E=`&|&^)rU`3ew~VYss}h z_;`2{fmC3nU??Ds6t6ve$m;zE%vB;xJ|OG&sNgbW>Lg}MRk;~iR@0QqR2{48oQM=B zl*Cq&;Ji25SB>L+!W!ol5NHDS0GXpTCuz0X{Ka4V1^@Jq|A@<%FZ1Vr{^vgYLS7tb zo;r1kBuP+8@$xILFh4(!5SA><=yv;P19$GOvc9p!XPaPtV|_-vyU>i2nT-MaAcU?m`P%}T9CWoCg>D;KHe89Gm$^`1`2vH{CB@xh|4X!5wP&DbV6R*>qp624k zn1@}(iSu(*TbktNFL<#!z}{SEQRUPTq;Z92ma%&KJ#_0nQ>(L_dDLUjhwa%pZs-ln zyKZ%J6p_ zh|OpZ5&j@b%F+8GtOqF8Z##zJVvX}BuaKJ*VVw-Dq6j4wkrgg!ROI$GDJ2`jjGx@Q z&DqnZSw6MGPIH&j%ZrFXkJ_NZ`BtA{?&Hd9ANSTmW?X_c0YsH5yRqbUI-u_lsI3UG zOZQ#}Dd>k;6ou!(!$3^FSJsJ2{~9|2@Z5{xfP3V2ZSme-LU=1wCzk{_dhEzOzYOjj z1S%Y+)K2P;#~q*Y`-hb|*uuybJXsX{{^)NC4!1&@e0L96dBoqveh~CUe@({64E;IC z5a*!pOzNt~0VNLuk|wXC#%6Eb`{OzPNnHQ2+w~v$`*s3YI*bm+>1txra|G?z&ggHa zr%vtxd+Xir_ruB^_t_-hD&H%wsl(QjwYHG+Ci&iB>uis1Iq{eEa-G+b3VE@pru;2@ zvL^v0t=8wh@cL0HVbNGSj^Lk@eLd(p3IshAmkavZKK$({6pVAwvc_i{ z@d`q4+e*O;gnsc8tgk$<;E)HL3MH{Na>A?_Rnxw^8 z&JW?c4-|2&28|eI5~#-LM36;-p)iQZB_bN?luQa_oS?S`u-oFyFyUHl1^RtLpsU6L8HTW9gYEZ_NWUS;Ff13rJ_b9B!sOhyPyEHH^h zB#NtN&Qets9({hFPP0SSNLaZvMXw({duNAgvVufVjbn70q0wV;Lj8b2EXln)pqu&8@Qua& zEL}<}XT1b&j7oxv9Z48t^yB(H8s`verEHN9%4!c7J4K2S01G9BtHiH;S3+_gixZw) zvNc!QcuT4BWj&uOHliZwm_r@|km;Hp+{vQSaM%)Xgm=><PhPpg%G^A!fB&a!0w>R%;mrASeE!i52J2g#d*Tw;o_vxg>$5y) zwR!i$kGOf~uH)<)@-*}F9(lTuMW4fbg|)q(py$~}U=ggV2RI4?Vl0;F& z{QNu%^YaML593(!!t*ci!t*cCZnbH5T1ce;cc}5%^h^SVupo#rmTdF@T! zdG9@Xy}q}cpRj^1)`IV>Ps&(c;w5J2-x0;XqXQc+!(}Nt9U=w;W`*SRxwA|ufsz&> zTr$51n27~d-A1%`sH7?BZWr1XAz~`^8vRtWdFL+cNlsI0WK=;+S4mYwQjbWgF*CCb z7;5IvXf9lv9K&eGg$vfCQ)^w}l0vy7Q~jiu=+qRs%F zW(Y6CDht*cnm_wFKmN&2up%M@s6@y{9ouNo%`~<>*t}Qgh0B+DqBh6k=_!(vhRDm+E)|ZOCG8J*cVnIIu*X`goHXjY zv!7CTn6iA>Wvwj^Y27D;Pw+~i3r*G)+~Ow%gs=z560Z_MxMY2iAM=rzG#dBf{n36O zWQ3q!T4VO~{V33FKale%LzS{jIBMoW?+Bm7(b+!brHt0$z;q<51o_jB#;Bl0M!$>F zSdX*s@%Hc2uOP@f)_<@i*}nBn{4I+8VH}9r4;VTQ0J`73OHWYiHX{;{GkIou~|$m{>eHfYj^J* zv@r3GGVVoXfP>CW=o;<HR%Lm41uUd#=G<_N#%N8y+ojj*lV%zbMMO$5?E6g4B3KsUQ3c_^I{+Uu(}4(K z94tabC}Mi;9&1}$v~x>l$7ze13ifNKNQ2<#t>28tOcNndNel?;stz(jXhabLV<2$;x@Fy@^70A79I5p)>G-krk-}8zB&(^)C2?10b#Gw>#YYUFue|&+jik!b>;hAj3LC8^-PSHrYidfdvbeyd^XK^Nvcj;{W~N%>(z)|Acbg3QeRsHF9UyItW`1#j#`H9|Z{22VdyCM)z&HE|J8N2J zR_!evedbd?x*^6|7z)7zNGV38N8{$eIU!1WhQP;2vxRkOfe-t)WdKHnvxamDA}qnO zxR}$h{zqAiigq|!8}YfIV2Pp;evXO;O!C)qi`;iaI0Uc;XRaMU_YqBpu?e>Cws^N( zqX)&r_;-R!zO|z^i;~bGa0dYfr#M#P1R*SQvomBmC(jK_b940C>zq3|%{O1V#+}tI z?%cRXcbF49=g2Tj#8z-_dY*6o&I{aq@PKZ6$jZq@hQo{xKU!rs^Ksq;5V^!m!iKeE zIe+dnzyIxT@#NL3-1_`BYH^xZzxBI3{le3pScttyd7d#fHI0yxcDv2Y^fa5BTm0}xKVW-jht$VA?)CfZ?(UW>vz@X%H$TU7 zPhayfJuU0&kJ#GYqTlax;oNyDNzB*2_7YdFUS@h~iogEe_qp-W4I%+DbqVvdfB~BR zFlDMSMXC+MAdd)7|6|k``sfx*&}oZyjjg*tFOH6+QeKpER+`p8t_2@|ahH3)ew`XV zo28P(Leh|uGcz@=pI>58CCE-n6xR^50<0n%4Cyp89^^gVO1eC(8f+9(O{-+WVzQj1 zs<_xVLpy4dpqTA!@}rw?(Tx+%4F|mT?%OP_4v_97V)Ps!|fw>da_~?q}{GBZF#J5Dzpm6Ns!rE>fUM6jxwMLITESkQ$;~qB4QZ z{UV7JRtbdibZacy=43W=a)be)LNY%8Q!4M70&=cjB zsYnn>cTAZXK`tddW6Av?hV+?Rm2e@0q<}c7kk40~?W^#@q_E!dKFG(yg#q%Qzcz;a zi_g*iKvW=TZSC^*_PgBa=FImr6)TXDbjk5zfeY~=Nvz;>qt3+>i!^emYKio;6T-Vp z9?rR2M=Nh-rTQ?1aop%*Kb0i#l0qmNDb0A}2jRkV$iQvvc;J|#ZN(v_`^R`U#!6hF zQmr^0AWO-uJ9LSn$c1z$C7?JCXt!UvgYHXFmSIXkW^C{_a((uZ@(V8#mvVI?Ku&ps z_hS**b&zv}5MBq{n@xH&*6z1(fFI;ZzfvF?Vz&mh0y_Qp_7dPZ~J?!J`OD%1l%9zy#w2QjBg#M z&dFK*q8)p#3ZyUGcQCb-igt6C z=FSfNZkvV0CE~ckU@$;bW9H`?#L@}s^=g&bnJH?uI#C>R^7KhMoen|>YSkKB>zi!$ z2B6@=`7?a|)vq&u;v{$OJ>;D?-yn`6e*X{t0BZz;!NA2#G6thHo9pY`yT8hCm|}#a z+v{=X?p=1awnj>B=(!yMDb`!A2u5yOQV5K+pqvHQ{f#X?PlpWkIGDNJ@A2<$-Eg&9 zN5jF@)Mfm1(eji(Nft_=jG&?gE4Idq3+I@LW1>ogjSWggSe;{|6dk3cqEAn@Pz}NA zRGSYr?x4~Ywzt;U`D~TyBSBrN|y` z5#*U6A7q#`gMNk@Xk_k_e&pU-&KMV$Nmv)x3rlXj<+1ga4nq2jtS%{n(FTnnvj#1U zw}!FgJ}HzG5^Y^Ddn0gR1*Eh0K*++nvuAjnoDl&DDXmSmRv}35kt#fsrX8 zY3;P=ZtpOiDAvFDj9XJvJY3&pcjFPAR+G2idff@lO7Z$@zv1&wZ?LtwiIf(dr*zUG zpM3Hm-~9HsiDSjP@4Q8?+vdd50>Ww{2{QI}4+grO4pWs1Ph7f4YjcyGt<6H56&}DF zaYX;Wb@Z(yek?q2k_Ri8z-vZ(gZ7#6#2EhxT*;2yr3;}XFsqWitSG%9Ja`$N`U8vL zMnC?p^`NnvtAdaEwm5@u!FYD)(q2)jr_4h~jd8yl&rd@#0O2!ln^L`N zP=4K^-9i`AC*wj8sTf4W{d?=QnoUlxEYaF}NMmY-OJ~pU;NA`{+=Lp@e3(yb=`@>6 zO;xGXB8<)1Y3`Cliuz)m&aj7#1g&1i>ed!>k+XI@d+HS1tq!+W*POy96|y*CwIcf^N-X)GBUVQO)$aBMQ zUw@sG%ga1>{W-q#oj>H)fA?$N|KNRE?KWAKhiGX9U09>Ja^(tt`p^HAD2kj5yVhvy zvcwA^Fu7)CrbeVBi%U!V@jv}1NL<(!k-5XV97}2qX|B1yy2_7#`cv+&Js?<`1z|oI zr;cdb7F`cbvnA5Oml1@B$*uwu_An_SPSCMpFjM2+d_odCU|uOjB4esp+EUPbu*H14 zPqmv-6%lb1QJV@WoaK-YSWYaa0=DCK&LechgRI>S11u+X8f$IAXWTsq8pn7&qaCrl zCE@{HSGV<<-zfk*=dIwrV6;U@P2}DD1t67RW`36Eo_&txl@8J0PD9vF||4K4Uv8hlJZ3>wznEH&<%27Z`d7R6C z?{^&sYzp7(^%6EVr$YC}$NTS(qwG^*dHzb9>zA&xT<@2u>p!n6%c4E^9>?=_56)x9 z*V5?c@#@#!6t|YtD^^*9)*w_*jZXqeo&xMPP*aJ-eA?xcYi&79TK;_&#)u|r$jA7kq z8l5)xTYV}w7|NkjydBqE7oDpQJjTqBNL!UkzT#X=toLo7}BcDF~KPxz5RRjgZ@7(jphB{#;08|N#^y51 z7fydZW@~uY?H4l8XAs)Gv>i5U?Y|cSB#c?!+tKD+a5IkyN)h<&n7ZIwGRjnL{5MB= zSAyHQ-$2U>tAFm*o(Zv76nw*d)?3b7UtaiU0i+6wm2g%Yrqosk;*A6i8OjTb@`(AM z-RJY0Ux3l{Iz86%do0bJMO2*iP@)v4PcG4Hb=lh91rviXNGZ5@;WXd;M_=Xk7dP3w zzr(eQS6N?M=gB87aOu(#Z@%>@{Xvf7B%YHB<6I;yr0D>yb9{h6bZU9<#VXHz`zu&! zu!&-;+vXR){uQ;UX`cVeb6kJ!IyR2^#p`ckEku#><2BkB_v$VC%M?W!Aw~yRDq?YA zfiq{$@a(hCa_-zY>a{wn4_0~ejW@aV*{6K%rLVfZSR3x%xyvtp@k{#s0na>hoj?4e zKjPx$OT7O2>)cyirQPn5rs-%eM{s}j0XIJVoW@k$TOPZ_NDkQ74EqBvTsTXmQeik8 zvbD9%?DQ0sY85Rd;M`mpW;Ci*<`(8raTKsN-zPWOBVawOr4fA=^e-sUc*un=y!=r1 zys++2Cld5^My>=yi9ZZ*){D8YWKuG;nhlYos+PD`ArX=j^Rs;G_rA^Yi(h5E-QnI} ztzqmGuYT<%p8x6#oH}!wH0$upwX3}Rt#6Q9t2N7H001BWNklMC!wS;J9npxQ;8fpgG z(v7No+#GN#1FbE&l;lDnYLYxsXlq@V8zS|={TW>~dgmv-0g^V0;2wAwT z^n0eKQQ`u$3Sft|4!Cq{-V}3zHCXLqhg%ocM2dU_9to%HMY#X7mZhphd7AgA*Tp?C z`dwIvgdox$s1okrBX}_=rD#k|kyI;mS}hulDLUN_Qb?-R3VD_jCo#rYvMeorD&@jH zgoFQHuU7yi;|tKYFeJE{lqn~RvKOClo%Ecwc;FAn_K7FDJgoA<#Z&G8K}v*F7;DJ} zJ(f>QGuwz6_E$(V%kD7aPP4~qH$x{e%TrUVRH`IGx_Qqu$~ZT0pxA?KZZN})VQ+vj zmMD%fRYe-Rc&Db9!5{}e))k=AWl9Me({;XR?b5d?R#ZG~ zjSLqa$Es&i-`KD0q>?}RT$G8ckc~_4gn0>kNC>31Nbi0#!XVt?PGe?@x!D=&wHn%J zFos&K$}6wD!u9LVGBq`Y)|%hG_8L=DQ(V7(oom;g;)4%A=E1`?U=-qPzkcl(A2N)^ zem{#uaEwCQc(Ox!ZyP5s8;mwkjK3+;zYAeXO7&>(DH~9@z1_{D+u!}V@XjdCWxcc- z#=77QOFW+lfMhW@wzPh{JP=~zhwm9i@|d68vHA%*C%#?9yp4mJM;Yim&UG9RGh9FX z*)@LnvulUG_oXdGOGZ5I!AS5smgV39?cUFN?Wpy8Slvfm`{5UpSi_g8^EiO@aoV+C z-w(3z8@peAB}~-;Lv)<+J??c}0PbN}{PlRU8aW=WWwlqVkNIt+-Nr2k0} z^l=V3%56Uo_X=t!kcR}+hqTLDON2k+k#cWkg1wDD5>cM$XqbTg0Kdr&9$cwcDofuk zgQEv6oKWsKz~_GLDKn1Hm-4eo#xmK4un|o@J&b5vdk<=-+vq~3NNo7M$VWWJ4+6p+ zzygXA7OivU=B9b#;(5-UJ8=xXGZ{?DuPBMD- z+3Wo4|L(s>#frDyev4oJ`tPV!szpzPECb`dBc(V%Aqj}U0Yp+ugz<{^J{dACxUBEx zm0u*S@rsZXW6BWLg)i7K87+p~SWzyCPo6T|c_04$ZH&|mWX^QGK{u<>eUu@#w`rWL za_{3?JlJTm5HE4@^f_A9HAJOBzn}B=Pd>!XwmJRmDZXf}v7y)o^QS$P0TO7E7YA`njbk+DKV!g(D_tKDLKV;8HIh~g?(LDnB&dqb3!g^X*ACC@Z@mO0Qh7v#-8SK`IRjUmq=_lZO#5>kk)B~lh4oF%L_V0>r? z=ZfUOK4)cXFw)1AbOeSr4kWPtZy^NM$6^&yI&!i&i%%s{LXaRygdkE*IS(0WyID?8 z8f@@Nx52I?5c&wvDVDA+IeG_0g23}kA@#e=y`ii?2hcOf_tyHDm&W(q7H;wmL<@1y zD8S3nmMMH-bRW9h;!s-=+{f7?tOYl4{}0xBNWv>0!iK&YA{>amv)e_IAgR-8WxV~y zZB|ZKnOjtJTAJscd6v1wX}~u8Ze4#wZ`(w62JF*-$DqN z^sU?J&>IXrfbRa!(##E>@Z&2+*0Nz}EaUs)FJrBy-D>mh z2OsdWzx`XbHa8&8u-ZZ6#u&0JXSdnp|NOuIFJ681RbF}J6`pzKI!`}yowbLLc;k&X zc=OFSX}8)%K!Sm{qJb4Q9fyykkVP~BWT1i|N77V1@m(+u(h$l_y6j9tZ!{_ z;=~DR^*Tx^hH1*i);6_jjaIA8qpgiG&~da!hXDv9vR7^>M#{Q&1Z(7QKUhQuuLp|< z)1@HOnmCFafMSK`^Uiu*M-euXXyZWpW~amXR-0!}EhCIZCNcf~kWR0~($X}wMzFp; zL#;8*XP@09(;cqOKgqP6W;(0WUR~#twO?V<44GT%wS;_ilX;P`aA}E0n{75Xx7m4n zi^|3(3+n?a*)+F)^Z_l^qc#1AMAxWw>vWLxV-QJ5nBsr>LjmpSW0a`QF&%3~5x3SL zAX4Nmh$Yg4ny#GZbs`+0xP&u~huDI*1+>Ci;WC~Z-s8*{~t5t*<&Gv)e80b8LV{u(rHR0BPwx1 z9K{HqiAY#WWI^Si0`Teq?%jTewLGI039_)|5$IrC_jnvovwVM2IouEU2wWuSef!n5 z=bL_}MQ$K#QHt1sCcw3j1Ka`xQD|P z>q8C&8lX5Vvth3->I*Td_m1abz*PC!Q5IbL>B5t`bNT*p43zdOS8mrt={_5jJ=LqSD4$Qz_5K!@-E zJuh|u!5FVSti6#`60Pl+Yu`Z(<~WOZ@|tw--qYxP25fKrB`xs4GNsZJlXj35>Et{6 z)o~o~b-yyjSo!~re;!&jnym4DxIofJw$_zJqC|)w=xW?U>IG96A44Hr+{`DhUgj&; zpCeB-JIx(XlI4|?WZDTN^;(UqSD&OYHANJ~NGZv+q1EoNw6w(1(g|LF?YFdccDQ`u z9Iw3e0%y-(pf||)`LF(t4?nrV#WTx%{j1Nja`qgLHg?(C-erDafvt@V-hT59B4NPf zXsuoL*f=T8*~l7fY@_$X5PBR`7O%=U;nHV4-ZOeaU{R&hWm5Wyihe&#$cee)^)?GOHn&IjA{P0H==Z7Ok0 zy;fy@Zkm&)8k{~;qt=MHcYm8)ORNU-!5WR~fGgJ)P`b-PZ60G4g~n9OtXH|D6QY9h zQN&O6#3sc^P_qT&wIxB#7RjUBf7ZEls{~_VU@hy?5?f1@8|V(*|53~^uA<{$5sDX_ zM$FgR5-=2mJtuKNP(B&CUCI(&ho52Th5D?~puOd_=pu%|Fu38kf)cMN<$2*|ro0j> zLd@AR@3Gc}(y|WF7E%=P&#ds)kWvvR2{MYD1*EYIhfcvslwfmqhLck@<{MRJ>J^4r z#%{O6Mzcd)YY@i~N=cGxjF7gJbVL%#2qo)8;Y8nhG!RP^#RwL##t=o)x!mU(p+^Z0 zyk_afQA+>5!5BuCt{S9u)+$z(HYyM0Y5#v%dlt~rS)~}^4gxd=ZQcKYBN^i?1*|m~ zW6>6JV;N?eZkCZ+OGQW)M8vd^RHbDmNoc4F?QWk(y_|NXf{qlzpT-3+Cd7EM!U+x9 z_-Ixh2-QVIGuMj{TiE#a=f1qL&b{AQ&B${>6o>ZK(-ziwYzJL4tf2zXG(oNLZrz0B zTYfyXXDQ5f=gg&NS^Hv*LGuB zmUm~G^twZKw>Ft>NZLCCW@Zd0U z{~gz!d6v1wdES2OZQlCe6KZi>tT*dH8!075_>9{y;t5g)ePiFtt#kLV+wJoH`|s21 z_1N9r<>t+sJbLtyZV8Oc@|-NoNVCi-eljB0DL?0`1@5}6SrP*x&_AVmz;a_#&I|MHK2mrpwQZFoPk{Phs6XN^M^3yQ7dP zUZM6f3?jsWrjU1xXG+!zj1Xif?%ABx8G#`}`~6*5LA{#r(l=k_%9B_5+n@d|jp=D- z=I1$k?gI1k^Q=8wC)1kh+$^nZ$jNi(*jV4-ycPW8?|zpzUw@6$S1!^0^fOE)A=jFL z(T;BL^yMv$jRFxVtafsfkRuSs59Ube3?i5@MW0Pb_~>a_35oI+mwBF}y;npz*LUEd zSQkEET3gh;hs^DCNV7go0b59VNkXR*k<}FG+#FebihP(-NfLIqx4{^uXJ^^i*hFgs zHpLEeYL$f92)fN}+U*v~LM)^MU9Gq1x6Y$QSsbqG0X-Q)5g>^W#6tM}d$gx!v7%E? z7*=ZTK$M`AJ7?`eW@YbH)-NW3rLt~w9K{_1*fp*XjO$Ego;3PRN(IKCq6mR;F(s{` z-Q4B%*I(nt&1s^@9fI}yeQw^kLA%}N8(;qhSFT*)_U+qz@WBTNA-HnoGD}O#M3Ev= zs*vfzrw=kx8RMINok<}`k^~6)ri|1p!UG} zCcZa`B^(FTIqteV?D-_vRi3NkeB;Ypj|=o3rw#j$xAp5Mta(w$NaX_#Aw)srht>JG z?U-!Oel&QTI*)@hO#+NZePa?c+ONI)uBij+dYtQ^`VYhE4(h{k+Az}d%lysDwb(*G zJLrmxF47B+b>hn`j>kE%IqC$UsKv{UV$yPS;=2cdk^6o7IPELn`!Zv3*t?U&X-j_} z_@&Ymxl_84H4ws|hg%o{U8PscAM%7@4fMKQKKb|tW|%WQJ;RBWWlo%2VK7Yj@T2$G z+}L7%Zh>a2jf`ThUAxAqQ>WUBSDs{j zYa2|;((*j>OLN@({5G5G8(e+j2~Mq?;@+J*2#+dBFVtd9F{cgyJ7A2l6Px@<0KvN~ z?K`H{yJiK&P{<;IP`R`gjK1KN8zJ?U|8mv_YU>guimyAbh7Umkf_}n!NZTh`73)LE{`8td9%RGDiDy`NUFJ65f%>sYf z{Q?Zop1{z;4%eZ8Dg)E5nEsv;M}!+C#)^NoRJk=pcPY$MF=oC5I|xP#D1=v zq8#8I#AwvRZ_W~{%%Fr@*alq8M*p4wf)$)7lKPGlb3w;GV{-v=*i!pFuJ^#-(? z?IO{Q60*n`>hd8uDOBREc<_8g7<869rB6jj3yD(12+(-}yh`Z+Q0I~t<~azIBcNGI zNu(l|7(vQ{w+ehB7+e`CBuXil=t5cot|6tfu#BWcO63v*1OOG`te%9$C6<-+Gfi`t zG1P`6H=K;CELW<`RAOctF4VyKqYdg7sxynIsDjOgZq8(|CAU6qr7f)O3px{SJ!xxv zxCM)qE~Z^BeFz4D4=Vsxy11Ms*XUdq@p8lZ*12|u8<_@M78)Jj1ebWphHnBQQl#1` zM#fm59o{Fh^6~X@VM(oaArNrZp_y;Hu%Hk`K-CBu2Bzbfq!u$YhSly6D$|_1aE;-X zptZG0ECt3ikaCfky5Q~yx7q4#lL=@n)VcE1^K?7!v%R{@tq;EdsfjC9gl&-P86IvX z%uLs*B&Sef$cfWuF;N3lmF)+s3^s0Kb(c>+`INOAx0#D#=BqK143lZ)wSMaBzil%e6R5!M(!Wq_8T5WH21^`WtWX z!G|A`xzI3Xm)$-Y&Y52*=3NYh&aNMkF4DTnxn?87seD?LNctfEa2^kOIy+; zE8KU#_xpkHaXqmnt)$`Z?jiAi!!i>;--<-3nGEkTHOxmU2-m zWT}jM?xw9IBM06S#?8-y_B&<(>PFx7VNZ%yy8Bv`_DJ96kTxWMr4kR23gL6;IbP-B zK6+1ssDenU>~{Oy`0R6DdF5q{ko1Nr_ul`2QzuTKgu+_WENJ=o+C|D?%loT!Xn@;+Ld6?A8_I9S*E6Ec>n!(eSB_rI1%XCiZX4xakN1` z5JB#8IF3>ZBP2q(d9j%$8xF|vhnIFVt8TuW`*u%SdU?upJ;7SVKv{Z1pxp+?=~Jiq z_P4){&NXS4v9!FzZ+`n5mKGK{v3P0| z65==}O;gg;#be#iDkt18w$6R7aeZZ+zN0uIHwKJAxNHk3DQNEQ^3xyx$jReU6y2De zo#pp_|M&RLAAE;iugAlO4;c=JJo)64)awn5wbW~MYSoGxGe!w_%4@0^s{rWub_oQH zdYzS(6=r8=xPALJd7iVdut2R=Wn*)LW~+r#ikX>dW@l#@?CcWnG-+4^*7ZYM=N9kA zMieyb<`w68jz#NH?Dfz->*wA2xwi#R5k)Lxzx`pXt3PgM}d+?CsPkagVN%Mxbi zptkP!?Ze8K-+LT@@3_G4ajwZR_zyKF2Z2#X^>Y$jJ?uIj7PI&Me<+~#pmt8S>!>n+ zpKJ0EYH}`*0x~B7+{3=}I5hfY=;)vWfWy}GvD#Alch5HudZ!pWOA*a=zlC>DV;+ad z4gwnYYv4$w4h2hHvwL3%iV+jUF^L2p6`=O8$=~nUv57uR?nKkelO8h5m3Fs%+Y#*3 zLTzqQ1i?K#!*8hJ9shOf?~i@ok|b3k8I!~@Cr_=gbow-%PM3GydY5C<+GzTNj88wk#e)ayEG{jhRgBRY zDz+%0$@3xGn;YD}bDQ0#uW{z=d6bGVreEyYN=Y)E6We6p@hup@=*igM66azrbAd8) zUl81TiG}@qG(;ukFu1OVZ)T_j?L>omM zS4f9BS{nxaKHYAcPG^mU`57)T@>O zr+oWDR&Y#s*Wa^HWN62S00<|FmZD6sI0#CX#W?SJKc4|GRYLmV4=Y0;(cYEI5=AcF z>JWzwS2VP&VhhPit;&3(MpCJ;v)knU)((TMHtPSU?aiO;IFkI%k4I$YJL;|i3Kt3R z06+rWWH-BKdS*1U8qH{=UDN&rt6x`tmG&=Kvo+H(YqmPpG&7RAr%#g2<^_<%SpW)k zAMeZ~Vt4e?A-y$%`D6qets?0k_;KzWMGU-#vUxQ5lM|^l|JOV3IMseEE{i zmzxc_5ZO9@skt-&64mKGPeeEAZGhe!PKm%rrpo%gx<{`-7&?;D;x zf5CV-1OAHzV~5v=p;Z5Ue>fHcpqSc_7L^M*?juP0RGMM-6fdWoG- z8U(DO1P-wrYl{{g)t1V_;`|bS`j7vdyWjtSU;g};ym9RfuD^YQ?fpGgmX>+^_zC@S z&iwKc@4a`MvMkBcguSCpaEg=jXB3##pDPd&e~Ov&MX#EInnkh(EC+ z7*kD#A`~G};yp5JZ$g7hFqn_V-23`#jD^k3P3I|}=X~|$mn2EX^JmY=$GKmF>!`^g z&gF#6v)08mw-!?w-g)aSe)!Q3`O%L)Vq;^2JRkG++i!8_&KmEEYEoD`@jZ&pBl|gw~ z;$(~a-Nc~M?nhxN#@5nDIKWa6Hg`|AhXZZRrlGU1$uc(971sIs-s#_*WwW2=nK|ow zuWr}r-#zX5>wtHswfD4doCXffy0P~y-Tk9o4#H(Hm@F! zMxX4YG_3Wg0Q`qK>pGhKJ&jhH-<&Rgo<2<1GrJ$J_w2;;1{PHF%~Xd^c~@)gWJ7TF z;AEeg>&UAxzt?f_dtDHGNmU z(LS~^bXqOme(No?WUQ{PF&d8;jz*-~wM!`l*2N>UUU>>}WSVmj`{fOQ${Mua5e^24 z80YaLGJD>eKAnvjof^N|*oc6c4Tg{qGBLZIR?CA2PpPWH9i7Qq6c%zL^z(|&{4$;S zL#|%A$)yWd_~P@gIoRK))1Bwam22$n8rtoIwYAH%T62twoc)8GwdFS7e)F7hwL_YA ze70qY9tz4>1m_(YVjKo)3+am>uvG0~GU>tSez#MZ zr!54NBOdPsV<4?(!NWO#or)@a)=t*#%Dc8%TaTer zzC&UGw@Y7zcYH;`BotH}!wGhu=6ze6=Au0x2wtI|K*C>!Xk^$hZV|ch zWE(t|mV3npaPDZY+F0zzp+GR9HVtb`R1GSvIWu? zo6L1Br5W<=!6EPb(|^X!#uiH}9oCobV#)!>NBbNW1*0tGI7z5l8P``XalJpFJlbYn zTW(xfAS*MLSJzQj-efWDp!Eq0_y>`sKG+Sh`5JyMdKHNCJ{zwL98wOy%U4a>pdaI;k2C z@rHI202e>zR-hK^wZc3x$x{?&<1ckRCdA#PS-}74*QcOxTk0^)ag9Z2Wi9VHH+D(r zg8t?RAR1}2jF)=D#zyHIqKO1O?%K6$+`aog&!0cz^Upu$U;Xd@igvrhuYUC#zWVxW zu3o-GYp%okrFAx6ZlRQ7duyAW-5ole4p**R;m(~q+`D(5Z@>ADUcbj+FmNYwyobJP zpJ6;6^WAq37?1OK3MP89h4rm5v|+f7G0xN0_a}JpRo0*?2i(x$Nz>%N<~kT`5Q_(I zg3R!IWDIk2b6mZ8mE+?9n=iI_^Xhfxy7TnkAfAnX3_2rlBA02V!jW;u|A@thifrg)lWQYb{?I}1#izM5rwy&gqT%*+N}mz|VnrvWszqe)X3`3=-Wk!W{% zLySS=bWVkV%Ab=8bt$2De8hkLPydPkjNABB+RfdA2M_p<|M5SOCYhI+m5VJY!S`@V zH@9F^P+hF^qQvsO@7>{l{^x(i+UgoxFJJQhd++g=fB6?IEiLfV|Nc`nmLGleL;mWo z{wWtPUF73m{hH;%a(8Zp{=p8%d;9eB7FAax+U04t7Pm>O6lo%0gm$|H)}pln<3NKX z)h<@5_e>6(X;GijI)=uW#JNHTI)g} z87KFoG)U>}=b(xjzd;hf)^SzR%L^V1`+PMT&=Z&Z&FV?#ma~AI*8wi-`^}$H{~pZ= znOXN_9lTb%!e<(y^|e5jSAkjK*=cpY3UECSUhukr@vDHlS6#DkobzBM&EH=&U#HbM z*^kEG&GW?8NrBqh3pSLzk`9~go1TNy=3?4uYF53o+JByF8snMy{;c{>0lAv_XkO-h{0K2d)@gBB;}7?PMEN6ipuc0$EJ(cosK&3m&z!IQhI zjlY}nF8t{g)LA={6W^8k^)naeS1{>m!s&H^&1oj`52w3$=66qTq+ie_Eb1;+PXs1n zdz|uO0)TU(jV_j4QIvf4vG<&JRXsBTxL#g(W%Q)P;#YA;_0F4T`49f_(_SH#^hh#r&f zIAJ4-QtY2Qp5Ig;gEAiQzX-g0;5Ig{k&ZJ0tG;EmylC>64P=Fg;B{!Q#%Fj99_GY8 zy5RfKXUxeW1-Z8L65(a)M%}fr!X_D3>H2h4n5)Q%s?MCC!c<8t=fl^k1wRDzhmM+Wg8 zAC)Mqaz`*VhG#Fg`AXnIqPfzUW3k&}P{GzH=jEgCSWXwHj`|$^?h7)dS^nPpE7gmw>BKF{rR=&?(?>X7G z2`d-g#Ex?ghlec97Zgc_5lh<5P+D=+J0=~Mq-BAW6?SA%qm<%kjEogmvn5tbi%B#( zP3c=`mqI=ovw&gW6m|T)AgcpO+d#j>HPcUW}6Tjik&C#m<-37$oF9`!^Z9F z7y=V|h`9E;WG%{XN?J+}j?V-_n{_qXA`(-i1-K@DPnv=>h@n7**41Lgdz^b&g0bYI z5%}1HmAH6uRK^`~_H|kDd?4VH_5y-f|8#@BgFc^pvBUi0GKa@Y6yrlaE1vRrV~3;O z2#b(r9V$9>=ev|;MXx`im1eZs9r9w#_-GGh#(+U%DV1^HN?>;%+b(XzHKdxg;!h;F zwX#+*{EYZ|to2^41bxEsY?%CLTbP=zqHK(nc-d<#y}kzpm8x}`IG?VGD(Ea8h=^+k z7Hdqr6{*UC`1SqZaF2(N9chaYhN{(biM_ej&! zw^z~Y^;lR~;G>Uz#Qkr+<*@ga&#zc-_Qtk)z%r6rL$?j`=yi<9=2cbIgy9&&{n1gv3eZIINwntL)oXOy9S#on z>Gh5rK+4u042v=Cc84@g85cRj!H{a4LupBq6r~l`7}7+uyt3?$f{$~j z>uE)jBq*&wE0R=`wK7C2#>JST$Y~`h?QTMvq!HMj z@N*tO>@1LPy1nNa_Y*d9g)XG$ypS`g&DI zXSHqCHOuHY54adw+EhYj?yv-KWrh%%p7?X2;a}awqCk|U)$XuzL6herbdvGTyYKSw(PMu0v%lx)@R+hJ z&{C0#>u>_?DH>p{_G`hJb#W>n(=5vEA99M zRBpE+FAKWT@!2a+844S*ikg|Yh7C#Z?m|M$ zH=@xb#$r>=>XkQGUBAY|-+f137q9u zQd#YRo#v*5Uk@7NqM-qfiA!$eYd~ zN5-&K8a9slB>RU*meEf$cJdw%KL2~Hg>*$@ZAJ3zzVpJ!MwnF>k*_*_$)lhCzl??> zY+3N=<1Mrn#)BS(F^sID+}dKkyTr>fWz;{y$_P2!VR`8>s??144rmSc$ZQX>1*xrg zb7_&=H?Gi4fl(hl%#mJ6TDHlO4!v>C^W$AcNdm1FCeiHVLvH1_Y0q86D2d)nAqhyB zMCZX|BJDZ444Z)dzp3>(=m8@2NsWo!Y+`DS$ZO}{a6Mfguo2CgC>?o3MB;IuW?i%D zvtkMH(gIyrJ3~f#o}2Wvm)MR#hc08UkNaAcU=36iiM2>op{?_vR(>55k0K~Q8<&Av zV^LC}l)-o~(*jyR^1{V?HWsWwCz_@CHz=h=cWHraZjI-UKY@?w5#oV1rIe3#Zex4JhHY0*r@`A)0_B^yl%9y1Be3!GDNi3gB1sbd z^iThkgTsCP_HX}&qr(HN5_YzC_{GnDPL-ED`1U)#{q|duG$qlRjmJ+J568TD^$qs- z_b|qwm108mnxHiy=>$-(IWn${QnPtx8fa@Cg%fedO2a)=%?+ZY4uYZtk4VV&K*J?7@;`S1rn zq$mn5U%ARBpMJ_=uSZc9eEIcP?CtL}9*sQM>OPo{$LKWS(bFev?d`-3J-0Rloxvyj z3gpqG$WuU>>FYek`LsQBo;qiZ@5H;|Z~w0n!(pD&GluR$o3}DenI=@CG0KvNMrlRA zDA?@v*~xP%U_MPz0$G|mz|A_~J2+$nRRX~!l0xE!Ux{#N4SD2)CcRl&tyZKRQPAyn z(H_uHT9YI?dg22ll7J{omM}`-a3Nz;G(*3^5eTU)OO}>bxcdh`W@UMW``_H-==n3& zFJ9sY|NMVJX~n0Xe8TSY4F<xA^ZFLd~^RBdi@^lj>|zX8Vs0kFYxZoo3!TV z*xcOY-uGbyV0n_IJQO}ECo!QP;T<4kl>A5;< zvuXOh(|9hv*ThIp!J?Y&JgxoHZJlo0t9a@JPBwMF^x>dpeS7lVlix_BUV5RH=b@HU z1>R|h`YeF6S#J#@#vg8e=NTB6s6!y?GYnXUv+vDdH4~ z$27zUx)PU(dd8)iRRqAI!3Z%ifS<2$dLvAMwN7bBBI@MS*13gwme($0^9`hIAv(jD z6sy;ejv{NNbk{Dz=9o)2-p6h{WqW5EWyTbxL8JneVAF(Z@gjv<;t|X zaG|1;MDVRMT2ttR0hU8t)_Ip5eoUb=HVC263$AVi*dk(X1N9LM~(iHWLaBm3GNX zTu2c`CCY&q7KhFc_c=_vRC$8PfJ>6GzPiHYYghT^lLx&2?lKoUEp#!*>cS<4FZv`q zV-~B7Jjsy6Fh@peg?HY3k7uKdQiN0wLC-U{cA0@mSXsS9Pd}tp9ni4_l@f-fp%hCc zkYf>lWL}HkyTQYR;^bDnMj9WF(>CbD$6gI`)>J4Am5QjYLGy^wHBaNKi83z!-hb8bW5~$dXBZ+`}!+hY(6I+4N0^Q zdr%pkJbA+L(J{MwJM{ZKmjF&DSZjH{vBAIlxBrffXU}}hWLt+tYOrh2EyA=z6I={5 z)jS0<$zQ=piOHw}Z%B|orynhgP8Wf%8(d>;Sa%h};fT%6E%x{KxOC|_F z`k39lJ=PBo+1%XZt=l(Qy0XrLhYz{1wnn8jFJ8Q$GdIWl`~sa$m%W4M>>V6}xR|{` ze)}d<93HZNc*NL;vXDp!H8W3H4)w26_vv-7=BDkmEip@`O&Xrl&uTeT145Inwqhv+ zo{k3m{KY2o3QAjHbK{a)S!lJ|EVNs+M4;o2fu~8vdb>l4VR>nh!Eoqhn?!v(%D@mZ zrzp_~omR%vqXX`bhg4QDqU%w#2BkDhOH1tT>@XPg8I8xREUz#e4%yn=Vl)_`lNO!1 z1;*o&z1>5`;{q$%g?iC2wlG$T(#uW}A4ViiS%2di?|<+i#uy$wf5x2;f5_EqSGo1U z_ZjB}xxm8u1@7Pb8gq2ag$ozB{iBaqdHZc-ZHoxx=GJ4|!3HX;&3%YiqoC@q+63n0M~p<@+CgfELB*^UrA= zAMs~@^-oE)=3sY+;h@jXsLxiSs5;JE78XP@M&vy4^g6Eo>oTp#G%9^|qQ5tK?Nn^bE;h=z@(%5F%GZA}&XVK*I zsPG4aadIu4sC)YRGYKvBbrNt4Z7}xaF6L>%?6ep(!H;4>Z8?QNPb2(U-#b&_Hk3vO zt_c(dT?*Q&`lwY?8S!%-TM)+C|1ieT@ArB7_$kkxZ}H^mGrqcakL9IBe)5NZ$okbc z*grVt{x{$8*=L_K&U3!~_B(Ffevh}_eg~Z-zFI?38g#cq)|q2__kerfJmPnse@R|c zY;5ha^`HL^5X!PbiJ~YgmKGPeb^9i7zHtp>;L9(+5db>Pur>uUtVyuS;KKVSXi|E;0(EJLrK`(~R7Nf>ic&Zl z6!bKGN8?j-+t@j{tz6)Y)^d5}D(E~5>w6PaY<|nB)`xUNDfPVdOKhwGu0qSX`p9B~lAR0w~fb+nj3?0@F)S;~E|~(?ZeKcck&} zgjlkZZ#T;}ukd%Hi?I#Og{qcOp1=J|0u#VGOp4kYI;W|_(mFSY-aRTR(Sxx zMvq^A7&c}zuEQDBnUE{2J9dxqV`o8mAl3~~Kx~M-V;x{A3XB+EnZj5@)jXDA-Pl`Q z_sali8@(Cbpc_)1aO3V>e(=HfdFSnS7##QbFZaKLEamM#{D|9k-e>v3I?sOg3ocfM zAOGZ!&`HX1Z@{L3TomKVptH>HLl{Jb+=G4{mq3WYW6nl;vW7`ldOyySz`pe{l7mbN zg022xsn_Xn)ciD~C0w% zxiSuzw8qfUnhWhYl7%(a-@Qx6j-kr2)+M`enRzYBDs*m0k4skjhNVG4OGC0WN3o-yMBqq)dddoL*Blyz*VhTzVQx}Jubfe z9*3JppbAEb;-FJtO~vwlf$CS>C|gKjNLJ>^E-Z4~?=x0y1~%i3w9D0Ol_l9>Q7Me+ zleao-o1A?u?2GdxO+@HchO9D3NQ5Ld@*KDre5Lp#8`ffdat`agA}vssE=)nCp|JkA zzVS0;YuPA1c3`lvEH2}5Wqn9prO_HBcd-G(ESjkIN)4b!!P@|y1Q6wq;06ltdW^V@ zBZZ(ulOG(jb03p`O8@{M07*naR6L*(sFYAv1=_&zLCN9nPF$zQMebHXzX9_3XP=UY z+gyqAB^VlE&GkooW6Vy^XCV)>#F+c_B0Rn6FwGOQ*00m}Z<_a-C!?&jiU#1%qJ-;-8Pw4jICu@mWUO`#*$jR07-{3!dyv>}o48bMMvkoK`aWd}7qeuMT|Nh^j6U}%$LW^ei>H;LBG$|%WeMdzx*w&ETzADfEo=j-3$@yy=fiqhPB?;`PL3MZ{MQT?ec76gQLL^-R_Vh z><(gD<7HGsQ<{Ylo%X%+VlJmWp9RqU zzME3h^}?hL871;7=pxiNidGQd<8+9?Hns?qfa8y#y}n+*l2**1T(VE~xgzK)8&SGZ0Y z+}Ov4@H%U?vbZ?sfTn@V6g+1IQ|8EU>=Y=3wh}s-W|V5QH6+H+vVyi2Z479y@LB7R=LXM_ z8mJ6LbZt!gQ4GQ&4omH6N6cCdV0^s9xUWmh#@YyeAIxm=L7brh6h{WFJx!uevs0TM z(r8Tp4Wc0*ygB@R6&AXhXH*l>XaaS%@m^DwHQtqISPIQ1)jh8Z*5hmacLU_m9CHu! zsT#JOU{BkqTN-8TiIR0orWWYud^u)gs)<1jSW#JaR!CmTMu3cuOm;_@|*!B+@Y6t<|?+uvvH;w4ru zU*>rDh@+gM3xEiZ9(?E+V>U1Q_zm3XW_gj(nJZ+t4E*Y`})OmL?lV zLU1h9NE9>ksvBzk9EHDY?>lMC{z>2=@`ANd-k#VV7N|TZr+^NcQLkj3799fs-#QFvWCuFh0-O-Vi&ufzyi>LN~P=`mF#TwI4%^Gf-#C+Gv;8B(-xuA zDruDkyPMl=?0iSoS!4UFLoOWcW5#3p>H%q0(anamy9wFK5_Vj&cG#y=m6*@((M^RS zUnIRS$MU#h=WvVp`6cFuE#62gF19l+c9%dplp1y@c%HzMeve!%YxDD5w2JxCk{Lgi z;$)S}z2iKyN*@c_RyNXMQ58`>m7@YF3yiYlRBkgs1gl)e4O0oWgsN~^i35EY4V6@s z7%BsTMpEtL7dvW-fT(r)s?nMXOXb(l5D%)d4xq&!aW)=QwO+Qjm10g5iGixLjE$uy zitTblp1Mu$%s}e1HHV|p(K}L%2L%<{xXr4{Hb7NHO(8BzvL)te zt6AC8dDv76_rkxX-7swvoY0-m06Iim?Df1T0QEo$zxeg9KSn8GcW;l;I7ewkq7w3A z%x{1DDPMk(aO?f|c)9tK2ag_dbac$d#xwFfCu_ALI6DhGoMq6>^zI?3l(jeF#2R%9 zxDwa&sYPwpxSSk|pbs=p7|gC?x?j_m1xx_BwWiRTL+d=h5ht^-mP1prJsgs0p(sj< z!k{f|CW`qiWz-)K!X>yEY0B@jd43Xi%BfQs-fX4(xb+@pJS0gJIslJeFBEZW*7L`Y z*?jbnR215y@FL=q4LQ%_N4q=R`}}t(C3I1Ikj#FuyqGrXfw}_xc?6`=~5qab<<^I47H%L$Z`&*rz=|2MGP)klYxCWl3%= zxrO@=AJJJ{=70L$_gGt5Wn7FnIyhv0WtEFp-{6bSKj+C8Uy!5;Z{51dh4o7e21DBO z3#?s&*2V^;8CiFZci+Fuksvw2RNy}KdA?w^-lBoX>#T)9ytx* znpO5yZ8@vWr-5d#1Dc+;@zH!X4IsTPFgI%)Ux{a&(!MjVhR#*r@HW<2pxJ5Fozd6d zt9`GyCO5$wjxevFb4!q!lXZoEA&YrkwVmnV*uiVo3J25U!o8xikeJ4hy z0DzOb-a^9zPA6c6R41|S(Qw4)Uwpy+Z|;+ibBdyL$$6}aNlA8hchEZVnNr=t*&mhM zf3$&C$_M>U!;oQ>r7{&EAxK_U-23J`?tS~6_r!Ecw84_-WP(VW>#jW^&>UnGz(xl+ z2x?q$$+Va+Gg|flpDz=Dg*4^ID;KG>2L_Zl1=$mD=#w$V?Y>u)a||jfbZ%K5CcHOn zv2c`==8AMqNG`X?S2B90A}fwqxiQbfbcwBgpYHw+**Jxyq^!my!!h&oDcLvQu(rKT zr-TdHGV4nh*cxx~ax|nG6)bNZ(%b4|x{EAbxyl2I^oYA61AwH{gu;rTuii;%{jO5zm$zj96K(UAx5+KErX+`EUY>z9$urib}f@^K` z2SY`?Wi-iy-q*w%=T12eN0GYfEMeZ|)-fnc1B3wQ|N!6^?? z(=^i>k}iC2ihrD**yJmj{LQyNz;n~fuRpDLGPYyvbre|f{-n=pXy-}gGH8?1s4;kf z7DY*(7hZn5)+vFhS83MZJ4?*e-kirN(d>rX)^)}C@%!)--Lef{u4R6+)j>g>9OF=d z9}hL*i|!WkFg{+Xx1J7yS4Wi9#?~1}W3Ph}Q+tNTxVX}!lKQOxq?yJI8`hiVI7!Gv zUH92G#wZY5%)aakIM0y?1$coP#%s|mfbpUW2qO9A9J+5 zjeX~B2MS~fqhX)TjpyX!Fci@W%0o?`H|!w1XtVpv*atk|PVd#fZbQV6Vv8)hUZs(FRl_{;mWd1()DU7Y3<7 zv_cZ0k`f~Zu@1mVltx)YSGnxPRx8L{>^+P?X)#6R5`l=I5`|<5Rvh>#l_j;BG|^~P zdr#M_=+`&xJflr%{l1~2!z#qj^g2mpOnk>!!(uCCt=mGEhQZ;0!Pu~AEKha9NDIqZ zi^U{CRTU`;m1wu78x#4!ShwM*L=(0-jB!A>74fl|y<8RX;WQloRK@|YdlDUTCRB^I226iz!oQDZIgE=FB_O-N=I&<6cEA&v|%_J@!;V@lv3z4c!4Vyhh0FY z2^Qh&ukU&Oayp=BMb>WD-<_ym8n#NKTcLdo12b+!MxC^=zBhE+kSwnmCv3w0!ZGx# z!JJw5Lp?K#oOzx6oRfbgZhZbH7F{VRL_DHg-%k&RN{^%#XbMsv;U%tfl&JMS3-zLpc_7C=|Tsxl{>zh;0t=l$M4bc|-ze;TND)_l*}SUS(xolmCKdT@MB)3L5m=A73A zUULo_I4_;d`p&H9!O>&pm_S^Q8Wl~nc^ZEUeAVnzGnC6|fT7kpzTezHoCQX|F2Mb& zE9hgPy-f!dw;@?4=(N_7BnerTk>_Kos*LZsHPqFUM8h?%$djFz^i;shLhsGlP|9ng z;0y+{b@>y;8WW*JP1Nn0Jf-GK{Z|Cc8pnodl$m(H@!ePLvYa5spiG_d&UAm|wC6MK zP3*>=EbKcXQ3=Jmqn}!w_eugN8jgEk8q z+>yc1Bs_SeG>J|S6^^3{?nqnk&Z=j^fnyrHGHvn6Wx}{L#yPxS_oOiFOy1{uOCd7CP;u?@0%BTNTe_Hf#<)0om4&=QR|4aTYFJPe zrArPWCXOqKH(JC{8h6Z8l%`CCk+zIA6dELf)ozzLF(gzhFSJmi*dL5}c{JoWH4WfQop{b|Br+BimWe54-7 zz)Dd@8W6D}vc>^9 zVLs!owFo*3l7)+7V6U&Y^^pk{R z5r!7*U`SH*NGn5PEvm5*EC`jS8(j&y3a1zx4OBj2ubTpkM$lNdK^u5byLC(<=m=gZ z=RpaGPfc!(B|&JbgqG4IN}Lz8@w6hwdx5&_$-4513-OuHqhf1OO8YewRH9JE&@q}CDzA!RK}qE@l#QtwU_GFeLKNrBUgSdRm}D-Nae$KfcJh= zC}puq5CwBfi)@qy&$qW3W+_!XZGO^&a{6DS!61+08dBQ>VXd&%#<+i$vyCo`3*#eF zCnT&@>*o4X^HRMb=zdIxq-p|@P5mOQf8y^)AakN0?)xE0W4L!#GSg=Lzt8n5Z>e*8 zc@bn!P&C@aBx%N6))mc;{A}&^E<7d-@?5w(91)faO6cqU3S)(Hj^m+K65^6F3wXf+?)R1EC;ViM~+z<>!gsJ)t>XV^B)4_oi#5f z>JXWjpA*h-);3r*8a^lLIaiX&#^25PHpZZ}Mr%D0QgIfIO@pc7-YcE(PPt;gLj4UJ zut|%deyuf0l28=Ii9Utj>+`%**DO?da*%?7*;uTn8$8wN>3U+u&xWBeU4B+Z;8;^o zI{aXqHl)*iaYt=zM6=JD{AOR83QAc1rYHUc5O<0&Zq_^fY{s585We9QF@s>OkQJ~r zRuqY4tSprZSdA-Z{ZU2>{^+5211}i!V5j~eLrT=xQjr9Gbo(sk}wKHqtG7}55{kuWgDy(c4W*~;ReRaa;z#2 zO~H}P$;%NpR#tfH@+#OdovxxP4f{t!Zd|a8D~&O}{&2+IK+zweOCiY>W>iv)a*A<5 zQIw=Y3bB+@VFfC&?s%So3g-f8 za9&>(0myKaWUP%r>5<6|8buJQ5)GChWNeVV2%?>VZ2j?jZ7nTPw3MQw6df;|OBB1~ zAw8dj!vdP7{HZx#No{Z$TSn@So7z(%BFKiwcg8wnnObQz`G{fPlBOwQN>t3M8@SSS zDoP~JyU~z|rEFA;-S7SzK7MSS&9|0?$}w|PL)1hncrPcc<8!5S-LDu5B7j}tiF;q! zP=%Y(fNe%u87Io|M#vl;ypN&^A!<@)+dA=2u;3c1kiXm5d#_-H}Oen3X z0^NlP4YD+R#o8F_)@76qAoIy}F-XORh>zclqB+zq&9>D*T3pAbDmT_8OSu2&5nIPS z^1Q$RrG>qt9>4wUbCRsX=F2S(4h}f(^_ZVuq&qjyuRi_+)tw{PnfK~#f}EiPL5jU~kWu9WwtF_dM6soZ9i67k+Jss@Xv=A$`{7MCsAls@~pR*}z? zI62`2gP|;33^MCa9!#jvo~{C|3104n9diF{B5SknU+|m>cf>Z6pZIo)uh+Xy2LL#P zO^Ax6XEkHIu@mNAi97LtU?ssQO{KJxC#DL_7Iip-lcV31V_sOx0zy_Q=s6Gnii%P! zeNh}M#aMZIRH|OfgDjDnUo2p?*B5N#D1P`MiVA;jLITL;J8mt?IG? zi1vDrrf{IA55a*yP2x(p^ojpwq6AgBBf=gA_1WS*fN$NJrM1g$ ze-Ytks_7&e84TTVc)xZ9`m$cf3*PQ0?18YpNOR+?HhALSGmOz@q&}H?)&xhVFY9%I zMkCJD%bWLOIXij3X@tq-W14a{e82hgv@1|v7{?Q~##EnXeG548?N@md#eRi*HF61r zQ!W9xZOF%9SBS*0D`80qpmnnz@aH`Iqm{)auC8As5yP$PSGlFyjD0+3NqjgOBiOQ} z)jMV-DOsG?=xz&JR>+{DJRBoB<>JabYjX?q6Q70tc+8?w*ue-}Rw&V4uM;0u#>uIh zH{a!7{ICCl8`rP%cYpVH>>nKP7k}{=9PID2@$4B3^YdK0c8$aReU6Tfc=+%EgFzpF z^-Gs|^yo3;d`x$4j$W_F^XJc;ej-AaWekTyMxzlI*VjqYl)b$@y4?;T1#e!z zL95kbb#;}Ml@-degi8=NB0lV|x8Q7yWpbRaGZ`4E*IoT4F-U~bDlOf-#HCbHjN$&C&#TUOYea?iRCognm8d_3$^of zB7jXz`kLCH9^F%xR2U|J}iwiEO!A#qwCXVr05 zyJx)<3GbBmjQ>{wg$OoDz`zCCe4v&%7NJN4lW0n9sgwhIi~_5CTqA|^z%#}hS%A-W ziE+k#S-ISuwldLZE(I#_>btd|l#APGf?{K143x?ke-z4PIy7DxkI13#uQM8bpS>rI zAtdmyQ2}mfvd*fu_*h>8zFpwF84?k6X(`8Js;pv!VLPc@Cf3GP2yFQ*=OK`}V{F$x z7m=zK1fdt~{iIQ~^(Kq?eMQaaC?63bXDN2XvT7FAVj4z`&qD(2=AQszjq3^VRC zJ{(Zl1g$jMx`v1-645A9x;p#+*n6`kIg%^i?|0lIGPCw76oA6k*qZ2#O>#KALsFxe zYuEH3ldjo(OX*Edl1!R=Z5GXNX1F)WCVK%IXrQq#wPsar5#fIH;O-HTSy=^ilh;gU znlP)Xtc(a>j~_qh?`NT7Etm?jE{Nhgkaat_3?@~9t2`NkHNi?mazQ$RaL4$_3CX%1i2k za;+ZJ1twSt>{kUt8-T)P3}?F?004mhe^&F1l}^q|-eEEr^J@Q)l}?X+SKu_nZ%o!! z=`f}t_s}LpD5A0#EQuQswpPTYQQSfzf~c6cQ@JEwSychZj+Q(rgosAVBVSu8PVWh| zKv4rVEf6d$a3m?_F}`ZegCp3PfEOo_i6`^HBEtZ=gcy(oN)@vXhstB$5^t`18tbgX zY9vsgIqvYa4imy)k>mQXDJi>CW8U1V8>n&qBr2!%p<+wzpFJ%N3ajD8_ACD3 zpZ}Ror-vYbS&GR303ZNKL_t&>9rdZoiqG!c<=&&mm^`B>Du%-Wcfa_8)wOlH3(Guy z{+xHe`&~A!y^YCoR09Yim1FSaIW9948mz>Vg}!Jw=rj^|L6+wb4-A}(fRhi_YN!uD zkkL3(G$&MbpBi|`dq>vEgEd~97GjbQgT(mZ3eIDl3v=X~@G5C+Rei9MPT+-tcfJ9* zqLS?6UfUKP>I0O>BO$u=#(ky{g)}X<2zKi9o)~G(d!>W0xVnPa@Pff&e4rx}t@KqL zs^K`NiSU!wRrpn8!hsRSYOy9Hp7Mb6hKh<(1ZN@$5ZCPV;j7keqY5=|i|n(FVbtJu znv16Ivn3{3z~MExj7lEnO4Ohc=$1Mhk|YO~Sa&@9jq5?AsUcbQryE{^Wv`kfA4#ii z609yo>b@9}x{smP(n^FwLz9x=gLDv0T>mCM(3*!d5F%KU1^fZ2Gioaaa^c9sjvtd9 zm6VldoGF!*jKwfC4GpFPjzr%9H;C4}Cd{LWb7^a^BfivXn-RexNsJ@+%C)Lu%awRZ zuIsHmt?qN&L(<=t4iIm)`reFJ`(87kjZ8l!JEw)hwER8~0I5HpRL3dTafMcDzS(>q z_oO+>Wc$0itay926;cz=NllEve;XuhR@#YeeiI9&<5g+Bfs1KU<*(V<++crszX-^X6Pz{{OIymTq!Dl$f}m=IpeJJyUNTj~UwCRlM;FF{7DKum=# zE4sddO6g9N<&MEFg!ZPxhIX?ASXk&W8V>o%PkzF`{L8;^{rYv5msbF#DoY+dd`OmM ztgNi^)?06}wzkH~$_fjMi#&Vwj88xNl%gzo`|YvFIDiahW2p;Ds4N^q>rHg{c6;aH2JQRbwEUCaomcj0pyo-}9|Q2d?jU3yf0?~^8sDj(t)-LK zU7A?X*Sgxg_>Tp5=au)mGH1tP3TK-t_}Dk|_tCG>#&o%8wcWPPJjq+&b+r2?bU2qb z+U<)R<2bz#W!IC}Ro1S&-I&*%w4_$Z%my~i$lA;YXn}Tc+-hM~K}?@!>pyMs1R6zn zrn9Mxs;Z9NYkxkqzBUtMnjp3Hx9(&7d)&A_-q)tbrx8I4v|ivD;@Nc*o{syhuJu@3 z%~{Gpvy`PXoXlBkAvd*)qL-MU!z)xCqakio0uH@uK<6b2CN@~8=QY&79))**8NyejQ+hUi5oY;qJ#o2X#LA`$^oBly}X43Z`^5}|J# zFT2q3iq$N*qMM+YR?eZr2}+3?i{>5jJZF7%mF1;HZrr-X`uYa{`p-W>R5`nOhRav4 z(mxvU`Ry;jIj&yZ;^NjtjLA8w3U1&3k|$3dlia5jU&pzs0S{V=5QMM+>XSbtPy~U~ zDO6!Ht%eNK0eIFr?x=~1ND@@bELcf)5S|w$kM{Rj%`F=nODrsOIUG!Qb#TbA3>Kfs zh0z#8$L1IxEK#!*s|>-KkQ6~o04YjSVRA!PY#3KSoI}SRnS`0DLmspQG-GUt5hvh{ zhW=VXa*MHo%`B2xu)%fDBIHT=6vCLAAov*tu@;~Cz|x$@r0+?V2Vsp{4C+Ea<-)_W z;SmSo$Zf`^&AGg=z(%jjQf67QIb)w=UL3Hrx`rG*80QB5FSq9F;Og@E%e#9y_-g21Z6GKMaeg-(Z&Do@8p3@V2cp$?x)vZf8y zs6-xSOdV>iz0V{|{e&2VKN^Qvl5T~h)KD5GR%nFgpu8j;JDPC?^H`MS;!oz=OQUW|sb70K-9?o3L+=yyu{Qh{sZRk8>4@HUOoEjzT^d zGtv-`*<*073Lu}R+BL-I%yZNT&NpBli2&9t0Mwvq&00<4p9Jr)p|K)q zZmq>+mhs^LaV{7dR}S@%dCP)YI?mO6w2m(t+b40-yJA_fLbvqTc%bRZNW{Jx-~QX=~DET-whi`7!W}wE4H!U^=LXa+Ft$ccC1gJV_Me!=9avVUlZg zQr4RQB@#oNq*>Ld2gQQ|O?)qS0H`!HKwJxL`)pOI*LUKxAyke{v)!&KCcBGdT#!YL5JN-+TD*Jr zZtyUO?%%5`tGKd6Rk?KW68+^q!{Ly%wKc9^yT)V^EG4U|q9_Umg8>g7JmA@rC)~RA z4p*;Tc*a$)NdTU(nP4UTyBu4_ zw?UOBj~}zYzt4^9R{`PGt8E5@0iS*PDUY8#;px+-RIZ7t4bdW84W|1nYyeYLN(!}5&wq<5S~S$up-tQC?K09BC(-&c*IlQAQ72#~C-I@v z>Nt%T%`11_bFZJPZ+88ufWT8~n4#Nn9oOm`nc+mHKIdwW?@9RA?Edj4WzJrwj#K27 zC(iAZY=4~YURVBU0J5s$oU4-vB|!6a^z=H~eiI%sT}HD5)>2jri<_;kw&-l>ELnTv zPg`gwS~qGl*mH)8WN*}ncFooH-#-SZa&lXy25qL3I=>-v-<`cqIz^a$=A`@6f=Ek{ zO+w4mNkT6{LC;mlDk1YW zWMpqyznUQRSf`O1=@|-Jg-{d>r{WjO%ew#ec z`Sovp&D%Gw@!|JA;M|4F+`0Fd&mTSE%=!x7|H~h6uP8hwOFL!OdaEtSBn!?!pS}i!YMpYL=s`Ub@5#CuBUN_1=h1#2*(Z#(SjyZ+ ztI38RnBbQN6p6Y5VBrAQsD?iNvK}%Yv!$oFeqKWxW1md|DNF!2RbwK6fObZZGi1v& zCIm$_#>UutQ6Sc&M8>vijBzQg^-3r|&0`YEQ|!+aoavzoetIt(Ri{3u)>LVm>(l1t{08q zRd5{$hCdP$Ml=ei*N~th#fr0;!Q^>}jhBERV*v@z#Tahhdwl7l^_+y{SShiTv_uRs zs126lVngDEqtTfDa0~+Hx?L_v&Wbl=WrfWxyJf*yRVv9*EUpa6w;GG_NLxn2|Ek!S zn9LFQiyEjjTIj~)EyyxZvCah| zO0K|4I(%sKLR&;uiK!38g0-Uw?y3TvET@XQI#qU&NDlqvx|i2=klL_PO`an#^?y=lk2qLt)=IQ9vk^P2V-ZI3W% zpQ-6C3Gjhx!yIdrH`3&!4-(dzwT5Ca!j)A}U?Uw(4vttWgl{jM!#dHc|cQa>$uJ|N!4M$ zOMR%VLQETQiU+nD>Y25-sCVB)TXF1b{uJro7+b?6ZE#gIhT_m>E@RUhS+ap0nz7JG zd*bVu(Gtx^O>OoUdGiyKSNj8-vlObS) zY+->?3}u#cu-s#(WAS2Z`9{@Y%Z#Bl^k43f4@YDOnYAIls~Ehs9A$==3zng^s2G+C z$Ax9XxCfdE_|`#G1tCjgw;++=xanLBsxfcIRza)ry6F0#C|%-ZTYXEx7Ztz|qO^Zo}PFgQBm z!Gj09_wIYFuC8K?p)3jx`v;7NM{I7av$nRva5&`3<;#?1Nx$Fc>eZ{z<9wI9_oXXW}ebqG;4Bt-saKANx;ZSKu}wgkoMYc^Q;z4%r3~L zUXES=EkLxd16uvJ2l9@$XO@@2$#Zg&h2WdEd3N6Cjm=3kbJ}yKmDj$swQEcrN+&^~ zU0$=1$5)Q??UTT~*L^O6$F?(nT!26;#n6xOkysGP26hDFo|0W;DPV z;j3QBFrVUU%~(o%F~=bBsXM7}8lS7jaeC88O0h_w^8}lw*|0z+hSGbs_qORSc3It6 zV=^fiPR5LeN35@}v9!937hzNuyxQC2&b@oQ|6a~pn`fw0D5{DOzQ1;eYpt7xY9uvY zsjZW1vXppko5(c|ZhEJtpxEd1OkGa2;HW}+AjC>5qot6Pe$}S`NhqhyJ0JqBiAW%U z2LYs33Yu6(q0NaRQ(tV&D_Zs@QSncGjcwC7pXo^-Es|n+>g!LT@vcco71~wR0{Rqd z(t410wa%ST!+mD_yE=Q(@rI*-2k zik+QTT)cRJUT=X(F=2GnXMJ@IlVyDQ@F7oMyr`2$gr3%Lc@0P#k$4#3>u?Ojb|?45 zFx=6-NPRv2q8ePd@*IsxdJ(Wmc4({LLLm0ti878wBYwMjQ27|&CU$;fV~w}pzD~K+B`YUfSz6((Gn?q* z0y^k(kXZ^521OMPIh;eP3MmUHOD5wnW#xk_w>UO8HhBA;cewxM1NQg!C|!jYLzZ_D zH4xnn)kmvm6TAdWOkC)FD+8oRZPBSfW}_vsA%Nxaz|UGkW<&fniD3X7!R1}!l1gDq zz@V-|s}fP@<~dFRsP9u?&IoRKs#?jc_urJ{D7vT%5Z-LZ)~Zgmi1OxVLM}6rSYE7^_*sp_Y1&xGI24lB{LJ zTJcVseNCv9bRhxO9%2&9`amu1M@i}TAv{8z+^LqI+w!8+z;^(0QV5YCn{}^#wM^xoy2GgL1{%!P44EA zxa$4aMQc}L@}o44A}O?lsf#q8um+;6R?FH+UeG4oO42cytGtJ>HPEu;sD-*lewvay z>)$c!bwlQ@xtyw|k*(T-f>O&bVOSd3qb=;Kwm`x}TlM{Etzb$wnE_}U9 zeY%d*>Lm#~NM(l}w5=^$kH&bgrqzaE%d2%P|DeV+@@dj3w5})s{}oVJixU&?Nz|dj zA?Y5Bagn8^C03VLI8><0lI6ukHda>|9vyLbbj0)L zFUUI`%Ccl*V}o};e2>F^pZ)!P@;v9uFYn`A#p>z`S(ah5jJ(@LOoj(uZNK8)-7omw zw?E_uKl~w8Rq^oQL$-IegWRGTMLKPCI)$F;pk)XtfyBt5q74CyOcVYbnrVMG3!2OY zTGE0f>HdKx+Tn+^1z4O&eQhvCn?qX?PvcJqCZ4utzSdQ5U$0wR^VjM;>$cf8y$)17 z?cQmJ9<$HC35a&ubsCuVrbcDykh86PiOgw#yDRZtLk3M3*C|O=!q$n(t4qW@;U(oF>a_4W4c=_sOdr`!hO8-RVL`(}z)_P)8J+)0v z5(HzvlJYI_6kp1swn|ns`E7TRgxDs1Td+^g z(!{a0H(;%|z|q9>tSI?n_XTvjr0GW_ScXQ$KiEGm4t6a^@Im*|U|lJhL2a~%&IHM9 z9b-&G5G&LgoOe{dir}%KKPh>#zfbOk8w)F(HyH~PM|Lp892RW#m2a6129>3OSkP@q0ovufd>sGtn@+hB)!#W|%aeGN2~-l3C< zOP9}c`NAsJ1=n7*!c8Vf;qYoW7)^L^G~}7J9OW4kUs0A5#z%)NRulf?_dkU3x5!jk zP$hScwRdjt?Zpn`yn~7`sT{^DnS!VT7k-Me4B-yE$Ess}X$b>dyL^eIrDeP-rH}ig z6I8>#W;BFOm=t4%#h9Wfg9W|>-tR;xB}CqE-s4Ljz@gEYsw}9A0+j%Ux(KRz?}ODR z;XqC;crQ4oOv(!No(#o`XLD_X3!9rfdHReOJKKPen;ay7f6jT-g2W)~5)^%{R$t3VZEA?IqG3WVZ$IBTt+{~9aXJK3o==(7T zmE$nC?5Sr&0B!0W&85Shlr$^0C+!PS_)1}0Bpv`&(ZW4&0}bsqAhGYJo1xa=MRAmr z>cG^;f(ah)goM`@*= zyPEu3ou5`u+G^%JU?SDkmUoZ8+mzYJ*Nya3|Ega|`*i!@bhf=rH}qKB!kb$^vzW=T zW*^gCPP%{EGpT*Y^sbpU)pmHIIMuj)H{+1NhGu+&b|d02>gqC-7_+|~0~2Ly4S52h z{B4bGBw{szpiKbHvlwUIO8`yPNQ}*GNzzd2e>%Fbxn*g25d_Ag5!M8LqJ^h_Z~zF4 zON;F7?_rE^_UvYup322|_>Qgf=drzR=!pnLQLwbM#Nxstd%L^jdCtbh27{v^d1ryG ztqU9+?6a`Yj8f57440fWIIhy4S3y-u+5%q-6N+M24?$KpCR0+WLQc5yLa zobiA&Jvq;!Z{FuOwbW{W32ICBWcgYH0Mb00_~_gfJ5@&u0N47PV{Lm=z5gT&y>9|y z&Mi}>@6Q6k?a$xT+Hn@3I&CeVR>x_;8D^<4F^3r5 zgh`!7>^8W1`J` z_}F~WXt^SAe6$sn5F0cl*GROQtlfxlN9&k;QOPnfLv#Qu8>Pqyt%M?xo?A3?;o>FE zoIM*{UPXBC{rAbKSXx`dd01IlVQcF=nGxRq-~&eEF`b28h%=}jV}f2kuQw-M^VZY6 zQh~gO>r?-7P?f)XD>6`0&oxQbT?`*c@Vu`5et8*4my z{DjGPOl5`M>N0kr!(=jI?ff}bS64YaJY*%$=uz;vgd55YGmJTs9 zuBwt{86t+NEGVlgu4j#h6zw_HdQ4hpf6~@V;v8=%H-2!7+Yj$>cH;uOU+pp8Hgp#kSUkJL%JKrk zrw5E59ndYn8iV()Q9Qh$Wu%`hf)vKFd2WM^%@wp%q(4N;A-$yqbW&1|g+7Zc>=vw7 zo>4brf@Q23#b;mgUmiX|mKMmfjE=EfSX$!N`7OSB^^$v|BPLl7;{vEAcshAjyPozI zdSsnW1eh~&1FIg^I*aJ;0Ryj?ETeP|k0CP|dEQ}RsTV>asN!@ntPKrTz9tJXCb1UH zYz#pVlrZZ(GATnWXBT|7Q&w?}7M&^lKwDT7lc6TiL8glF70cZ&Ik3IA$Ed7ATtiJU zY<*l>!lWuGONVn2;7lPCtWa4+)Um3D>kDf1DiP%zYiu^)@7y(*RWZ)^4sQ#II#AMjr37K*71o=ehF!EheKe4?elgU~jF62k(9s*|zU>9IjZ4--P|sHeNQNP$B|$*rY28Y2@=$9AY( zh3RHg;+a$d0a73MK?+0BSWrfb$|wT_Ywp-&ANLAta1!Ji?_FX9VH{!{Qi)mMorWWq zGJ?7Q>a>nsCDufFI>Ezc3065hD1Z`<%Qg^d2y5Vo9B4?_!-(0vbCJ$ z^&WV`V0VNsLtS2im3BDX^khq27S?-=MnmwP@p!^QZ;|3~j2o9&I=BeLio#?~Ie7bc z$e`ob*HuJc_xx%1zrND@&FlJlWt{tEjBa|0F7l%zVs;c7XXu$L5FSv4LhbwPgVtHi+Yb|Fs&+yKB@3OqS%;Di7TU%SK zt*x@N{fgD~H9RmF4A|Y>!K-V=P+D?b?0Hea*LKXyYW~ptg=aeZ_f? zV58Quq_oPIsVtSNO&Lj#J8tpC@v8-X{3))}0NSqwZk@1n=IQ;%jG|6hkF(EyowmJc z`@c>zxH>t^z=H=?zQb~$Gc&k738!=RKHUzp|9^pGTx|_;8fZvyz>~e zlj3p5Voo=no8OL;uG94WM!i06VSO5{y~#Ceu$O8&{^|AQwAVx%;67d$FYIc@x> z-bs6aM(|FgwShFxhYcixH<~Obg*q#4YYrNbA~htPm^UE8^qgs{ENx;+#s6z@B>}j! z!JyXZAZhbzwJ42qe7HkAC+F zdG7%SgAv2gn4|sypWXR_OP4OOx8LW%qo@4(H@~H-s&6D@)_tA_V$Ra_DQhpqB1@v7FI*B%OvaOR-s#m6>V?0pd06kER|s zgT^_{6`l`Ata{~QZ;`cyjMc0ItE>EO=Ot##|+uq~mpS_E$PuSR8XHqQi^tX4&cS~}yK?aEHC&kl61fPKy zM;C|S*;rYkV>2in#z80RpcUX8D^9qzu*Q{2@L88}QNcooyZuA{?d4+*7xRz+NIcov zI^W6+ukPOA?#>RA?h3{SfTwU{6o=Pf&6imV60EyA*3t#uSzP5m|L8B-`}hysbp=(Y z6XFYt4Vk>HrPHysi+^%G%3~((kd!W1)+ei9FM?PbEPuUXy<%hH9Wg>Dw^(a~>#GT1 zTb9`fsza9LWSJ$;GmKZPcXXCkSzTU6`bP|kG0tW&ca);(YuSk|5 znc#A8R>@SkZWg&NJytdfHZNXe(6ImtLG`{=HUNZ~2H-5qES2-c^)TRmjjCCa98ZYs|iXSl!DN2?N7R`e|l z@{AFdfq|YKva1iG+*$s}5(H~XNN5Pl%XVgu8`iY1R$wrNa6gC(N1 zfo77Wn)j}@pcDymh8i3H51|o=ahjWbyF>0OR=v{8l-^=aZY;(LgJHq>Y>~=kOgwBX zEU}hl^in4C#P1_pwO(*VNq3=3Wrbfod(Pw1HD+Y|2TM?B$d`~Ws=|BErAwE1>#etV{`>{co;~N) zs~xUhy~@VM22Y+o!Fy$8d6ie&J3M*(gqt^Sa`x;wZrys9o40PUySvA4fA>3{J%1jP z-zC}B06vDK07O~dTBjThqx=*o!>Ga6Mk~#{&tCJbr>3ZL~ z(=TYP)Ycm1DLmnu7)2c`;|<}L8Yh@~_YLYy`t1oIa{BH~EGxgxHE%9Y15*DK*PB>^ zpZ5J}0NdO$W@Bfj@IOiClv&!KmigOzWTULM>uz)qsedN~rFoy$6ZLCTZA}V-;p<)} z8|@iZR#qs>lAAnFi1~8L)7GGP) z*RgU0g6r`~Am|hm{Cd~dS!5?8<eSQ5i>fC1>l(dG?>} zGkQ6~x&)Pm*mqt$&MKYF1r|0}IoR80(jQ)3@56MGPp(IyeQl~I6e z2S05W76F?2&?aqat$u`$sZ)j~#_epI8~&w8j7OwkE$e5`qb6rEE?D2#;KId=9Q6Bq z^694>9Q3(-=@NqH+_^1^!g2TBmvnjyT)TdQ{^4N+OA?@F@Lm~@$2@=WoXKQDa@S6+ zs>+h5&z^8JIAnKkhsK>T9&n@~6(L#K)PQeNo|8Mdw%2j8K8_&QtfZ#eiig|qM@{XH z6(=AvC?^sU(?kHp*H%8!6*Q1e%Q-w{8jY)3!+1Ayro;?KO}tufYLz4a(gDDUMv<|7 zLHVkI*d&0|pt+f%#gyzN{I(ISH4)&AU}+rBkoi-@0*&&wu(0N$M3RMEU|cQiBGZ-Jt}g|BFkzPg&>F*d=+}tt19-l_c+Qd%j(GY`&j2N zxh1Q>`i$|YVtYJcKNCvLg15jpXWLiUUPjjjD@p4;J@MpHlKGM@ilYi)Q$(?%xXNLC zNd8omAyTgeyoOi`RZ+0u4x#@txY)ZMWO+;i6V~}?qB!-zA|@d5FyczuZO5NX|7T#V6~q4b=mSl!^vjT`LT`;y=8yrAs$aIt+R z#v@dPEDymOya(rnl&P#DxH@M=K_Oi(U%Jlo`(N@&<`^t5Q`w9x>yQhm7G#;BlLaL` zB+v4^+}=jAyaCbUnr+xwq+cUUI+mS0B_NkXQ}$XHSG}BOGRy0Z=*^x{8s>S=5M6`X=F@5h1Ujr=IiOGb}GHal5FHs>0`ucY^I^ zoV#_I-kBvn`@_e)^Mh~my#JWtmSJ#qLVx*?g)3_uTygkw1)T*?**U`a*j|mG6P}4J z+3yV~ItNV7^{__plHsndv47uTGKEM_A!(n%$g~Xi@7LG6=IL&y11+=4e%l)+{Ph|w zDG>zEl7Y9n3v6XwusL5H?xR-NT3p4I6@#*%ltw~Ps1p=ISSy(ntmKwSRZwP1DT?V> zHm+u4tUMeK1hVmU9jY=C$WdtVT})`@RVv`KH#{ zuLBnU7%=pY0aqIu3&|?OyEK61JRG4#W7?jlcy9{GlzH#9LH5}yX6d@ohr{KAu13<= z+U6$n=0T5rpZaW!VQFcZ;c&=gQcPw0Za+Ia2J^71(=de7Fu++{?Rcx_=+pJP$NO{= zZqROns*(l8D^2`y&@K(oZ0F{gr?x9;r+J@Oc^i?KHzczI+ODgysyY7r>#oV z#@m<&GzJ4->~Qh@%ebvBopZ|!9~%xH7u>vgliuPYlowqDs*z(jDG*X-YYkxC%;q)AG~RR#oVHP%ME|F&5-0PF+4_#P zo8xpZ?N1_8tos-ntoe`Bg|)3gk;aNLmD9*i4X|3RM`hSc0D(-+8&fk(4L&Lwtc^TTstBNi0$zM;>-GWkOrUsu zP(%O#q?uEtwc*kHFB04*J<$R~!l6W(^BOqNhM(6kxyIB#A=NOo<*)Je;VvS$n86(+ zSe&-#1P#v8;dU5n--2j;tlP%evSDn(QyKxX$_m2^>)5R&9_>8k(z_R#e7V4II3Zi# zVBL;EhUhur#{c*M7rGa@_t8CG-G4#3J>h@*U;a<7zJH5JZrOEX4n6FYC3b6*rOk`< zw-4FQl%)kjjtz-sh%t5qBqj<9dXi_T8J|^So<4g<*SnC+$z=GUN1Y?jbDUU4U1e+) zwJrd2>Zo!U$3z}ogsxM%r9;XwswJup^<|y6N}WT!4A(k_=KiR4J+|@`NnLa}52GDD$Zcz}foB8bAK=kFcFCKl#_6 zGASl}=fe+q`}z%3J+F4RdHCodZ{N7V`q~Dsws-jW(?9Ul(}%2VuG8sccq3G0#jt;b zt13FH3#_cJvi$N z4>2{9ieB};cJWvBF}#P?EZwIV-deK_YP)$6R2>4r`V%nT(Nj-XJ>tO`Lt!$C%!H5& zUU3S$lZsbg!Y4k+UnQ|}rR<{~M{>9E_VvX*zbvarOK z?XWi-qQe0ztIK@v{CQSv#*6LkkN_%rbSM(SFA(-=BOtEe+?izxGbX#RPF0qy-#X9Y z_b#wAdd9-qDucVP@UKd`lRR)}_4M9e=lb7#$o*g4X82-4XCY(rtqm5huHx|=-Q8u` zh6A&rDk*$HZgX<4kcHU1VKAuWAVDzR^YZCaKKj|uxqtT_Pai+!@#80WU-9`Te_;FN z3!XoHLa&qaPrHA|7oXn&^%Ua~Kl`_T<=4Ob1xE)5;2ok4gHVn~xXFalRXl(4n4kU2 zzjAokXJ=;{D~8?={+#U>FZkll9rpM3IJ35jRu#Ya#V`2evrl;W@+G6;D8@c+<($wc zwgguMM8I}43i094s3kfktNFla?5VUfUtgZ})N28cQBDkER&c9t(^pe&)0WpXpiuy3 z@x}zKdw&cU__|=)>wv*o-RL+l+k~H*YG*B5GbNk1#O!vIr>Qoy+jg2^R6Bvoyz4Z} zMhNS=?FK%3orH;;WW;sSb8pJWzv=VsHnjU3KAt=_bAj;$RBHMs^=-CYE#5KpZu~Ga zB(;7zRd2ZCL+pz-@;B$I6_KfO(%+4>XR`kt?&N<83OY7|UZ5GzL zjE_1Dx5oh#5`&fCMs8PfHr~0wL*1co`t+`>QjT{RE>t{z_61j0FCjfc;R6N zflRP~NNYJ-CsFTl)51YitV3}9q{sm3QGGLW^m(VK^G-ZZV*c&xqz%1&B_O9p5;*R{ z-fqva`lrf=BnZI?EF^(UGku)5EbF#P<4z4)yUow`sRqvKs-%8Dxw|#OOKeND%=D`4 z_mAiw4(WAsmR48UJLvQBWdJh|j|P15>1Q}sv9z?zjqBGrJUZgwpwHpq5rQ%rkNMG$ zen_X=BVs3f{i2KuV8+7J;l08%kD zMcYa_!DJer6%7IUm0-E$z31K^Zj;^XaIn|s-q8X_4_@M2$^D=F0a^4cEDJ}IF%uc0 zrsD9RPg$1WEqjl4S=#JyI2^%tNp>ha{^=byubyM=T8I4?ujmXMh7Q5y*ff(v2+@(? zUkH|O1EBIT2AQ=Oo3XyJ&ieW)+q=8$9~@w_oYnQ<=5p8{;VTEuv$3$mh4l?q1|zy0 za(N|Vt*02R2ytLT$PYATT-Cr=f?GOtFo>3{0awV_@=E3=Y^^Nud{8pVJl-fG4idPm z!MZen%PCP_OqSqWTuU*bn|Cz@YvdIvKCr4rpefFv32_Wn6Oai=h$V}bWHCFg^@$29 zLd>{8C58Yrh6EJ`=M-?!ide!r^Z`(^&S5-c&eL^ZoyYhRgG04IBIq0D%%a9a=20K{ zm~RpUVn~UCVvfB1_Zvc9&)(()3h z=jiZ=&CN}A_xAYS5B{86AH2)|{m*~TXvxx9Uu3U5Vsdau@!5#1f_MIh@59CtxBu7w zoh`c-tYm@8II@)v7ruLwQO|Sn!bN`iv!C$258vg%gFC!)^#%t|_xb$a?y$l-B0=G< zL!D@F&o*)XDj`~0M}A-|d9O!hGIsa-bUIyr^y42hnoI&%Yz!M`HaK^23&F6nd%&5^ zb0C&8XV0>_y2@`p{+R2xZiTGM-t%&IkAL{5pR|~%QdK2iK70fS<8eW@ut-J9%q=yq za2mLS#sdqFsja%xUZQE=)X>ocIMgy^1Zv9&V5os36$}|B6Vw`vSQHb&C)lu@jVfaU zSgEs{Cw?ggCPtV5g*Em57_1>p&D*_);j1H%ip4VHH-GnQDnDdtOXwa#KFN9c<#Tl5 zEvk(UkG^`q^;@@iZ}UBV^RK_-XuHHaP$^kk>u_8x7P#e+DT;KYCI>^w65I>Uksb-6dp0#1kw-mM{j|Z8n-nr_J>41!yV@Fwr z);6fk`x@uDE=i(ojZo`T^`4f%neeMg1{^xi(O`hJ!D3sDAST4u z%_PKnR`q0=GF~!N@1LP6&$55_G3X%-J%et^;zpOEQ{r@twO~w!t2~{J4qWasxHF>P zJL3BF8(jPAZ*hD3J~~nsw^rcknA|A1k{-&Mb68_3J(Lpn>Lm9_1p9nASbO-p-|-c} zKqn)x?gzj5Esw0FEGu4qcAIXe%VazO_52@Seo2WkniOP{NeBg;rd|Z_EFhG7yL|fd zpQEZ|d4@58wbA>**pQ4c z&yg&{QPtjJA;xivH6Hh_7EPpb=hDUu?Ht1eYJxrXMqOsTQ0vbPX*EE{+(PE+E3-i6 zNk&WaboXO5XcFw(GFz+88NHgLGwkmDY z?n!;i@Jg+dHuW59{A{5~a7fI*y_lh4E-Hi|@w4?$2@9#txow==j+3s}oj}Zc_O#7@ z9zzd>97{rb~gPL)wvFe)Y-RTHx19$Rl+4`NY6001BWNklIjPVrJnD;;YfQQ8$4(`3gl_BP4kgm0+ zd#zI%2ca`rXU#4Qjn5nh+UE9_5@~_g#S=GEyU^=ubDo@OP|{ZDQ$O!6ih4@Rx3#o5;|zbU{vw@KYYs0?mmOT zfbE?fJj%GN@KwbxKl&}7eSU|d;V`r?S{d!__j&Z>8RPQje17{2_74uC)qpfvvFn-} z`XbVTj~k-aeQXqvx#OSY%Oru#Jko?#5@tDe5;}#XAj=GGw#tEejB3$P>g9k)6B=T6 zKC~J5pu{(wBb_qV?bGRbOO-c$m#LX+9Luy;V!X}>Ky6KOraz&5Nx0SN;Y3^Go7S+_ za!jy9RBGE}g%q~Hiedbsk1?=h3=cp06e%57FJ9#4=4G7k^VX${eD!#jpZ(%vUR2xU zzC&g*uDo@DD_1u;oIK-u*WTqP5B3=sWAfdK6^{7s%~i&iSNXqw`H-@O!I`Re{d{`PPGo~;XKxp8@m zcW&O~+QqjR{q_?U`_Hip6=iOa|ChZt|FtB??)*L^W4X)Qx6GHBm04A})V_69b@ig| zk~D(4N1DYlAj?15@IT~VY(N^!00u@F7zPZF;TcKO8g1R`ZdG+HU0KyxSxfHQ%eOCg zzY!$;10&)_#EpC3%a;6`31q(aB1i^#czAfum*hDn2CGR=-8z4S?nJ!vuj<++vG@p{ zYSj47*%j8*fUtL;H=kc;^V&lWhC{5931H#oVd@7}CS&RXy2KgSE|ZC36M!*4ubpj} zT%C4z)Y00}A_xK$E}Hc)iXc&<13yUI54a#PzJ|bBgo*L(kQp$Ys#aXm*3rKv&c}aA1DYd&cnee?tV1z`f#Q$Q#eD3vX*xeK6y&gaP=}&p()mJ!o z?ks1|p5gXzkKwGPZ#`N@YG8S^rIbO6=Sq>JwOWLq@$FMYa-{;Ukz~X{CU3h&Nm5spZ9<9K4B2>!+-um zy4@~6`N@wtY8~bPok-*`pY)*wJSS2;50? zleoYT!aYyX>a4ExM59fvmAkben-@v6a*gv%D7_?LlO?;}ZG{BmA|?~XSQ|4NH0a#k z;q3YXmzv8g7)`C~GrZrXb=czIY6so!bK&`Un)-m3FE3Jm?hNm}f0a{e!2l-ehG+hiihnR`FljCLpZ z58{a4(Ga1H<0((l>2=uZcAPGqoI0`z|D}g$q;UQ+ZQ_Vv?J0Qem2dF%y^rbEV-^}A z3oAhXh(-(rCjfq1|Ja0Lg9Wsan3oA=>Ht)LtSXK}Q zH7>57=A2NNz;`}fu^3}8+WGgC!u3hYBsg3gx$y@E!@lz|Z6ziG@SHv$#b_akt;KW) zF1Q>XCQOblSg=`eQO6x70Z<(s6tU67`-kYg1HvR4vEDnvTEn6>%v%?POOpbbj)v(b zs*?j2&uW$;i;6Ve?vVaa69>38CsEo2>B;sSlTxA17e}6vQIq{Q0z+-Oxo5K?MZGg; z%apz?va2uoRI-mIfkx$0#*XS86(+xNly7D^6{=7BRG}Z2O`XD@c9j2ki>jDT3dEM* zP0LrV|4Fcte7-7qa+2>?*;v1Bnp8&>*q5FrKWRTJlSo!$F>QY2vZwY>VpnA?&D&&2 zzEp8ZtdKev#3>hFX zA2MY)Tl_Pi$0ow@myq8HhPv$E3w7 z^A$c?Eh^hcYGgoe44kx)LXJSF+#D_UDJ`Ar)|gDR`RhKJG_>p=9CC1Ylr%LNYXs5@ z=no?L(IJ8)$~x|ZReun1?fMtUFTbMO>k(@`qJucW_@uH}WQ`qxZY3F3em*8gGu&fD zlaWkhEMHk(ruF5&kPv#zv?T+T#S=sN3`bvM7+mvp~WjpXW-Yaymf|tv*D?AgU zTLq&zL1HK;*Lz99P%__%X_7(`NXgkVtDJvoox`IRUwwHKI~cGKNQgCO8g<%!gE00O zM($F*L@+-$!#7^P#Pu&8aek%Ad?=X@eNHc~aJnftI~yTp7x6_JU0#kUSt&oK!Td}@ zk0{Zqw1Bfuo#w5#U+3VUh39*mTVLlp-~JXei}M^FwK3MvoT>A@cfZTIr!T<3vVZpx zezXUHB@zN@ES`m7GMBBegtZ_GmVgCoG{S(?f`&F+SZ}i0^fBE%)>md&Y=Yd4usT5N z7;6Q77!ddYgJ_60?kc>thA;t~90=(m(+;BusU(5#BNM-bxseBJ84d@`G@D#}_F3Xc zbMM|gjE;Hc!g&@J7Mx$($D0g#edZcXRu-GAM1~8Y$CBrBd7;Lfx5(YL;p=WhO9`U% zlqe(q6Dgk?klM1WJT6I}lrrKr=s<96PA!nwkAK7bf*h z!;PCaSwC|I-}kwG{W^~yKW1-l58wA$TU+D1-~BE@5U{8E)$~BsH zyTzc_=gl|Ym`jAU85TDp2%L~Jzp@0xgLGSg6hO>bdhQEJrk7T0xnM{Ol)x%Ns9!lZzMU z7(|DxJ@*_>g}c=Jv%K`m8%XJ~eSL%Qtj}w&y~d@BmzbHI<Fd9wO3+*y1kjYX`EVn+M5FmpF+^ot?fmV6;2UY%IhQVuka!o45ne!nln9Dbdm= z)|NPm-6>mFU{F@)AASUPZhpN z$Ii*qW_q4+Jv)apOtBlv*V9i1PCZ$gR3-)D;yBI%+)lC?p9Cx|f1|3ts(L54AM)by zq%@eEt<;u^c`}|~lwOSNjW~|8-}7@c1ysk|k=Ut~G_e^-S#D>6wWpi(rTin^HoYjK+_LoWG7h$Nc%7$)GX?yPW?0hc}(6?yYQ<)pg_(^A<8uX1Xdrt;*R#>CSIdfean zZFF!2~?&hq!lba>{II#2zomI1}Z6Hg-yXY_u=T-B3(wD^pkjCP#!#TbQ z*-u9XOr>QY1D`YfF8L#?FPUHI7Y!uWMIETs?e&XbTd7@DbV>W1Z*Ntf#``$>d6N3e zzv^P-lkb2X{_Y8fTo)Q`&JmceC7}}dW=LRT`ohGvb@CWuf5K>bLSWghkWqi2i&-ID;pb=xcX?+>%8;! zTfF@8i#+}GGyLMcUvRL$$D40{gSXy#lZAx^;#l+H2Osg#2Ose6_rA}SXU|gy8V7B> zhc{T6H(VL?nY*>kcBe;2`Uo?Eu2NSWlH|IoHDDrE{2Ff#Bo|DRpfg~0bBNX<4@{4( zrpEw7*SNqIVNk;uNz~~Q4~Iz4quFR6m5UOull9nB3eWd4UW=ocZ+-jQTzURElv12O zf1Yo?^9~OlJm8z({3aJJT%g@NI~Yis=O4}QR@^)(J#M|3+!oLLR2g%J^w zJHsxI4%-DYQZ2aQE}UIM6())1SE#G5Vx7P0_n0+E0riEf+qCe?RD24J|Kj#G?q zV82Yi6WeV0PxUevprxokTX5i6Qk6*8Q0w4{f*m6yMhMQFdy047c^e%MXdUeGhyUF_ ztW+0arCG|yZ( z$AxoGvv+U+78*gFSD$;47hZdr?d@%h)-Eb`DZmJTXW?uu;0Le1NZdNY4m4rCi9g#w z`U;^f!Z?UA>zfcrm26rVKwIaI0twcEL>l9QSxJdd5-r?yIo%&(28sXrzyZDVz;-lI zZ{XZ0_+SD7p^uFqI_xs&^)P`%)D&LLCyq67KPGDRF#{YR8A^<_Zc{u7ULh=r%Ma)G zP#8-TLl4V_j`-Yk*)88h$c^>HXNi@Y&ZYbsTcrS2uKhXv-jecNQjZYHHD)kEB`2th zTaM{;NL`6oD^Nkmx1N20S65EaQz5NRmsLIB8}p}$C3y82u73T9udjW=a6@7vNnjP1 zgwM-ilV5qWTpb*s?+@{Idt7>Por}+$;eJhW$9DOm*JDHU2@#-U6xO+PBJDsTdl9H) zgGgD6O6HYFfkG^PWUcK4~4t0iMUDye%~x4huA%G^Ufl+2hi2VA(WXf7&;v@x+s+0-km( z#GWr_a&c@@zRKR_{2J%mHv&2TG40G_IR$=5?b`g9riL)pV>ijpIticU-+TFo>B5!@ z$110?W6EP{oF)pCtY}nK*>v%%YV@?f%k@rOuq*0HQF6Af)E0~FY%}J=SkBv!@r!4`&N}O9GmFwWft_Ps?IVPRs~S!C(pF;Db$}$9RbRK zkTipkXX1eGtY6>@Tr z3BD^U9Ws~qc<|*(fYy^NOjXKK`CV0-9G%Uj{7zgI@HOf8d>QvI&6Bp>&G&jeB#I&i zgF#kKr>7@VL7s13VvMGb(giCI{-+AD7D)gYn<_f)UvV)irfJGa706sAOAE4U0uPq+ zmFW&eC+x>LWS<7M9;ZO2<;y-*CIN}Xah=|Ws($8!9aN?A`zjii9g7|XVMs)hH6!J^4DzP)K}nzaxkUsnns&WW$Mkx5o`;f( zMx#L(_}Iu|j0=cn4aCs^(>lNe5!SCKN>wsQchs?3 z6L^w=u^40VmBRWyr%$c&ul~gkx%k}k?C$OJi}&9n1b*)aKj6jJUtw`&m5q%@JbG}S z-~FB6;qs-+_`XlAUZ>Hhv9q_w-rnA5EED5$X<>mkUVn|bxj8<+c8xF$SXo(NdwYw$ zy?tJN^))VExy;7KBhH>b&q1q2xVFN~>S^NsAspUCDMQ^lAQqz_+u*PnsFconDJ@nQ z>PYJCAuBx(*->u8ty!ZnIK*oh=4a=4_wDb2RQ%$@j~EPwy!i6VeB+Hb=yto@x^;`2 zH*aFGEG@5a=FAzkcedHt+jEh_wF5Oi`0yj{-+#c;(lW7*dHC=Volb{eeDDEx?%pMi zV;($wK!4EV<(FP!es+$noh`oj>Pz-_Hu$XirUwmok8E8gL-q6c@)>`1JPd2E{HTd!W_DkpwP=jV7S1mc87h`j6 zOqM59jkV?Gt=s(ZAO8u;SHw~5^fqI-cmDx@{3rhgKL}_Y9WfjX`0TS^Wk&Ai))wc^ zJ;l|lSLyZoSZj#2PHtWZB<>3Tt=o5*nVF?FGlLKUPby5}x60y9&Sh7Hr9=qne6*(5 zw;Ac*st`eRv_|A*V&?iNH+Qf$i&!kvU~EpIuP~I0*PmS+5DQ79b%Ah!f`sd#!E)`3 zuetWcR|w$L>MWgZmu@#gDIbB);`}_n^YU9n%cuC+)lX@6+bk|$U}JNSeqU3sUBs{X z%r7ppxZJ?TJAlW72b+Y!W3D`JoPSrL*!c1$cYppVuf6pK2zNrRUT*;XF-d77&0MWX zLs?w!@f$&j*k6%}qrcYe+07rG`BYv9 zviAw!rGFc(9dMVNZWjq)m7V~$DOi}aL1y|-a%$FCfcX2vSM_0sda zcy^KY(E}QD%iKMDfWF;B#WN^RFc|ju?Kj@w;`t>W|LQiip2o&C78-MW_rj}OxNwg9 z??1*^pN{9zBRPGnJY=Hlf!ka#xD&b}2?VHYYLF-GmL{X6BY``)DY704l%F^maFGF> zOt$NN#Y)HpnJl8$~7z;GH?bzG_%w@GzB$-An2liQYJ1Z923t@lMbCvOUy zS>!$|}S1#KMQ^6EFX)Jm^&fuL1{BdO%TNmfzxvkI^`#Jp65mxiZm<=n*6&e;o#aZy84kW#(DbY{X^yzxV-c6e0^lk%jrn~Q6Rfc zUZ7feyz_>coy^bEzCRrV8+{4fMo$ZdHkZeZb^4~d-IG$8 zU&=~VdWx{BJ!P6q@;Or_AM?Q8Nn~0c&`XW%~hFNT~xHOub4L7M{tzvsp@nXdOA>=5fZ z^9wbs@Yp}-^WJ-3u`*vns1^EO-C{6wigVHvNawytXjI@k>8@*IP*xy($^5C)?CiIQ zBF)O$Y365U!3*hhBNi8zm|2)5u!8xOWd^-I0>kXeJkh=nXok4+QmHRHB|YLerqyZ@ z$1zHIiSaA2QlgZT=hCmCY9TiE@I4R29!48Ptm*9?aJ02c{k0l_A7G88-|5oY*=2rq z)&Zqb&^~H${o3d3Zfyy)N?$a{#njE&l4S z{tD0Y_`^T^L$uaxKHlPsYoF6=cX;;vInJIwgX#1b?jIuh+6Cf?B7}|+!pAB#@&W4x z9v5Xj@mYvO4mEWdAO|tF8>7Pk{%pw7>@4rT^AcBn`}>@`a+$Ag+~Mo5Z*lp`6@L4- zej6!ZadDC7p1aI{{?C8L*|TSP@uinpURmb9{OCtK+IWmejB2GMk2W^gc>I`=Z(!G^ z&8@BMZ(|Kn9P!y_*N_(c&?Ab5%+1xfb7O(knLSRe3Out#-5b!^H`Kk5KzUf9nVX%% z2O2&!=X^Z1N~6BY#)AV6@7y75Ma=XiN(&C71B}%u;lQ4Gw@uz#qq+C!5%)JXiUHW9$|9$`Kt2{P46%ufjI~BDe}QAy6e(Qn{0TIw$XqLC49dQX#WbXbzML zu~N~To1-_7{4f9Of8@-P&$*@NSc#ehwNng(fT57!&oVHY|MX{n&Eoukciw&tB?_Cs)jq(|Fd{1mriFFM9`2OOy~O5?`X$ zx%EJr#0RMXGjOu8XQAf?Kp5QSkg;g1F&F}=@T_pb(F}OXqo$OTJ;penrO9`ckcl4T zg1Y!db0!FR{^CUf9pf2;wBymJ9r!t}^OQA-JYN=om=V(y@|Fr`&y;c-SV9Q;{eH53 zke$-OlDXUxd7Uxon+tTM5pj>2-y#S)G^8fblAT9gb`}EWnm#odP}3fZ-YiPZ5SbVg zcr4A-@Ui%DOn_^H>VxPFxbx*b_8+&2jYJDa+x(nx0pC)N?~Y5#Y{xH! z7z)ZKB-iEBez_oD`ICX}u#ZTUWE2W39T4fVXQ@Pk001BWNklPMO(`>(!e1DR?@FZpP_EEl`!r!TUscNq%);F#FE}ttj zDbFODPU=%Nj~Bm{+R5oNn+9V|)&nUg({&QAR7LwLy~Y!%(9RJy6DnyXw%Xv!|DH}t zW0Kw7d?Rzc7O60&*cey0Exj2}X}z+rgFQV}8TI8lPXf@Tb)6(%xxFJyBxkrVk`GmY z>2ZK%Rh}|1nD2KPd?^1u&7jXepY%S1@>JD*lCdm*e~b>+l6nWjO3;=_8-j3# zFsO5GeVyAgTbN;!bC)krtIx2xz03aoE`bbDDrEQYkiZXFTv)~r0|W{YOYE>u-M9Dx zbnK#t3OuawKxnYi?Lv!WVYL?W%0j8{sWCE1AeRBANwgbJ)4`tnH*!zPR8Gm6dnd?o zZ0>oYLFMY2CYZ|os^a}ZGp2Mm1p}=u&ZTmy`tIbPf{}vgk}(FYHHU|Xljmi=K!H5P z(=n6h6_xLwBvpOmr2I3fOnIDAqk7V}Pf~Z4>@M?tRVsr3`Ml+EgmFM1-_G)aI2~Lp zw)3R&tNgr7%j|tg79{7iFapB48f$0Qx&74*LM0HP&(2|&y`whTMBII_&DzQuDyU)G z5eCE7=05v-hxnnS*Bziy__YQH+VcMO`}F!lTD^#%RwoEUI=wElGqbdh+KFsaBYw8I zb|-D`^?UroFFxSvr@unSF;Ntwl;Gxrhj@OC!JyAkyMr&`CqMf+^B;Z8k{7bx9Z)}P zQ}ZKsI(u~cJ&^7+yG$0u;b1VLAI`{$NUbJWO;d+H{N)IwY4>_UcJf(AAX1z1gx$s@!WG)7#+~M52>`L(Y$*WiaR?1E%V%i~LcYGS$)F!2@VNQ#fKNZU&+3wZd4p9NsU!<4^PD^PBI{pmbNl82 zN?Np44j3xvq9Z*q6|VVHH)G(SO|Fm%cgm2o)R-}z+f+)&A~A@K8I1`gnUDt6F;XHt zkFZu_*lLZ*fO1O5y1b#3??aW$DC>0T7$(~H)O9Z_pY&ri#sY+OzE6XIj7Vu2pkAM4 z?aU- z!Y0GG&QIUJ!_wjf)=r&gFdVYFaFL@U#m!p}36)QOU{G^SZXa&Z{@_D~!y!g%Jmrn` z4ZxQkUZQKo%F?PyS~Z0h0&NA$t4ln8`7+DP%YXx;tT9OCc73#VLA+Y67I*I6Wpi_r zI2O+1rZEf$eFV5Gy;3EBSK?<8n}$zwk>ssK2?vU&pp~%h6q?Zn<<(hQTH>hNrQ7LZ ztzdR`7AXb&eh+I6I!ZP(q;hSP0wok$YfKzbk0ZiZfsiPHtG{A7Pr>fd`XVwgaS~4! z*U1VSFRyP?tufjhPm@@Z87C!$^5sS`G6h(H4FlwCfUrJ|#wuaZ;mn!yte=^ub99?{ z=<)0`m$-Owm3y~$`Q+oz5mAiockon5v+g5|p{{(s^X6+jbN(EE`?IUGIs=TA$xKn? zuS2J6Ox8A$>Xa@>T;6tbq!mTe+>ju1ztGLeuD8sAB?MPKu#C zDOOPCnJ2|+rv7b5^YquLv+8};7dwUCIT=yD{d{ulWOOxrJiikRPY(K4eI+e3o_Ct9 zHp+K$JTB9nNv@eI&%>A)@;<+u)Wukv?PQ5Wuotd+3Xv$36b2)!!zMzq(20rz=J`ff z8H*&s$ByzSrHJGB*p63$uhS6WV}aY;Kx7I)5&N{me*2m4GwC`mZQ|@c|J7rl^2Kn-abUx)} zVVbY(5}?@TYK0IIYA2RKc?`?_Rw{}3sdb^A5nqlk{8h9nw=ut2Rt9gYK-luB((+hW z(YMg&L^cQ^M$^qE{ZJay6xdKQ9K`r>Ok<{o7kHR{k4~$HZOq~sFnev#!Ue7{mSJqL z*2k;)%q-0_U;wiXl&{d;K3ZD*MnKd#U~Xv+ZDU&dEtK%G_1{`ZPr)Tn9D_l`t=o4T ztz2hvve-4=YIkbs{ZBr@w}z#G;rsxutj#b#qxjX`2kb-^8`cmiz5G=*HWnA~#Al;3 zGG`6mA;}V1c>u1kUt2Kxs!_wjkYip-? z=Gmut`|Y<0{D8&91?J}F8T2E5<9ols>E%@h2m6FGKJ|qVbLh^}%%57uKHPUoX<`%S zT>y`?O;AE%kOW}^(d^-w*hSAB06z4O+6<0dq+5iCQXbc@UuSP~i~r_d{YwrG4*BSV z582<|<8gh1L9a*HY~o4JoggCpzD}ayCIDW>pE#jpiosBfO;%Uy#{8(=X7A{LX5i84 zMI3c~*4LMKdS;Eg_cm$uEJ}E6?e+NZ)4P20t;-zT>+|)EEv(*WY3VGpvyx#yX@jsx zsS}+t@ki@~0$FfP?f6Bd0pWA>t297iq7^c>CfK5WngSa|9X3_|-IeuTuSYEmC(8CR zPYWTkpaeS|+0;3p2H5#*Pw3{m|oPVBZZwIJjOp9K}((iT9!l$-& zk-^Ff+>-O0TAAl@yT$e|w^*7zVtr|aPd?q^@q;_uxqFvpJw$7_kV|txVL3ae z-saU;UvjiQ-`vc*Rm+jv4Re}r^e!2{ti37lTF9O~%bO$FDb18$$i2+j zEB}}@(tV0IzJN3e*@!PEfi$H@sr!ZEGe!Z`^Y+Y?=2z*y)80R6s$35yRF~N5uZ8g{(;=OA#|+y<3_^_Lh-=DZJ$h0)&X)Ui=seN>U^KrV6%3r~J(3CPiw$Pu&=((yOxk6}*$j1;pst zBmi^L-_v+5Uzg{uOlaq;p;W-Q2LKMPe$w58eBMbw<|N}-K;~-(KpV~c!aNtAewuEt$L{Vfhx_|Pk#ig_?C64& z$@RCY&C_-ns({2XaHUE>O?XxXdQ78L*8ehnGlQ&X(3jgk23Swvf${N>+2l`8TolWR zfm{ZAYf%}mG)souhT{Br=&l#glot3cfH_tXAOQr?c6Q#R4w^TtFKa9VD6IaSHADteSB>n}JImM1)1NBm^EVIvRmr5$HEffkugvjYevXU66`@JWPX1(5UiPPd=kw*Cm1$Hp zCiybyTuJ>R<=c?|y=q*_?7k85+9ZIF5cCIqA{o=L0_FP%Dd-LR3=W3G!Z0)uRtPlU{B#xDmnwq>gA_8` zJahRIhYvY9Kms&HHYUchSu;e!GG9}~zQ;oov#UJ%o=C30jT<NxzPx*nI~$v55hdoKquWup%g^5XkQ=w|<9R-vPKW)yJvyxpTMxIHUtB~4 zii5*_Ha8#e@yDNV<>E6mq{QDjz}~sfYBNG(**a`tJ(-+3lp{bX#~T*V7!a1B@;T6g zHRYjZX0UyO348`|kLJoUGfT@T>9e@Fi06A;eD(tW)Bo^4vNXTI-~9cLx%$~>9334o zJ3B*TrhyE6{J_Uz(blA- z)<$^`*y{{2N)YLYP=(yy*yH}6y^kj)(h7Q=BUV=xpZ@Yc^62p<%JUIY5$hqo?;}tM zBT#t=K|6g=GLk>#Q;y|%UY>V(-@aA(^7?SPQ8Jb+q<#*umV<)>eBa0OysT`#?kZU` z*>7%9=gZ3xAq0LBr5$kBRIS_GPvunW`OU>kxGaeh^?}?4mV?KC_F9 zM2qtr^?k(bEZw6%QAbjrnWbxL^bQSkLl2KWx<8<6G(%&EhfyVs2E#7e_c2y8j15}5 zwawuC?=CJaF}E;Jzt=DJ3ss( zaTFoLfM+gW;>~xy$@8zif)@rn-rAzknBn(-|M!v7ZJ-Q?L;n4r{wdc!yN*S>U^?2_ zV|EZ>B8`+HJE>D-9&)OS1(E6_BQP#A?v2GtF{YHJJd^qa&-jo;N{i9y;PiQ#3U*EU ztF5;8(rfem^?L#&net~B+o>D}j z$^ZPPf6bMbzrm|-{vK!i7S}%h8UObG{wWtPJ=YKAVzYm=$1#M4nwouDk{roXmIZRm`-eyCdvxEEI)1)^e@ZkX)?UBeN)o7 zjWJ%ntP2Yv3OtpWC8_*QpD}K!q&6KXS+h)H)c=Y!t$mX~#A)d`T;n*bp~|kSwj0uy zWb-C#XQ{V2WT&vXle+nFe^%Ij6u(#eewi$JD%@k z{Cqs$7w63=i%mg+OlPTzGC!%U`LUiB{M)nG#ObWa86w5qCjr@YfP(U6ZK90l8EM1< z(}63-2CZBsUoO2)B&3VJS`JQBH36#xQTbW^v{n^?^X;l)h&(;gXHqHl4-VM*_~Y!k zER}ar{zTzlKJI;OINP2wYkf}+#KD*J?`K6 z92-X?0c*urKgQ{k7Y0@HXPn=Qf-1LQm1!yjNXIiG%_CA}Hl1tL-yZ$$6q0O>EbvL1 zWAsOraXE?4$~-VlIh*uuoF2vYkN-^Hk9?6;rE=RU<$xVY!3Nd|m6JM+Z~-x8D8fqcr#?o9SpxF4CMpqAy?BsrA! zb(#WJCAZSN>G(}6TTZDOB$Yr%F$@iLKSb*oV_|>)2+wz?!IkhC^oE4B8cO--C?eLH zwY4=moi4-SkT}+~4%noOB3bgMof*jjl$9VALqBAwLQr*L zrHBw{DUnLDv9-y+{dfP4SZl0t>$_5tYuB$iMaT_c|Q;0kL#It<4BmT04ciqPS1YhKlhAN1UITVf%2Oc5Q~%@(kh168lk1bbo_x zx5uf|t3<pU~Xv%Er)F1+hA*N$jYfxG?y1qp^vXT?%ck|?CdP{`6h>Z zN9e9bQse%kE$1Uu`uMeg`Netm4h<^wY0Na)+uo&7t04n--L`wsabt^i!8)kmnVjsY zT5pc?A6B(FGp>(}>T`Ui3Noo`6GaFIj!u*L)7Al{6!m%?&+`~0?ik9a!c&=?J}=1W zbYqIq+&0=c;6{@DOAam~v4p;&*$9|l@bNvvPVW#G3@K(1_2|Y5D<#WHvQpF3Z+^nL zzT}OC8iRg~mL9T!Wh=I{clNRNh?Tk1Jbe_?`s614-CKO?H{WJ?*5~wW!0?fz5&ImP zh%e2MZ5eYARgc#)mKp*xv@Vzx$;mh=keY1nc!~gz|+yl{S`8h=9JSi?h8p9!sO%Vnl@{eIr0>R7Ni}9j=XqwtPofk+I+tI{%FV-+cPfq8 z)Rsu*O8N7Wkv&-kXGXw`&DIst3;V)HQpflqw>R6&#M{)G%LGAc1j;Vjea zfN}aIq!AUQo}i2TQ>H4uoHRz0*4?~*Ki1AX4~gsnq^=usb1H{ar5_*h&4l(JhgYTb zS(Em00qa{iQ>4D(I*Yuaf6z@Pv2G4on8w(4gf8KCQmZKrh!dWbx&wPMGuY> zvgyK{`p6Q*229}75{g*OU}X&}`y6z8Fo>w1eG0L#On>(PEgSUW9{Yz8UtWJmw`YhG zN9@MBtK$YuchF<5-o!VGSP1H~O;q4fn{6<=xWK_-56`brYtA8*hmJuNXfas<9jAL) zXAolxQNFzEw0rMycyQZC3g62=ylR9F{`$%@6LqJQN%cbuTgKdSjDPK!v# z2rU?Pha7AjAhqCN`+%r7KqYdns27UNKU_6^MjD%CR3^24+{r(SF#_ilJOwH9Cw<1{ zLH4%*YZDO20Gz-~}F$iO?p-^S#X9 zru&SKH5L^}q;Nj*lv3{0hp^Pc8W;!cM1vtnXe`wUnWYwk?ha@-6&+8otqr@LqOBxa z2uK2Vr2g9qynp~H*_N80+C841?X!6K9AAiiZrBcw!y3acukrrF+eqaRN0wm}A*JNs z|LLF7sJkfO`-l4s1_S=`zy1|J{fGCE%A?cnvcJDaJd9jB5?(dd`3FtMFdN^5Pfc!K z<$cLnYp^n=D|={Vh?R?^i-4ht@vOx6JhY98O^omq8jX=IimwzNp6>wyZ8g0?kH|zc zW@hjjJ~KQh2`=s)Fk6HA*PjveA98NChrGOul#>0eJ?8tcRreS)x?E%YbmnX8r#Ov|6o%Nu znciKi*O{H2Wp{U%yZ7!n9ZGu4HD{Qaogs>b9337arKH>KG8hbLwc2>Tk5V3yj!^=_ z&?k-~_7C;}%cV<~c=_d*kisv>_K!dQh+lm0K2bDuvQxTKb@h6k+1WWZHa0QFaQgHa z+MPDH@7!f|Wfg1SsMY4-#s=4~U+4N4Us9{p@qC|H8(N*Nla-bXeD*>QKlDJ_1auzP zC7lf*vH^EFMv{4SkGxby6dq(emd}%Fd6|4q^gdxp&+ZlS=YJO4mv6SK*9t*rFyz9e zS6NGfyO-8}}~HU^K!+g+AsU~zq&`GqCo_7R>P zK&?rv>IfNNjEhb#5})YCPCn&RWuHv5fsWH(Q@tRwr*mb->dXNEa5B3qzxk4$q^;Fz znM_JS=^zLYLS*tIeGg>s3NlBeXZLe zgBLk_^LSEErpdIn9cSxJ zdtRP1ll1n&9EL>Z<=ieLO(+BeD}E>||0I1JWlPG~M8mZs!d8gvu|EDfsXQM~%9BrO zKTk@N07+|Y;i~*UhW96Z*Ar4e;`b+gXssm!3=>8WQY3k*nb77cP3zDA0D%90T4zRz z6UwF%iyAXr%8O`bs8n}k5(se8lr1z<7Oi}nrVV`F0LU0Od6x$de_f`TNTYH-7&-h- z+mT;rsHWRVfuZBT&9b4MFEd_$wp!&D!Swm)U9M*nznhlIYL?D$R$f3D)QEx@lrw@I zP%xxB+#w7bC?zpMa_PAjIrG#de*Tk>=(M(wN>dB#_{yW#?I5fn8VtF2`z8@SpMCU8 z27RAs*kfqgaB=I2Clu`klIoWJmi5thKDp~}uyrukC( z<&@S8>>Q(VmCwaO3rM&8%V67ayz`{OZW1t-ze-<~cQl_SzZ=gxzB5?n>uK$u2!bAy z@{4NzU%7wf{FA;@HAknlHIf##MY#{<}))o5X9W@+A-mBMC%+?nFowQYZqUoK6>rw(MS=uE1jnc! zxyQnSw9e6nOpJ3EK*i}Al4N}pIZ2;aP%{~1krt#i2sBDKzj3a)?rMG-^dQqClU5jQ zF(Sqgk6Nd|da!urG+J8rAMa52YpgCUvvsh|(((#hkG9aUaRERCXdw|&plw9Z^w0xM z|EN#ctYKA5-*%}5O$J9AAuK}#R{F%APfuwgDO{9dJMu|7o?Ksz6g;+ubBz!k1RTx= zJd~PFO%e|y;@$n^6l!v7#kvz}Mkj!}rvStLjRW@`lSp!z&SOVkDUvJ8>0p=ioC0s# z+-hLmiLWTu#KRaBdSC@odhBf$l;j-@1_ha%cgN$oA4m>>C;7b9cUCgW{WpNSB~Q8o?|fyQF32BR6Y`gD(W zkT_7t#ISew7X5CYbw6ftb%ue^)CXPWh7n7i4}O65B{Op#p7Ck)`gEeltUfwor4zE! z2xu;8Y<&Rjed;nshcUXZQ9&OQ4TwbyBCe!=O$hurMtaWu1;TP_ZH;%n_gz*`uM=y{ z1mTLiufqUCwcV`nrRGSjFLM*{?)ltc*0&dwh1zyCgOz5Nz%y#5BW zb93CibBEzzn1M&W=drl3!20?+XU?2uetsU$4>&q%@#xWGcOb+h!G?sz*cbwlfSJ`- zeI<-GpD5~oPNz%exfO|vEHXU}a3^(*%ad9$nO&HDGx>Cs4R5m9IdQvHGDrT#oNTW+r=Vjz0GaFCYEpB+{h$pA-2WI zQy#P@IpiZof8u!_wR)YwU{L8c<+y%VVO%G%ZKmQ>$K?B`PU&~2>11hJruDa~t)64z zWEZa_M1*h(vPc%D3LDc^Ok(sT3bL@HYk5M+u`4~(CRJ7Erx|@GEnhac%c;D(UB1GY zCt}%rU_L@M`27x+z42NH6SFqH{kHWBZhs6=UJji(>~fm8%rc>^cX_B zgS3&WMGEFuW^wp{WO4Nr3k&l+e)N#m!5%_b8a0o(rBf`-H`!<(;j4&#tIfmPH&|Ic zgN>XYO=DtYuGPsBUf9E~0z0OqV*%&P_^+l@k&np~Rd;15UoI2Jh3{qs!707v_L0-s zQsG_#gsPOksu~Ui1 z@!2#5zKT}mXXTTUqf~vGwCeBwm$r9*mgC0q{5}VnSylJeeUg_ik(4M=k5>1nyVcXJ zo}Tqg&w9hN_Sz0__`h?6!{Km*f3ai3F7l4SkZi~uV zXtenIN~v1Bu{dp$jwUZvbBU8>L4GKNDtLYXrW6>3;I>J-Ey|H!q=i)K{UTBPt6ICq zykY&&6)9y^87{@?hF(!DHDM<@LYqS6H79{~|I^w?&bY&I;zK9q+TH(e#UIQgPF+ou zfboXAD5uGUN>k@2Yb^$Mu#Bp{^J8W;x@7vh z`|D59!&xal4VD6fkh-31=*%_wgg8%DYnW-vG)XBEh3)s4=m}$^(P*?lK>-NLsB(Ig z%q>tG+=8;C3PDJfqR<7}Xp9d7AQs0{;uoB~mLK@D?VvD9L)-mMC1G|rL8S_Ei%JqO z37Jh99S+fGl<8m##dMfMAy^G6&Dh@BWdC5F$;54&PE_JfPoEm}Y|82Jfd0lh>#KdH zqbX>-2Weo!z$6ke?fh2qc#OBnz(u;{S)fb|T87{8!D}Kyp6BP1_suH?CFAeSxwP3& zL4JfS?e#-Dabr~+kf5+Q(c$7{iUeO3>+uVN!hxHK2Rg9^K{yzn@XN1HS+NG2SQ2Zv zpcE_JRrJg<8&4To&GUmXdz}tXW(9=^sp)Z?^?9y^?0AMUDM*eIg)%8JvOL&7;hzQv zJn1OzUrK;%u6=&<^%MGQo7_%Pi~>o7we>#Nu3Y8vYu8;{h$1fvI^7Jd3tql_Nw?Ew z=i+6$-7c%Es~jC2aplStHa0f+^{;-z=lAZRhf@;c4oJOl;Q}D6t*`Og_3NZbh6rqJ zZE|#c!tU;Kvb0n3MV@QGvbMI$cfa=mfAmK`CeI6g{({w_#x7& zv%7b>b?X*+K4p7riywUWhunDmb@ul5xOeX!BEsqEDPMf?1*fN{bvjF>-q)c7x#xA| zIsK?+1sQ_paMOyEOHO-}h>> z73cD7A>+I@{CW8z)!H3~BF(}hLRC=CBA{a~t^cW%!fNA~TEJ*s6MnyM=f#uDBBMW) zAJg7!h$axwq^Ye9s>3U)ZHwpWh?>z#Io``Q3$o})MIXQfRH3cT0pse2(=i4gYrngAwoCP{9Qyk(+ zrTJbneQCH_Fkv-*Nx2nNVdwaV1;1myZ_?F_I~xN{i^ltTud1CYWxiII#-y_-k0#A+ zvSpcEYLh|pG`8onh!5iX&Gcs}&uyUOqB<;^7uqJwARC+D-aPQ9Xk)Q98Dz`)%>$Cn zLwQ2}0Yq#&WxNL7HLm9Kwiq@|B_PZvF@Vu_g?5R-g*a^-OAq4sIe75FZS_qees(f8 zXix57H0{Tw6|l}($(ald!EZ~yY-cEEy)Lua6tMKT1I!&1GS)MWbJUXbLe%h%cz&_~4ZF!>4R! z7nvmm>6&HIJ7x3M3jIqKvpU2i1_M~FvEn+zi1&zBi921lb*`3D_nPHHvP)MPZwt5I z`7V!Mz2YA~`GlKqy+P8;xOn9X$NPso`RoCbn{vL?wm?v1y)KevY+u@9bTZ)NV2TmT z`pymF*GM)jA1fsXA_CKaI~)Z8C8_i5(;7X|Xsbyjp_mjLO!iPH5;CUK z3A-;R6ovBuvKn5!+-EcyqLK-cD9DA;!HDV6*m;&3zg5)M^_R+kYM)!$t~U8u^4*e2 zp(#_^1nE3FbSmxNEU_rScqDaui)QubQz&5Hh zQr$QeuO9%$aH8NYxf|RTdb(h3W_dK7(X*C36^@NzmMRLJGfER?z$=sUaB|4zpkTEF z`Q8j6Coy0rnr?T6fe80kGmbkcqfWa_J?JLrjTLli{X&D~v(N7F$!GWIuJ-x<_dn#P zKm93N+gogHZDOruJQ^`P9g<}kd7d*Gjp%l}+`W63J9qAId~}R8niN49OMi8Z;c&#y zfBtuT``hnx`}S=P_77NH?a}S+FnoH-Z+`nbW_gbI1r=+ZhulXWeZ-&s>7TH&(nISJ zgTa7j&z^C7e9RlS-as3}%iUdGym-Om$B()D*=HDQ>8-4A=gu8Y2Sbt|@vM`VDic&k zVI|1p`AZp1$G8O_QR3$eyxeo+T#KL)j8UasVDWms4Z7XDPA7ry#61_!M3vo9*&1z% zRegkp`5ix53Eiy2ba;Z2%pKDs0&0St=8pG_p-@65LShtl29_za!E>y*L!OibkmN9C8!Eke|ORnJ%BE;{gWZUD^NYh}5C{;2sX z9nYdEKq<10lLZuh{<2_2V+6DqNZnjnXzfA}kzfl;Y5^N962%zisdpIKJ+d32#=SIN8QNL!|7v8^2IUBUKBwJ{_KC88Z6 zEKZ;pXYz!J$~|Mgw|qfmERKq-N;oZOkVSypBEz$-aho9KS&bC`UX=GDg)%O$4J>Vz zWg3$~?U${LIjEYm+=8*hFs{|X=T#bb*3Pe>M)`+$bk@hpI_p}>>{0$PJ#%uuJ?BQM z>aA*$F{RK5p}WJYbN%CuSrrU^5fDo^Qy8leGx4#(T=LX=@MtsfN}j<0IfAS%BKK+`h}+qnD4u? zPS(k3Cy%W=#K(#Q;cBY6lq!eUTztGuT-~jp{ai90kNE84JFLBSf!>>!IGH>r8|RqQ zoa5bBB%3|@oh@vVvUzEnuFjYYhUlrKx7z2{_ippz;E>;b{s|Y?FL1nnN_Ru?-p);W z$qL$nMiWj?v;jyBUZXZB`%=mV->HDO0|HIX`sM~7{+Is=tLqlsQH+1}0=<=R;SVlT z?4;OBm#`0o@#8P(9vc!e2dD^a?QHSh2k-D=^pc%7F7xQ_BP5?7-Gra~*MH8}_a1QX zXa51QLK?giRHb;vd>vo0`WNPcLO zc=OgRl0t%>w zaSKQxyV4j8SZL+QL9l}i87@XKwy1y$ENQuLhYK+wPB&22V6_D)sC>daErs=RN~3~a zhmE{eiQe&7WW1F3gPfMCHtx@qO%ZlBh42k=rd7K+i5CE4@po!XDwUi=f=y+8@gW~C zV?$d4Str97&CC6lyngd0N+s;=?{j!~K$4}r^WM8mXH(KNfx`ZRWyg}J$8iAwjV84YEdRBaKrdp-VfSr}E! ztd|JdvoDbsnoc;2mrElv1S)PGNXb z=XkXIB4zl8=g*(9yZeGX*KP-cZGEZKiEKOzd;XPRKSxX)^>F3U*t}}|>EH6@RCA81 zKX1?`NLg9iXlDueG^Sla&`P5*kiw-mudsP_n->qCF?=zA-06NMv83xAvOcWttfB=D z4-Z-2*rFpDgO>w};~5l|#HboSmcE;1+Ytvnp z**w25tx<;Vex%2pb+zGji!gxHon{$%8U}{772}w=LgEEQjmN6W&aH&E(CZNLGwV?j z8XJpY#kuYJrXXhXej5thMo%-JWgzV$h6qD@(YtQKt(VzmT-4|fUyN;z^;RW(Q5!5% z*cTZ#3)wwV(3f3df(VnJ^~#ImwNf>yO$<%mTHgN-xU|+dATts&;7-03Z}5w?RT%1^ zM~Rzos#m>26{bI1xb0f4g7$10+uEoNK#J4H>6Y@*0)VP5pEh9eyjL42v@Bg4O=m3* zG{;mu?ZOhNPvY%{YBQg+WWhr4tvT65R4nU@_;=fu!e#HpUN=q9v)QMM#(wi^B*^)fnZE1c{owtsM&J}>EI2DN6neBm;^t}r}0 z#hw%{&X`huvCJSBdc5@~-{Z5t`!#Z8dGm+w@nmwqwTsu7y*%YtfBAE=WTjG-ZR`Op zrKq$xPhIP2t5glow?0<40VlhKQVF*T@0m$>FxPJ}s9Q;I3rgsAyR5JFSyyYUo=!QI zKC{!D-8)a%xxURP-DN#nWBZ+L(p-4(>3wF?oZM>G`WeK&bUj z5!Nq`Tg^&QaIJvN`86`$mmdH8wb!`P12vmsim8kDt1`4v9GvF-djFWu#d4HFVF4T< z7(lbK9euw<(2L_kORcX;SxzOhODjeyJ`3*#AVb3MGBei(gghE?abt|7{Avw?s=UHV z`ytq`AOt{xFd0?=MKpIH?z#A}5iX+*X`=l35w`4)%9wD&z+y@n8s^&)<3RjaiRZ%d zVOd^ipEBPT$j5~QiEUwv);Zl?pZDIm&DQodckg`0*W*(zT)4=Eix>Igi!Tu@*RNfr zztUrSYlBCR4w+8Iq$2DepK$xuTiC*Ku)oi#K5^c_zF<#!2}oGqC*@8vYwEHT$~mCK zm-_BT=nSD8YR3dX{5tTa_IZY@@MU-Dt08S*lbrr_9oiJu8NEBPH4CkUz=M z8#a15q1m$T zZMyw8y;|{?1-eR`-SGd{b!HiH(*!vg>xLoO8G2naP5eEbGm@GNFH$kH=eVY^-)1nM zx5Jhxjc27@2KKZWGtIo3G3R0w@w=TUUaK4{Rl zsO-2Nr2;Z%^i{vxzL#me@@U)IH_&uOdsKNYpgZJIT1AoYUow6qO79ePz{eh%FUz33 z7mq8`mc<1@i;dUT{3Ik{W!1M4x~xj+g|nE%<#f}|*KOA~5mw9i^sIa;<0qtVKP9>i zWI3xI=cz;MI3e(6%p&oajFi*`^wo{ivxRBvSXyweEw7mV7zk_w594jpi@YhD?EzIh z+;|)IB6{1r%!7Q4(>A?7V?#ZzTU#v8Hh^y#0MG^y!hUD@Hn_ut^(-1#eh#3`%WD{OD| zcyjoJ;p~`XRiQHri3c+!!J^QjDUuOIPnczzQ#0UTaKMl$WEra$H<`$czU`6*1z9TT zX-fdbX{7acy?s<+C{8C}z>LNm-T8_S|L{XD{J{_SoB#W77(Cu% z)o$~{cRu9w^CQ?3IyxmW*4MGLX%MUjs4~WrNqvs<^+?5mncBkM^OsC-qzpS#*0Y3k zW=V!A`h{V8s{@-CSpDE_-jHiN_=mfUUb?u>c`@U~-U|w5WZNBVS}<9$)sA8X1)CZvc1KM95_Sn&%(sB-s@b1c*-<9DNr@hoG#Q=4^*$x`iv zA$+$`aYakJ8=SYE2Q93z7_I4adtAMFleccaMQ^1?UKIS|7yn3+=loay`d?$MaPQtd zPESwy@WT)3_xs$sb&LJ|eO~VFQkW@{Bo4SxDOWCEW_4v1Yg`Q4*I$1FB?-@;@1l~F z;c&?Q{vPkX`wm)bX88;RPEQBCR6?F>@>#(fZ@j_xzV|&=S6AuvRv4a++1S|R^5v^M zdHjSt&mAyiEP0VP!Yff?B`duix2{}a$0{Cg4EPO)wRZXAiuMTKPWczi}eVh@j#Ha)*ohOTz{)DR^1^| zB7(L)jEbS=Ea2h?`w;ria-skLAOJ~3K~yO&WA~nrR|FxHdl zXBu}CrGxNmnsGw~Bz~&MZgpky4t8m#lTeCcn&klR+gM8ffx)67^$TuV7--r;_qb&vU`Rs?-^4D=mmn*T9^?UMzuc(2FL1P+Y!lV8V>EeM(1SXg2nbg}UW(=v@MW*a`8kAM~psSi+l(5b_ zwYRxOx{~tni~CGYPU&_t>`WkcI`7_#SB(3otXwdRyPCC&n+%5&(ta0N7eut{2XUU6 zWpmnygfp(nDX^xsexvIwo&UT*WgCvO%JrDQ~h@^hN~GY@KEKAfJnKJwMYh@rw!uI^IRpb zo-wwT3BtB9HJ>Q_*WPcfys)4eb0B$iuPJ=n!KFZlQ(EK0Q)>OzQm6Aw4Cj$?ZE~b- ze8+}y3=p>E(af(2&ct*#PZ*D%hxQ2I-XhxDz|?Rr1}NJ=&1Su0!@6l?x6K{R^vgcu z?@5x(Z^vGccQ{oz(Atb!8P6?keAaA_`d04wIilG%0n`ZZwH>U|HaErn*4$Pam(jec zF;m_tWi@~<3Z}CeRvXgXpr$!`I_2BY##pYsb&W*e@WnpUvBoBlp)iHP zW(t!D-Ss{zg(9C#(AuK1#7z*|`a=Y(Ip2og-XL?IHIpRjL#fN#SYZwzh~Kgm8!RGH zdMik%dXU#X7GPp6o1Im%ZpNgTGA#;})?B-=%@4o(J%0YPzvtzP6PP(4{ElRx481gA zTUmZ|`vymoi~Qnef6GVjzr&Az{9Rau7cUO^B7Me@o%pz=Vcx;lm$GlP52F&(pNNnM zm_nn(aA7s&^7=Z*(r5hm6XbBr(|`H`yOwdO*dVbpHrQfid!09LUt?bxUhSPQcyz?C z2Y<(3`3gcy|8G~9Mf&rqFqO`*$kx= zd7jtgN1IG+lYwQKL5KG-!L0QLqr!PzW9b^psO{U)P)?#ttB^>0t?o8e|D@QvEsr-0BA#Mg+vrK5f|4Cm|8fXyvA@iEwdT~;?&C=O2;<|kkjz0GwOb2*)D2PA`%Lt z$*m#Bgmlh1Or0-Gx5bt}$8+E`0r3Ky~|%gFPBna=(A0HqSFaUkH#Xsne|&Ks?< z+Vz{`KS4z>CAhQb=O=W*l5m-cCs z5K_S#q+)I5QHgRWymGv!y$7;7J|Q!jRl*rbk!=$6hbmLwdp5= zbVHhRj5N|a1tRD+fLj0$b3;%U;(0%6qq*w&n=U+dWy`tye$hjscCZ>HmTNaIa^u!D z1|J`=a$%jFwM*Smn(A zuV|*(Y%x&BN%H z$<(s`kvxq|1yZFre!NRDJmk-A-$Xrn%JG-ad3A8W^_!O&tt*bc-p5R5T)eu$)>enE zn)2fD5o!kgX$l92tfMHB6(lz#(}HPnf*s80+0-8ZByQVbFxE-}q1xtGwq&i1T{1T) zk8N_S$8h?mpxG7Z0$fCv+L3k|{8z zt1O*v*B!1fp0d&J^ZgHQvytU=k`Wn(E{aP#7ZFDM@*n;^SFXNIH`S!d8)e$~gAtt` zZpBjQf@wZ0s}n##h4V1b#%=q$vX=4w%`G;Q5yFh^)X*UzH68Sk=F9)%pGZ>0J8$0P zzXyR;22MqQ1Awz(!AqfIaBaK)hAoWUUTpk7>lEJK@evBqO9?;B%k zd516&ZRNyy$BQP5>bpqBERvx`QLwkShf=DPF+rxc_02MQ>V8nk1?&2)lPI=VSGn0) zVU?6gDriyez$4>0};n)272tA!3g`f;rV=1)d*|X>T^FCKDdddi(%2X1t0?ee#ueu3K9VXL=-O$=r@hLORDB^|FY(TYjGi_TK8);lUD z^hD@cTaIIEKwB3|CGmfv9BO6)IFUPmI_q?t=N!02q{2Ah(ijJVDdoUsU0`)lc_@nG z9cA4CZ{b*U$HS3c8`jHP?>S+Nf&Byi@qhWZy#4OGY>sm7K6}pN$B%irxykVJ zM;Pn8ukSwpIWJ%Ba_!1B^wWL*{+FMyfA=ePY=$JkV={!CaD5fRgoNQ(o~IGyiS>2G zfUUgK9Pj#?Sc{RcW5v%8Az3M$JWy2LjOApsJFlEqVh_5?BXCYOiu+IbZRUwoHi9kN zyUII&$u-5rMn+}%rb`=Q=En5JU|-E%ti5ZanpJZ^wSC_bgNfl)@w$-&Xf3c@l(&uY z!?(4|%kffqpm(TilSW^CD=uvqi?!-UU&?nvo7xa^qJn?Il0j+D3y7)sfpX5Kh9p-O zU$x_-tdFszqF^*)`1BEdGebHV*eRx%fzfnQ#f1xf)>Fe)w@c@A$mq#S`s;nNaYFZS zgfY>AgzszX2^WGqt~fQ&Q|c5zwMV@^@1A8>o@MKw2LouuL0ZaRM%P)mz@q0%F$Jkc zZd-~PUKZ&|i*%wU-6_6%%E1)+Da;rR0xP#_+sIJ-Tzh^Gr}L!wCXE^^#cj{p(q>sk zx7%ehnbgWZIXU6s!-wQ~_JM5#Lwsy|^axPeL%`FrNpd z#!c{UOvICRplcb;B|T9c+JKow?}oBtdYkXJ-EYgQEln+FU-7U?Km@NM(mW*N=<%lbVYi;KXO zxV&b&Lc8Zxlg_sMmI6wRa$A4fSQ@y|+EI#|m$te6)()?|-eG2PUL9K|PYMPvcRA3H z=;)MwnvuVJfzgUTc>5w3Hd7vdz01XojPBlZZuM3eo)+By`6rkp;Uw)~#xquI$HzW& zWvk$w6Vp`bg_T&ZtcfbMshJtxSP^#!gpJ+}AF&ZoKCZSRIk};j<(e(3WC)dV>Fr3yuRJ%kN?Gcy{EZ0L8@#Qzs-OeLZ7QJ&87}^|zZR2ebAh_sWTm8bl=E753J(}fO zyBL@Zax~9#(ljmej<>>>=_BB_&6BjE%j@g7ptHunWLnVg^m+f%HS#ne3@xJ#7VW_o zoStZ)Fczb%3sKO?z#4id6E2d{OA<5%#~nEAWIP@YIGp9A3bu601f2Se)&&)^4m`s3{M$NCQc40MUp0NylKrRAAiD= zM~~@bUGhBV@aPcH2IXVXO7IR$Go;fY?{yeveP+G+#l^2yQWzYwaxfv!C!k$FS!+R@Z5Ny(^zZ)rf?aEq!iBwoId>zTu4(cCOwLy zG5P+dAdsYr*VBX>{Vf=bIQaQ5=vl=NIveQDDw0szdRnZfQ+dt7m#rL7tc|YC}>Fx-#+B=!zb8Tf73wEycxqMbze+bi1D2vHdM?Bu~?wA2JCDY6z2uEmKkV^jJjoDK-;wlU=?uz!Sg(? zZTl7x4i67GIXPLl8Xunk9#y-R1}I|=mCV>sS{Ko#qDkPamRw|1G}A~h@Z62L_i1@n zGamf2vy7YY-cqo3Db01-=0V!UjNTjwj$fS;VEklR8jW?iNtN?_eHL9cfFm9lmS{3u zoL?IsG~2F?-_PWKKS?#qI8WW?ji0z>O7TrlAY66p3dcNcwwlaa%c$3NzWzv>i0mG_vEr zCNH)b{c#_*ft7KZw)Yl+4o#ZQ(>IIel0`;-oz~b3avl(Fwo9B>b6b8B2nilsZJ^6} zu7&9ub8xM1Q9dxcB@tLnQ-1XR>%9NLB@Rx$=FRth$d{iV^LVyLLPm#-#9Fr3y8OvU z-(#gaWPL+;`<*SmxGa3@_O}=ePuSeJ!RTnfuYUa%X}UtLW?rF6ob0tu`L$A)0i|3i zX%;UtE5k6BHI<6t?kXinlAx!Wpjx^;-3p@9vBA1=X{GdF5tjj0V453p3u(5Aq+_%Q z&kx4@=Hmwx#VRV%^j6m>u=F>#Fd9Do^ea|+uzmRmY>Ff)r;~#2u+Pg^imx6Xax|Et zZGur9jC3({(7|`ZpQsS%D-YN32Ld^dQ|&yMq(E~@(kYN0s1&lqa^>>p#SkQY-fY^`wh(k2J{8JpW3dR=ezo4_0X5G=6{DDaAKtbLRoG8JpV znd7%U9v1OyQ;vkRu{UM7mYpWvrcW#zOXmSE+hj+(K2lG!%=5dsd!eR3G~{HnE-`4S zl%mt=lxYLVIh{`D;`D~^aoI{DezTSdaOd$$PP8Jk4p>nDMi6UI%95uB-GRbtHoI%s z(;TZTC?V-3WQk=(K}TRc>tG8_mMD^L%CSv3E=H&z0^R@me7kXXvY~;s`yPqTpS*DyM5sNMghI!6spWQ=}#L08( z>TkWCrc~m@ku?tFPZXvou+}Xugtj!=AjbIZ`4Ut;tTD{S69@+g8#m6hDL6hnboZ14 z=8SP$t;+*zL>L_BoE{$&JU&G!Fq)*Ys8K+YD2y0#1yd3VgywnguM*4HXt!{ab&)JY zMXkISi+%?!Kscz&TCdW1Z^Q(;m)5xS{&fbEDZ8IOhM^(x=cj9Hm}Hu(AG}U?t;^n5 zPnjGRY+b&H-Bt|EDIJqB`Rau1G)0U%ZP*&?J+Hjr7Y_m$CkK@`&ZQx9N@*ny3=yFg z(=JRSrO9lK1AoFnKjQd4^i}3KE)KBrx}j3u(Lvm}ESI~=*i_bgssagEF{MtDO8#(-do+N4gQ~ylcZhUnb?L*xo)+k=`2a`4mc3Tx4-L zhgHg}93CXaQiw$pw*Q;bD^(;x<3L(nxI=Y9@J@%BxW2W1Zp9x0R_S;SGtefyTg@HX z8ehL4ODx7}@7w2T^yKRh?n8X2J4H!*@CvQ;KX*UGvkg}`h|!i|9>U;piyEaa75d9V zsHH~W^*NBrwhnqiIig~2tzNWV=TM%pq}0*;d?Zmm;YH(kv-DQB;^meDq{ZuvomBC` zci!U7w>#wKh%8;@i%-5ncur~T))7jYa6_Hp3v|2IT;MtSii>f~|w1RIOpD0P9Kz4NWDH9l8j4v{XQ6kX4oHTL=0|q2}+Gx`0&FR7JTys1qQ*{7_ny zudVng^p;EM@e)6TV=b;$l@_&X8GaF$QDt7~n9Fp-co$onBS>6U3T`kaDpCkdkk)$Z1S49ncRO+&esL(A{80GMT})<9B6)DL{pru*$(G58Wxo`|GeHpi~7A}E$as9qWYHa zgh|e?X1ZIpX0R=z@4PDm=K%_r@poH4Ho=HxR})mR5l=RarA0tcyFjW7!vHKUCWLmr zjqaCnIihPBU>o?ODt8f|xMvH;6!Uz%sDBrZ`-U8umpS4UrzV)WoPU;Gv5_C=8I1cF zI6F%oHs5dKySQKCG|esA;a>bc%%2k8Y_B#$z4>mOU)%4tJeFOHK#;b1BHVK)@aiDm zYx6>rhGxCm0O7cPabC;3HN(|BF};mmahl=W`=wq4r6Mgrsw`_8&|kMad%jDzEA)F^ zRy!RuP-uf#VI@(#abpK7Im7WIu3fr;(OcYlW0Sl0zGi##3OidFi7n_T7gNhvcdXhR z$u`&kwjTH@P9l}Jll3PrTd`g*ai?4xG47uC4l#bqR4^KXCyRnI&YA_#(MitUfKXY7 zYq#EEb@dWYzxE0cJ8PV*>>w99=)+eSeTucy`gp_XUDg=qwPO7V(vtA- z<&e+*@jf5CcZK8Af`k1N*4Eef?swn8D$Or{`6&m}oXL2^l}i^f`IyH~2OJ!ac>HY0 z<*yH!O@#i+7MbdK&#U0&5_qFh+{09CY_-v`X17I7 ze?quYstl21JjmxvZU4noYKv%Z=mh}B7!PbIw^h{`3Zvb)HQt!F0nG8^MjSYaRN?Bv zXi=g(kOX)z6&&bfy|F4-Qe(ktC-)LTn_wc>ubBvgL^-fZ6iS@j45md*KMwOo2{3rD zG&SgJE2JO2#r}gQ?BBO^X3l@ROUguq!It6mfAKb_vk6~3|D3Jf3UB_|cQ{#_u(=|P zpC5Dh@|fNPQk`IIinZeARiQj}KIr7-c$$M$tfwuEzlOz7#^#H?p=`tR-_OC8(x|SM z$im~OpMk{U0DF%YU|d)+G59|AWeQ-0#Cp#*pJDi}JlrLIT#4~OPU3)lsGt&eO1woG z9~LWjCp3ebkKJ_EVgm*}CBQ@0mLMU7EzmBmrlTS9@^j?7yo^CfQpONXLY~o~PFT12 zm4v^DCb9l}B|w#@kyq{H{V5#?C=RqwlawS)kR%~diZo3~lhmb5g(5YTZi7Whf=ZMh z8?M|0h4P0+i62i1;^T7Sy=DE@ZQ}sW#2;!^V%rV9NtIjdRXNT~=0n>^sE_YotK2qx zZA-8|@Uar76U|IfdbnG0Ag&2v2zD@i8qFGl7Yhn~sLS_d=2=te#>`K`NSjGG=2n;l|Yq z93K@7PA8;k%49TUXRV9UFghM_{AxhAmyxXtHqV_mJSirCSK?^#xWd1#Lt(2@ZoTu6ZyVn#yQQ{8@%* zTLsR_YmwpK`BaMl03ZNKL_t*7G>Dg#v8;n2vH5=eetVkvyyLd03<|#*T5d&{HD$_~t-mZ)d@J!v<|xnk{P8ZEm;0<=xW?Y$37xdVAAa{c92^h0|KJ(3V#auy^Ww!T zRyKxox+|O(%wg8(TDrNoR z71q{n^7#HPHW^bO42yNX^W#6~qJb$t0KvtDTq`(HB44^Y@rHdlS*biClHW0Qn~QNeHj_5rU= z*7H`rmf6n zV|@`l&H6RpS=0tidYg0ydD^T~6O3)EYy2F%$5Iqec{-iebe*QmX@VcdI4>b*WGIY< zu~=pv$#cf2A?>87wGPASn9b`K*#6;l4tEXL|J9%KH~-^baPZp`*3u3ew=VM9kKf~~ zJCFI|$q8BR4qq8`3cm9vZ}VdG0D4#0Fnzup?$O`A$dl_EbZ%ty_6M9k+eIC_c%W&f zu&To&v3$1Kpr6|N%?P74ChPC8qv^ac2Bq@QaFxC+dwuBRE zLOC`VfUZny-8Q=Q0#=m^2Vum;TQm*5i0GLW2Otwbs0a`WdP*XR^Y9W&IEA_Z=%uic z@o4WU2Tz`2iUQT`lJzc87yZ$`FjZRmS_Sh<}Rgt?~d=m@azx~#61 zLuvPg6SYGbfi@SiBQ$gH#Hu{dBp%!TUVKQ3gz;)U--&=<6ciG7a<>OHLVVJwb|u>2 z-tw@airX;I(lje=33^~-XQq0a2D+WMs+alH=@8#1ZX3UE16%Dr2zITtK~8BeaX@Jx zx2TzG(6HXAiMIJueLBxvapNwkkNCsMl#N3ZIFH%j$rr)_ga#2mD3j1`uH}8*o#$@o zOQlrlt*S(kc-})GQ9_!eq-o;3z41Gs5`|3^HkA_4QzDVqD`ofug(4BRknTWPr=usn z?+JRUg;XU}0KRK5*CqniAgbeJgqJ-C4n*&G4`koYDs1PD<~Lnv3S+!J6$XKCPob?Uu{i3V{z09CRVG>^sxbP6hmU>TK6d0pjEMjBP+CbsFl#NQ*osM`qnNXCY z(#PWz=ahHv-dCV5a&c#s{k;P&U%tVWD;xao4)mUki5MBXhr{ETPnWqi0RbR8hAZT>r#rD~(ws zh_Wb8spMzipTJ6Ul$G+U>ZgXPUVE>pvuEXHUl&^N+f!=$Y5{# zUQ0`*NS)P=&ANl%7SYm9%M|vruJZy8in#QTL@jUP6<~yaWT{wv5{!f)eD~y8?#i=(E-L`TA7GK{H*` z5Um8&@-g1VV`s^eINh=|XUT|V<+d5N&2){@TJoCDqutoA+O>_Y=DkG*c$;@rn?b&8 ztLN`~udPkm>bz*2pVilk@;XcYg?@2^JS9008yS2dxVuq? zLJGQDU0#3h28U0dQ*<)+E)-<%Uco*(VtOz`B~r%aG}bWFImT!%UcAWL@4dtQFYhs& z7Od`UVW*nG%YB3dtn(PsI%QfYvXyNvUcJiE=@iNOsMQU&w$~Y+bjg&U<&?Zg5lJ9f z0Wsv-uygedlFk;#gHzVluVUpPz5X^g-+dR=-9T8S|Mfm6`wszyC=IlDDwmd(Gv$;` zDTXPh4^u|{8x+Ykj;8nd`h}3KZ9s3GbY+**!3$0w9kX}v6}{~%+` z42ZotjWG;|!;+RTd!%Vv13ZhOsJET99_Yh;hY<12eX4}+dY}HyZS?Gv*=oXx8Ig1p z>DnfHtA>G@lBOB`3mv{1?lBr3llLs0EX8O`v6*o5^)((oIH9+F5o0U|+cUa9y~WcF z!5hg>P6uSCJ$HalF+)qrL@7qSj9fvUwCe+}Ryw>A#noZXjhk0VR#&i{1e9R3 z_vit47>^GX5m`*2Ht4Na!kpzoWTUUKu9j%mf zLkuGXe@RR5MGf3w2pQo4#mGC#M&C>Dp$T9{^2etO+*az!0SOS--XeHVIx0AnAI5yp zwZ+Q`>zx3kqP-sL6GF`Os=gt1qJs=I74TWs$)~PvSKujLpRb}e`!*55yVXGn2K%-Z zRm;EG4}w(t>qDhuGS`$ zK9dsvFXxMpr>&@u^HOF1TSuFnAsR$iuvG{{R#I)L+C1b{szOdaOW+F|=#rAw06K-| zAw3@8b!jWGFMh|02W7qIt`Z?h6OzP3&gER<2rvpz5-rdhTsRCopz8rq-`C3Xyb>Wr zxwyanx$%wQP7Dv#wPif!5M@09Qlm(WWh3d(?XQyOIWHO2#uzA!50&`h)sRu1)89y# z+k!2kuNtlgu zFPp+#-=>GlLLjrwDYZAswl1Hxn<8F5sESX^;a-;oRn9?&pyXd_W6yuHHP%P@1IkK4 z1r<{UaiJCwS=SF2=}V=oZ0KKYdRg1ErhXQFm#3L8Ca<1h6O|2>Y+g-opGB2xD!+@; zHPbILg5xX8GIn-$I5;?9GMThCUR+-M8GF@)1AF3kV|vSc=82uFPf(28l;%03JIeDc zBX;5CmDHw@d>*6tEC6R&AGEbsdmd0{s#>iB#dCQ#ftR-STt*YH(2HSF`nbIFTx|wZ zTy~pBT^rD{=-zqCt*47=@o9-fpV+y4mjUO}(q(6YE-fOYDX83EMe}kR=6T~hVB4~1 zzfZrP1z0VTG|j!k&TQg z>m-ClfztFgd-OJhQU8>+Z?7}V_E_KA;&f|9|6A8su_>>Pp3?ImrUL;qy?z%mp>uJC zZ~x>we7^T7!;>jH?_VZ8%Guw2LDzOL#xg53D@u{}R+)__%nC!2u5dcgoDMA}KOxZ* zE??has%EHE*xI>9x6@(&M>fNw$q4VLVM-oF;7<4o9peeT*0%^U7^6s|?ku z|6<@Q7!p=n%>T>Yn}1o7TlbwGAR^wDduCNuRaaG4b@k3gO`0u{w55^E<6~LJf2QY* zf6*+OnPW@x=-4BX6xpIEQoXSEuBxsj_wDU3B7pe;Km;NlA~K8e{A7Ww&KD5~;Ns%q z;(Kpg0C_?bZ?m@ZDq=DsljrEpb*A|`Yn!Ve28_pZ@>H;Yq1f5DPH%A?CAL7GvdCx5 z(u~evm5t4p7>*WPzx4)bcEoV>2|xPxf573ei;C8eS;pDX43(t_ra)dO?a+ri z|84rb{=1H~{4y@ncXcOZ$f5e8*rsiKhRVCFz4h{%babc=za0)wMA9y`^xALsk4#uB z7ABg;mFf1t(=IZ`8kg3P5d!9UMtXL^*>uR}$}U1Cs3c;RFNlO7RXNG}fLGsni;o|D z#@TSljhA=GrYV!dAvza)_Wnc0*@E6J$#^(I4Frp=4j0K3`O+ps9r65=3kE7C$s~Ce z*)1wmaWZtsVF&Q|+Yx^uX8gLdhrF(lodqJYrkc_er`=jip)q?-N`Txhfkl`|BH^O> zv^|a9I4#{gW^|H9M#eFfC{0fY(o~_ksc}x`nX#vG6S1X~GAC^VCVI>OAhTVdKuCwr zo$E!Q3(RBoF2y+=j5*6^y(z=lluskYQw`%A%X}$uou=38v$M0s-Me==Iy&O3uf9S` z$@S~kdHLm++27yi$&)8+ZEbVs_H9--);S%Ya?zP`^5l@o-U)K1h$2aUEvCOJ>CCqI z`r#3SOfviIhgg-t-f_l&jn-Xel8HWb zyb&&P(a>T8`YcTzvx|J#-zCA0)4uz~@l_PO^0}zJqRR(`L|CtIp&N_(v3#sBtNiJ8 z)l$1PXzRF)v(ec9&{Ycx@}IESpfa+sQ zsA4>bT5&#K1sZcYpf}E3Ub`&a3=du6O6~rd-W2FE($$s}VWUb_sgvA8>C+Fz{-lvG z5y+7+k-bT-xZPjn{-A^T%;xoRkpx-`qHc#trkI?k#7V+@I3bQQdhvj`(Q7aPZ&QG8kjfS2|r1LfTlu5tCKf@-5efUVx}IzvMFUKGphotCXl*Ln%=OKDsh8zwFY3L5aI}@tM&H%Sa$n>8 zGAb&a@|&BTz_qG1+1+2}SKYzaM9Wnin{D^&21%O}Eo5jk8Ev6={O3&$qpLWOL$nvH zmtsja>I$l@o?^mv>YxCD%VV@cTXC%mCq_NbRpqTof%?}?zpv`-Y%(mH@?Pa*Oek-O zE$!H~sIRQokCd&CrEYyUH<&&&H5g4em-CR-S4J-BtzpW^Z}@C zVhp|K^Q&n*HvRV7<=zdA`w(C2`<>SLQ>U%qr!y?X@_{HzeWqPm6njFIW7fc^?qG}F z%6-mG#|$>^LA=LkHsqW4zs>$>gzT>oGh}sLGF3w!oyQX$r545E16pgevK!57Wxwi22+V28 zE4zJGU+wXm(?iCcDbZ%a&g~7d@fn+2x7ocL^U1HjBwwV=XDM-~gOG|h-@Z-v&MN!o z`>53k^Ztm_@raGigzZ608fA1cO}3bE@azfGu>w6|F+b;EZ%D7V&PTs|kLmFVr^6El z15K=snNQA4Vjc#|q@xd8V#+)1-q4WYJ6P`W)5-Eo;pK|n%U-8=gE+_dp)6+w= zT3CH}i7vAHs%gv_sS1fA2GJ!+RvD~pGMpz|yLlUNeVgZxpD;Q<;^l9>#Y+#q!>9l7 zjM+#NM=KUfq@=0laPN#<_j&Ss%+c|j`CM`9-UIf2GbGg=HU^s{(Khq^041h?#x2{c zkGHlJ4*njy^)XbJ^$?y5&AFjGZS$sk-ZW0?WrlQBsLnP$Sf|G?qh4P~&xQ1)3l8w} z`5#z{a)ls@B655Bx&P5rN?Bma(qz?zVGMRTv}aJyM^m!#kW_2*PR#ryqnp9m=btb* z9x{+Iai>E^B*<|J8m`^G&g^W&^yHj;4v&8Ekj-!O**Kjte>P+=O?dXfQ_?Q1-54;L z!#FpQOPxrfm0g^w-GMx1#ArP|`$5DrGVH?W|FP05?ng6ziQ||*_=A7N+i$T>eeUm|ivf20r#&G^$pzVYT3zuVj4t7OFW zH(%n-d#{tuXZ-rtj~QO%h$4Q{I*=>lTNgHFiFlSH@ydplJgC^8!ZGvWnQ}RCq!`|uK%pugTHjtxc!@jaJt*pE35;=MLjx4pWTYZ?vQNwU_CurQ(|Ua z;_GthymR=KP7_f+u^Y3Nm@>!Hg%wsByUVN=peV;-P9bca+PKrJb-KR}Sa@X%QG6^Y zvpKugufBBnm5)kiX3-wu-FICkiXWyOqNK-8zmjoG9ubBy@T`qGJE$ zs*Z}#L|Q{`4swg^{9Jr0)Km^RGP0x#+=m^Deq^_MR%R(W!E2-20z!KaTz@$^@!4W| zY3WCo`oqa`A$7v;!D(e$yXbxZAD7etPQgn{(7`LpY&A9(<9cRAr=wJKKvn=D_;D9tu3p(lm(9iEP zOj$5k@1zj;U83F5=JTZm5H~pwv~PXZcopK8yOvUVlZF``ZHD?)`Ce~FSw<->%VpIC z)YS>a##@K?(;1)enaEBsISC6rXKUKLQ6=#RBldj zHET$*hEr5TbZX4U%j1)CX89pPu5x%jV>lCtc!f@915{3)cTmxQJdU|I9~0$0Ub_7f zPrp7O5@U24ldCzJoGi~7o{yO76=oBd&0;I&p-KBro5+$4Y;Nz5PQD4xZZ+)H8Bs zC_Sdvjfu4+P7*r3E^$9Lrzd9%&YnHw;@~-2pg1* zYO_560<@Yl9i9@4gl;TJBy>BP#bSy~7f6g8Zfx21HD_ac7ZnMn*_3X7z^5NS;{2@3+Gd}VlT*?>GjgUhW@Gv0j|BB))owhv{81ED zoN=LYn)FRm{*WFDjUj(LR`8LRKS)c1zG|8${rc+d@M#Ur`B%XnZO&LfDW!`2G+jQ2 zV}%grkg1}LwFYQx6q8-vER5k833FJ+^ywjs;{`wboqN2a1P2SnP)nvK6W;%eUobp7 z=GN*OZ(ZBsRL^+uz1KNB7;Q4MrYhLCzm5| z%V33w(ApfTV@~63=KVjXYH|=)2)O^sEByZN|31Uv1)U_J-|zFSZ+(j&{NM)+1_RdC z)>vC#XLom($z;OqJGa@`T<7CYKIOsnyC5tZR**ekK z9&Mw|l+zOtcLuAxwzbaWT=K2AUMG=)r;krrUxob$vaDSGT6ato@Ei*JC6wC6X6Yny zmO*Xa^no6F~es$(`$sm3q4=qcYGD%#|gT`jJ7R@rE)FledKw;a&qt6bM-q5W>F zZ>aX{L7izE69{(D$LRC~*@-bF+!%Xd-hvod2nW{}Dj5L#$&dA%qc_MnSp@!CDJ zTX$5-vpnz}-rCLOxa}e|8E!9Q@AP_~Pj30X7R1Cz6`8}>B%})yDc@fin7vJ9#OmrQ zqw$zDowL5R&i3{-vdru`og@kU-U?C*x^WjN1%uuIoxprPC(m;Pnn-DSTC*7??DSUY zSbQzrm{-a;HlgP3jv}p`f zlkbOF_7MnTAJiBk>?6BTwu>}GK%686Sn|^nz!TNUqs>_oN+AQVt#NfD)+KkkW0esN zG*j&aAw;E}O-5tAY&Y@2vd#{rCgZj!%VcV-uU=N={VTlJ<_xO#Th+`g?s%_<*rWWx zR>^NRp%1hokaw9@f9`YJm zyZ*CZS1X2YweLd?4>t+A{H}fnl4x|&h}Mr%%6-!UVLYg@hp+geu!Y~egE+6P3o4KA-Xz{%K# zHqt-{vUEbOhHPHH#&|JjGS8VWa=!ZV8A@EI--*aZbGmCC2C|P3DV>!bgSAawzP82D z!84AIo{{Mxv-z0Ay@#xCzf6)Y5HrQ;$r;-#*LZ1TgZW62s~oMf!gw;|X|M+;_&`+v zkrs>&CY+z8bg%U|>K-CyC$PE0CX#$MhVhVQM23wokOr|r=Pfyr?^qlSa2Dfi) zbNcv{{5)nOUuA2t!tTl{$EzD$oQ}-YYasJDw4-{I`|glu|`Z-&}XFSmXja-6z7JN>*u2o{S) znOB>GaVidIC@c-FO_xh4SzljaeZ9|YI_32Egw8nSoy|LZ`{rGK(OV@}$K)4~KTk{m z03ZNKL_t(@j?ec=BE{XCo4kEzhv~IBH+FCG>BnE9;~myFG&gSE;}?rP9=`vKXsBSE zv!=WBy6c?fCoC2jLgrlGzDCkX*xx%e2DcQ%u_Ux}-an`2B!xMJ{)^8)<4^wG9|I<8 zcQTo9a&m%FR#uc|IvMlRpZ=H!4_@Q`{rf!If5!Rg8N1s%M6u@L=me2NuM?YSC9UX4 zL9aI;iaU%(6VgS7%2KZHuCRN1jpv6?IX^w&-rbuV9e>5$8#iEaorhn1M3!l5V>$;& znTp7u8lEbWUP9ZAsmN2&AI-R^?kw`EEdIKW9c9r~yQE@M+j+M5Uv2D`zCB+j2y4%< ztbB?(90{fX-%cytK{XzD1v(yBKvBce(iB_d7v8_pZYX>q!Z}G>?36nGtEt+MPOsw3 zs$CFf3Y2^Lmu2~XYgHEoYT7Vc8N}uHy~eXo%I;jQ!|^d-TQo_rUMfixy1Be>ZaC+Y zSv}#EueGxZYI9pzuGYGwU6(F=*G|8ULVJ;fHR{aDpOP7VfUX9#kQIsaS#2DZF0XJ+ z#Wlwr+$J3+woCv0SW@Rwn+#_#(jYXm$ga*65!pzQE%IXiS9#9b>KgBU?|0eU+~gmA z^-E6APTAht=9}O6CZo}WCr`d+Wo5w4-IrKdS>=l_zTokr$A*41U8Q(su)=E_TP!A1 zQY^ECJva)1jN<~A3+#b(2(ve{)Ru21Dq3rE;E}YasH;3L`dKS?ZK{Zg5DK%EiVaxM zb{yr(tZ>TmtWXvjjf`UAD8jjveY?M~V}#je+8kP^naor6=QB=J%0jw>;wn99?%9rR z(x1M-)Mco66u?noABeD%M4_Yrb3k69Z2hk@$_lv~J)AN$fW*!yu z(^B8K{W_M7n6PumsHHZ!s7^{g6bi$ec79nk*x~^3>-5{wRCaOCO~92&yFE<$D&wI^ z&R(S7i?NPtkK=>6Zla^f81+kOqPPB@4>FpJ(@?sKljyQ>a+!{LkyjruO-If7>Z^x5<_U{zK_`yU!zpjPeur0Iz0co$@Cl2(IV&P&bAQ4+@4f=}Z}aoV zAEU{^J1nFyqHVuMQW6QbSCa+X0`FRzC=!)}(j-zNBM>SDy#Sqno*|PQF5AIYQ4t%)rV`(>QE&3W(~#K=sJydUEab zoqcuv(FAsz=xEY2{31Wp z-(08P9drHqi2JX;#y@`a9y?nP*jVonM~YYO+~fA0`@Hq$Tb!Jn@HhYO-_YIcbNlvf zzVq$hVK$%fAO6GtjMj>+?QK?G+Tb})g8cmFw4AiH-BwrE*xkK>5atYlt?g}AS64}r z1Sung5Nz*kA*5t;Ym0BZ{S8LrF^?ZV;pvm-9G@Jcb;jA*m@H4o@-Fj5#B?Ir*}Bg6 zfAFukxES%XpZ~eVUl-su+aCmY|nx{agy#Zp` zaZ*03!0N&`a}^xdHYQ69e0d7tm^NJgA{g_kHe~&srSWVY7&=9mE`E5Riwsuv^uG?pJ{?G>Vmvgg=diAiRwe!NX z+SBMoQ&7=TYwTaQ9YT4pf?s^9?JMr&`qI%=EwgkGJ1?fOQKc>;Jk19bjZ!%!crD3t z0YmBBevr1?HwXl|&RCEZ9}_MvF6{g#+1c6Q=FJJ$QNIH zju3)3-guMk?Q1-H_6)EG6bXpXtRhg838xq5oamgn)F32@!0eAK>@ssfaR?Ij!-jNi zzy&DJB$e;Y0mAE9`|+e+eFz4 zN@sDm+lBrbvx9wxIweITisP9)$?)79mySnIl3pYQ9hx<PpBb!UtJx=|z=HlhN(hSEsGMpOZ^%MrRXkP0vF7@aqvm6iXAQ(`jWw^4|^7 z==v-~d!4SjBe*_+y(quEx34hyH}lK??etR{cyA=;w;Cb-i|sVl-&>+hyFcCp<^31J zj*#CrUFL|X-wTvbb!^yMqB8M`g8U-N+Uq0xa#7ri(i^%i=+Q2MY*~hz%5IXEkb~;7 z_pjnCuIrqT^UEL4ZH~>laUG(~{cSo>#{6|(?jqb5>bef8^C5H?S*UJbkJUe9)!Xjp zy($>>pEn(lc3GvM(6>z@W3;zeC&M^O}%i%`3p%5(QZfV<-CbkDR# zD`mGvujui$cElKkRyik&#L}Y}ADigPFQ*AA zGrx`FO^`0;oSi&pba8?ZUGj9n;odXGM+u2cSV^bEs*AKo&}!j8xUwkA`+~1c#CI=A zc;&&%eB+yMvKSA!*!zTRd`y%dbA5Y@YikSoGG{WMv&d$ErXwX?0k^gkcW+95_RBNm zB12>f^=zL(`X-x$6(Rw7u234=ci3Z#wTaA=5-ALb6atijG|h-2X_rq6q%_NWv^_B% z9GF(+6rdGoWj+pLfT$9XH+~#*eI^mY)}vBl7D%LVHVKW;CI-OtyRy*}3UrFZoPw;C zLH$CSB~nr;l%A362_QgfVi}?GF^ha;+{(^FfGRE8T559OIk~GZEw8t+K36s2C|@u5 z_q5g|iIK}Z&zJJm9h6Pu(9!AkMi1%T(A?GLz--coetAt8IphSmY_6}D@7i@$oa^RX z2L>dXJj*#aIA$^#v6#+?;~wvP<8@wnFO7%BEwB{#WKy#m}{xJgqNi)C|vD!-VjbSmhEWm z$w_Hdilr*G))d7G3c9qNi-eHGQ4Cs{?UV53(Dzc=&&#r`MHl=q8^009GSk2ot$d# zz8{TQ(kK{>MgYaWVQZh*mMPcO`s#IJ!Am3rhzvzWBBcF- zoS~VSlYOPOemRSdv__Gm($oV+){UwaS}jm2MKeZPV3{pIL;{pX=xyiQGjuC$zAD@O zfDB`8iP|`1&QH&H^6(KNowE|bU?m|LWk@+kiYXnb*}Qg*(Rj*iI)z*l2|=7J=yY=W zJxNEx=3tHQeB*V#e*Bn^KKuf7RLnmzGE0JTotsET%mEtOqIoGJvMi<3>C)*WWSQBE zFw64tPtE;QzdCqV+Tq-Z5L+ZMy4J*0(O-s{5b%n-%D1ZH4h#yUH)XCvPK^<&1WPp8*qFxX&iZH?3Ob#$`L7f&xZ{(pYO zy?X;*fBP;Ud@v*XyRZ1>?|hHr^93J%cEM;OdA@hR55C`JWo?z|VosFAeEs!f{`lYh zF{>MEL{Y@X`UZdaKm7sMuJ3Z~+BMeK*GT6_d?ognv1sJa|LAG$ONmI$qWrV7Gyd$) z{!e=SJ_tBFKV>?ZaPj9ue*EJf6Gf8Ac*^nd34ieyf6044e-Et%hi5}3I$`(qf59Sy z8#iC#==l>KKYX8)zkS5N`8WR-PCnwl{p9aavmq)^%<@Q~`Rdt-Np!*O*WP6`Jm+U0 z9MkK+fj+p!>B%9vT1Sc$rOh(RCG>6KbV$B^c`bB=c~Ck+@a?1Ku_Ukuw$|@AV2BVF zbE(Oua?c89LI687<*m2ff9Hkqs@)4W)J|WoH@3z58N!Bb*rn+($0ZVL{0MD3bX80k zqAOJYi`Zptv^UisDkDT&v#gb~uW$oHmo})-c>4)gJULuOYK4YkV^mMZ5eV%f11ZK* z={TeMx8hdxD+*?*wp|!pS+>!UW9~$ST~WU0T1mo3C0-)dTbN%{t-gx5;k|g2Tvu8}F6SP*9{t$5tKq@6uA(=&nS1CZ`g3h#EW?#DC zgwK#c&YHJQ~Hg>1X7^83Xfla?rd8@eLk!owW(6v9HGv2J9?&=liWS`=LhlS^#| z-;1QH5O|q$sqJ~ayvr0_n_>>V+xDypsE6nRv%k@FIt@v# z9^$zJ#Gwg7EafZ9541y<_b)R-u9})Ak)}y5ls8O6y+7QwF=AcbwmRCJB5m}C=~ zn~bC~d1Jk?0&5uKnX|5Z`{reUuJYWgR(Q{^GG6P?LSr|Sr>TxM8LPh^qT_P@hItcm z7&aNA^?P+kpkHtGARSdHC&Z_au4p@%xQR}Gxnfg2AwD;a=a9p&p8Wd!a$*ZC3dfQz zjrkBTzN-9$oEi0Yxw`W_ujq1@FXT|DJANMNSIV5MT4|Hb?{u_6 zffPgt5-CAC5T(r4;R3t-#2_YZN51=l>%dHbg>8A}w|Pfm9HI%Zo(MY3k-1}&p1Dd(DR7z{@`1Pq{{~%eay2b z&&YJjEbXJ!Hrs2r$rq=rZ?Ev+?Fanw!;d&PJO|N5$Ov?9&VjH%(JhVYcDwX?J<`Pj z1ay*w)zwuiQ+ct6c$LQzDNHm#5}8fmRhrTVRuD|lq*9=Z<@r`tJi`S^aiE5@>x7gR z_FGbB9h>&VclUwKMrB&OiIA8&v}LN@qt3~Jkr4yVC1&qDw6-6wgeAfVj6Xj&8GKE6 zsu0tF-fc5%n=q$e#$+;CqN$E?Lb|$X-tzN12Vdp=TAS-*J=ErUs(&D9GxnQwLmLm< z91Ed#`fYQvBW1+S^&8MzXXnA|47PUo^e6A}$y3E*C8zuHk9csq!}I5l`0Ecx^w#dN z{^kvi9(}^zqtDqpKIb=|pL2FTX1<6xUd$O>+a~LOgWg(~=?oUBVmeFd^;cP2+hRUl zkY^bh=ybZ=xpSAZvkO3S?b;6AZkMg?ZFYa%A^AMDjj8rY!5?Zyoz+Q_^3aVX73%a3 zn6XgkG~xX=A1jhv@lxF3U);RG(cvMVjwhTX5t%fHBxvjhecYHbWxDdM9lF$Cu8yL& zyn!dY7b|%R;A}F*BWL8nXfozk@4rtJNn{ieMG~zvPo6$ECk@*JDv*MMqeCJgh@yl{ zbXdLhDqYz_b$hJszRbzVggjp*6J3UBhv&z0PA_tjOc09%Aq-la=P^^&V=C9U5ODvU zci1~RafFN?LVAjJU#~+L zw6C~dM*X)gCIpI!x}vkKjMm?G@A_k^q)AfWpR$SOra7SvL;tomnxEQq^i|Sj_Io|g ziE*!PO z;4-as@zAfu=~b=Ev8$=pD?FJJ8e*-HfAv&zS9XQ%rcZUHs%olE zSxdMkWCOP*A&8y-rSRUCQbfTTkI**7?Kk8yrGMEZr=p9#&*sCLlVdm1hQ%iK47feR$_& zu{2!5=|v}x`a4aAO$juw&No{Eu2-tE5?*Rim+=>EQ@veHbu@t_6RA^gpI-V|Swin7 z4p(mjJhn*Ct|EWn`HL7@ zzR~0KyWIk)EZYNn4#YT`iwJ+MDLizPdC(f|mU*MiQe`DjQiHIkh)U~|)OP8Ky{FN3 zZ%K=Oya`BFMz+xyZigKqa<0W4zO}v1y+O=s7u5Zm#ODh>8P8Z~O)eDXJFtQ6Zmx*q zE=kfsE5%x`j~GUr{_-iSD&_Q(Lr#WW&>K8_v`^AsBNeNx?!3(CY@c6$a>2>$Q}zyq zq?zF0V#r_r;$x1_7R0?>gov3;r^KC@BuQAzX5{%4tuq9Q$!KCuht6{b{XV^Jmpsc@ zqzkevtBgwnuqBZ&s}UllxwXE^-IZ0gJA!+<&*>;`NNxBfoa4m4D=g1*FRB9Tq!g}agtL(lLfZ&U3MY{EVEghMUK_J5xT*=q zxL<6Yn_OH9q;hvmlsUWMu=X-LJCqL&Azy9>krFV z6M=2{?ev>)OaELAx*Ve#$`1;@+=$8mT9*3~uW3L=W{+O2%>ffa2%nD3Wd;XkGx6iFxyF?Kj9qch3PubkL#{DnJ>+(7FgsWMw#NbPh`7$+@Z6dlglG@mx&z8a?Wb&)R>b;G(1 zGhJm)2Pz*~U8--Z6T<43*>G)otqs?BZKkK^PpSE4z>2%7+AAgL%;4mTJ=9b-B8kOgT^UwVgcImYi=EQaP?KsDJGYZgN9c#kF>`#OU@>KF zr8tU_QDksp@iEM8d1M%&p^Uh+I7H=U%cE|uYp!v6%wvelFKn6Gt?n_h(d~E5iSRl% zJeTEMU1>?h9)ObQ#;Fc8M-ve+K1)l$IX*bohQpq zA6LhV%5}&{e3AAwmFfC|iu1WS65Ds$XsZ3(+E!12UR7r_0mo4LnsidgVcG#X%LJ>cO(vX!d*NsGD zWaUddsJEf5&mE6^!0Gd?DYfauCLTyBiQ~8u0b0BdmDB#}sV)O`8vJVqcBHwe7Ewzi)948Ej?^eE zJ&sX;NGa^zat=5aCyF}FU2gAUrrP2=eOZVR$f{gX)dkw_S6MmbvPn1ibAZM9A$;W5 zq6 zJbA+Y!9K&$kU#y?|BcPfE%x^gSjb$x|j zx*R=1f{X-OLZ&sT0F6T8A{13&SPHF8JyIZ4iJ!6cmoYw?!o%psU}YHk7gZSwjT4vh zJg;Dm3Rc$k!$a2Nn&JOF>Z*zF`n92SDzxXeIPIv7lgGBK3;(|^pf(e`zfcVwnH{fd7AwAw3hK!$0^eG(O#RTa;xR&TDFKD-ZNC&04Ff9GRP5 zzEb`QE_v6&zd?8m`E|D0iETRjs<_*~AF>77Z1fO4q5EyAjsKU`d08q?mdBe|N_~y< z@D-3N+!tOc6&zW746pU42+4rcQ`((0{$g!f8rRx=ToQHJx{>T{fq0s;`k3dvMzJY2zf+l9hkeKI@&j6Ku~;!b8>$Mtu*?I~KH=E` z?o|3DPPo3iip5X6JZ2&uEv+5##@L z@xKto5+xy5IYP)nw)`-3+6_n>MQn2_V9UQb9S+Gz9N3zZ)-{KE=a zSK86U+83QHf7|pT6aI_UeYLzHSzVeiE&(iI`fA2=&9NQo|2BYh72x;cG=}r}^%cf_ zt^PJ0bd`4a<@!#|P-;6h*w-C(AnhANe)8$_57}@AlHVUqWjMP0vF7qbQDhHNkriiT z)3~g^9~##_pX$5_85vFG)StO`q9|fzWrZxum`obA$^%cc81)%Mz6O+x3|XX_9oB&{$n~q zlQUyJ9}x=^y{B}aG?N@pB+0G&Y_0E-p6s)BF=uP%CdtYM{hs8@N1qd=!(y4Y)*7W@ zzE~h96S|$aaALa1OfchHX+o(>5MJl_3jE!)SR8Q7#Xl<5+5645^|L_CixPw5jNax0= zE$!^mq3Qa#=FHSu*+?GDI1vud>7X1e@l`usnrD%QPHeW2$PwgGL@Fhj%t(bo$POa6 z2D&u1mC}$4jrlx;+0sKorZSXLBvEW4f)E9*rdeUk<6k#zqtYhp6&chN4hoG%>5QZO zCmfxO(6Yy~I3b`Zm8Hab%IVQLKmExuQbM|z(@7%s9zEpf@c$s2gPx8! z89gT&Jm$maACr#Hi1Y%XG+Jqv*c@n^|GPf6+w6g+@g;>U91aybpy0JPR`K}bq~HlhEJGQXTvFI?cylP58Bn54?s2Y=Dr%vcwd@SAv-HzgM~pmtB^8z3lK|6h*Cc zUuJu@z0-ujeVRjh_%fb__)z$20)N|REh~olnGcordOx-OPY9njrFy%9*d9BTdU{=B z=AsAzxiS%og2Xbx0XEmFd&dMn_$lhk8LYNkgq}H&{ZTqU}AOhGn2$ z&U_9)7Q*b&7oI@e`;Pqf$jG{5AXL78FOFm4IIb)iENQ4t^lbpje_p%W%JwGk)+Ezc z0dFCBYvOgdT~`AjRb?0k8C5z%z)7$yu~(ZQ%W~Vtl)GQ&#ft*1dX!+uFlaJT{k;C< z)uaPLMsK~2%jB_nJcip|KlwH^el7!)zOhk{6m4s-pQmoT`0c&Q*l>0G2AofSsQ!9e z{WgXoxSSJqHk&QAv)-=y?>c|oU+2&*Uux!42(Vrz-%a$m>w3GJWV9Xu8>+APEljjv z9!bv9oaZNF?hIDwuP2;OHK)^*Ic9mbkjAjcbxx^sVK^Q#oC~(M20S}BhSNi?Pls%F zyBr?QczSxs5wLM*1uZ)4?%rg(x5A^zoLDNd*$G*CfffoG3#JPNIzo0j2$8V5x< z@tlQ>$h9U@S%DjUzU4|G(U9hBcN1Rg4Y;ir^m4`BwKXE}{aj6?>@4GJ?9@I7Mw?En zCsoL`XCl?GRg(j@uE+h4Lv3~X{pRzcO~3o?^79K3-s{eEpUq?TmtZm;^Z4PXJbS*6 z79FzrgfyM-<*%Qy`sFrSMdTyJ@j=G?a1E^?pU=7IC(Os^%okH6f?h27#b5nr&JIq= z&c5dS_?UDtiX7jD}-k86hJRwUkXp%tteXJy1=Ola0n? zlL#q5>KwNO(9qjFS2f{wKVL{Uwc%1<;u7N~b1T6|0||qK$<49HumAZ?AC|AuF`Dx=bS!c8_%BW+RHpLTi>7 zOUc`fjSaT8wu(LX0q)+t%l-TJNs@$vg9DUOEEWqEiv?0j7K;TJ7Z>DtPPg0T#?8Cz z?CcQ72?E%;w!_`Kci7q4;jOpcqSx=6Q{c5A%Q8m8As53Ti-muPo*B=@aChTe+4)ys zF>Vd8x=yqFujp34&)t1duKl@+*fb6~*AJfj#jfyTzbjFHM^x?!u^cvg)q&jhJTw8n zNcmSWaIZp7lc9JOWqK8dK@%NqWj7hD!LnRCC{>#;T$QY~GW|AQ*47Y63b##rf0t=* z>-XxtI&Yih;<7fl`+1&|rYT916wzVehjNG0Rh4&{-%K>ZN5d^;w#iXQo-dP~IzVco zzr5c*DYh9-%@dX@xBh$?FogQB&G>Gk>qXL4z>=S?0&?oleZ$nh*W@(vjX8ge)qh{L zkNy0id{+T?^|myfa_-;v`>Xytv{#vX?teKC@yF$@*VSa`x5;Rzoqm4zPL^eb6SGcl zy?@&Db&~-KRK66~Q^+u_KMzIH)$4wd`Lbc+UO6j9qjI$gw_|{ifxcSm8;wa|nv*!#aW26+keETl_evfC*o->`z=)?(|8=EAZ zgvET$a5yZM9#+PTHJpS78O8Kg`iweV-a9?yVl8Ivwb#&hVxl|i@bH51Prf3Wn~xw$ zYjhXZUfrShV1;Y9b~t-N*u(y<;+LY z`c>@GU_W8zreY%;w`AT<0#XW;&e3^FqLo?xEEJIzm{>zWWhrujDa-Pq+&dDCBQ(*l zp#&=#fI>vrs7{0Rbf?QCS4{1S8zcP+AruLcSPEpZcXR3E;F3PjTGQ!tc=z3RdHe0R zQA&}fDT6_uwY4?0*34!zzWwcQ^ZM(rGntH;&1dv`1NQdz_}R~X#&9%bdwZKd{KG%w zl~-Qj?Cgw-iwm+Wj(RRLL0?sRtCrd z%eliqFH3?_Cj{@8ozWO{SK3Z4lV6zCV*$B~1=4t*(GCjZT-H+9FXY;LDNwoVoKU(7 zSZhlmrFB_qG8)}8rx=@@2yI5Ee|_2D^*g4mO)u(13DFobBx@O#p0%~VK3V&;US@Q( zP+bUV8%Sts7fVr&SDVn5sL*9(#B@4kJRT#2U~6lOZnx{v5bn1SmXA`kKLvWc(W4yWk-XMSI|^?B1c5`|U@(O297L{Y!FU*&0=eAfYOeT>$fSicS5 zo09K1s*jyEdG?=$WUBtpcW%}rbVB_TGXC58(eJMi|C&Hk2ynV*^(d>$41jt>mtT(W zuyh|ETy#@i&g&7Ke);vW>i1cl4^2GU+1WvB&C$^ji^XEedDt`->%6c3*4i0T>xL zbIQN2FWJ9r9u3K>-#@)x4=E+%@wj-uZ62$Xqm6?hSAr9*_kd;Uwk2Chtl>LiCX3?1WG-G(8NqVaY*(DZ& zd=|45Y@wXiu{4-T_MC$TYY`j zM6(pa?pf9Ru>>&7q5S7pm?sL})gqee&)sz;3R`PL6xGHpMLD4|n)GoSRuoh}CO8=j zmFw5(JDL4e74$1MmV`*BV=09^2rVDE<0Wo^F<)A&gWQA85CMmtK%7~kd; z!UiFGsg1@E-fAoVDrp%b`?gFo|Jv#+o@vjw;1z#a4>t8%BVK61=2zkIWj=|^Xt~O> z%WMfpXVX?DSFNW$KZNQppEuc_O{w}3GL-6dhU|fQ8`|x_mgLKooA=N}LelN^=tPo1 z5_1h$NJ*y6oR5%3^C%;UX8E;3TSuoxlcP~qQmv&)tOPqU;(Ob-*;NU-S`haWhyd#}XZx2-Zj|`a;r?0xfd$Ax>+1YG$cJ!Zxo7(n!;k4?g&S$6tR< zCrQ}d-Q|Zr`~m$zpMU(v_sO%IciwpiDJ38M=0lE;kLYx|jP0S6k&IYfS>fi58%QDe z^2;yz>Z`AqPG`LG$^*8yw(0l!eE7i!oE)E^az&bD4DCLget&7aNL7XXcqHJ{JvbUKX+yGd63-&b*jl=|U{N}lTEc$tj1 z@hS9tdBPO9K5C0xX{*2PTne?P99!VPRuv@pvs-i9nw(G}S*kw|(H`pO%O>u6gkHVe zdJ4%)-MIDZuK%l-6MDB^kJfrAiYi2x|14w})p^jgG~K7CX<3hZ=I=k~>hpP8-;dV; zg2*}(d}pHj9Ys;`+b_Sa+w1#l`hYnk$69L!g8|#y+iY)d^XaFbGM~?v0IbhgsARe_Qz)MUGuIts&PCcRD29F5~%} zh0-Wu7p#LKH{etlYgg$Utx|;6q>CxDOwrGCE;=2OYqwdaJa}A=87~jCq!zZiac5-S|^VJat^iTSCb)ICp6XT);T3StIqxMRKm)n6MkkTCxppiN!&rOt{q1EXuN@o~m z%xYwbmAwD@{alV4^G`&k*zxfR@jT<^y}Jxn2B0+EOw-9U_wV0hb#^L-KCo(9PRB9B@(&T z;cStjVnH5f%;Oo84b5}~=A#9f&Kc%&2C|FJO>axv>5iPxeDCi2^K{)*W_?$>cD1JOf%d&%T$M@N*p-Tou>X@nWdFwsQRTUS5}Hclf~yzB9UJ_q3K- zm+t|83)@^&YK6dK3vI$GK)mP6Onn&vOHI>&!cRiohWq?mzH-xaZ zchyL9{l3o=Up|`NzpNdhcSY^FwsG4ENs31Fy4<>Ti(7YZlPgemuG89lM9Ec#&drJ6 zk%A}_$c0Ad${cQoS!JW_Ugu}H4o~RzA|fJKNsI>Ub~zZ2S?zD~>Po~M z$n89a&RLa$H4!ru8EKxOd>2`1LI-*3`1t(!bMD={$Gh*o%i-Z6z1%pcfqx7w=LGe<+)J}=UoVa9gwV}(^+ICg)L zsgo$VYH*~+a^`Kf>cFwdom{HDGj5NwBvxYk#ST~Xs z;-F)5(obYIvB|PeM0$Yoj{J)AlMRL#Sw*_kaz)I-_U7Vvd+=m!v&{kZ=ylmTuL<7t zS=tJEEQ<9QfekO9_D=t*@v&tJ&MF?2!#l(`m%)=qDwgl$-}B#UPReCYzVfq0Q`Q`B zHD$>hleIDBhD|Jp5dtQY3C^58gR38W5QVNIaqu{ZKn~3C44_GEmGf^xHx?y90Mr>; zDsNra;nJl`xO?Z0e~s8Afgm$43n)%rT$sqiaWU$dWqc>xIKv z{}Bi?0xt6u@9pmcYnynNJKXgFVFXwOA>~d{wnq9gJt58jFh-*m8|xb&YS55F+c=Cy4K_E|r3$QTF!#wx zm-x5la|Ofu4xR4=<9qwK^Q&Luljokmq3Ik~KKu}F44i-EA_8}~yL|_Ke}=oid=F2a z-oP<-xOwvdPVJ50-+O?YbH>NdUcl_q100`CBr20xoXa^jm*@*a_YL-1UT33)nzJu% zo*_3eB4LVSAdjlmiJ_b_BPlq2hHaK$Mn@jqQ`0d@n1{ZXSE)PH)-pR?=PcAF4iuHZ zQLjwx)%2MBs?A9mnKv|!WpcU(f4$8n&v zy86umdCIny&EYB?y4L0@nizuPpw~g3Co=pj20N5;scg4&PS*M#T0^3*EzZ->RSPUh zFZS1Z)FPe3EO@;2&K2DH*}E8dj~0Xm4pQ+)=u*^BwIiHE>m0^SE2oP?0J#S3sFf34 zL>K{>G%e<{IsAAXhYlYCc6ZQBz3_qH(a{{68*7-IJcQB42IfH=_kqN2UJo9lZi@My z2el(OcmOWu`t18IGG{-fpoinfj^T@6{36buKaczO?x}9z5IQNc**FIZj3bAS;M}=$ z7>~yI5C7pmDjppO4&F0nvkss8{1@RF88>g< z#?OEL2KMJu(Y4V)^!E_ibmaH!;aQ^Vb-~AR*r=RKj?E+oe4LkTRuj#jpo7xTfdNpE z$O@U)#%MW{KhBPTure?1GbtTOPM8lLK3oK50k*{>HMQ$;oCD>13r0cq?yB}{{gv<9 z_wsmFk6}P?)=NyQ|051)%=n0NYtCWl1d4%T#&7CZ6jRHMmFX3|XVh|pIju7FCJMHk zHp%i#n9wr^pxn$_u*Br@qBF9{6d6;pbH$S(P6f&mDR5lm6pzL=B)k2JflQIe1{fEb zMwfAd&DoZ`KoXxvkD2F_!f*~~8p34U!Zi-#aT{G?95I~?EzaH*Xw8UMv_~__7I(f| znT^J1m4i4MNyie6h&V%O;89VO{?oD~y_cJCJ(Z&m0gIEF%}Jm~#SAxMH<*H&XgWG< zlO>|xIVXYshiDW!HF``{OvJa5Q7Zg( zg_I*)`R8~ni4q^^zn89q&1{Ab|Du+WnyXLIR?=M6%W$o0) zZI?xb)$msq2LXV+y*=E$cUPP-(Xk-&6qqBAaE*f_ah7_m%)L;cDC21X0J2Cld&V4E zJ3Bkr+uaqO)GW`vmyfX~eS~z)VCJ}N?dXTSUz>v=ddT0&pFPNx_i0yjRH>sFAwcUZ zi^4^v#MAzal>}B^FUM3Jv2J$dd@bQWm#-p|yc(SzE_4`kz_CN?Xu5rj!Wc8>z~pr9 zHQHAB1(DH$v@gezWjukUNCz`(G$ddrOo>!3sdH`UC~|)-iIGq2(P-4$*aMr~U>pKm z2$Fl#5rS?Umbgv~RT$c|CERu$o;)0`|P07>PM3FZ$n0JmgOBP`16Wu9NN*q8tVQ62pPZ%GDBKX>{K=pi zqdN7~PXz+iLtNv~b?VH@a474EGMTQjHh;$WrOSA|Hour(nN*kQnvbqWBZZCiNn(amQ#eE3jw{0OcyO^PVx zbAuB(#QIJdoq@Hf|R{<9vZw`SPBx{b4Z2s>%6=;GSjvLIyo{8hp-WlI z%sq=j&`ue}<{zN*lzui^$#uw_H}t_+uA9_kVScUDcM6-WqA80^V@ChAx*i$VcjZ`d zEe%I)JO}x0`TYtG%#S+2i1LR z?Up+B>0fM_OFGHw+#X@ z_GkOJdHW{r-oJy%XoShQML2l^%pSYDyV##i;he+V&*9+(FOC<8#)7F@r#Lnlt#^CO zunfzf%IBUNgTcsYZIK>q&gzy@)_jF;wPIeV@Yd5H=FZC7>?t(a!Y&tc$?TUA@z8Zp zqifl~hM4p!r|7EZmKo)8Bbz@KW7-AAhyeB-MnnKfa#=DXiiAjl8b8e9I3Y)HO`{61 zgiJ@}*F-Rx(9z>%>c}L`=hcIY6Wr0+Not6Qw$U?+Qu`6a)PGPVR>u(3V9W)eVNdr@ zqSD&KXNIfvo3xu0q|}x`tp#-yJ|Y6;M=*1TAobgh%9pO=5jm`FtjA~&0@U!9b~qq& z_GP;el#C|pL-5OK{Zl#u2sN4`4w+S1P0WH+iG?y|Lr{p04s|4C(Kh{y^Y= z0BAv&c*3#ub(~mR$0&rvxmlEC!=POaMTwiqSFOCxeHy44ezmn+ji7QvZZgK*J9iO6z&3UG*{|Qmt?ez`ym1rr`5c|^u(R_BKmPGgaOch)eDJ}Q467N2 znPXZ3;((2Di;csZSlijb`9p{C^_QQ+#-ZCdf9@mrJHCT|_oLTv`|efDxr6f#PaZpo z-~0RpJn@lzJn{5#ymxp9fAiiC@rAE^9v5FYgSEpBSJ$?1?^i#;6jC?|gm|nwC!J+H zzr@$F??YE9-}{cwyNmT{ho*IKO#@=d?Hn4ytRXydgh#4Znr++!!urS|j2p~% z=jei$6OAlB0AU<`nV4hEQvU4i?BXB);d^-JopX>XkA4)B z$pj$;T)BK1>+2JI>|-Cp#`?OP`XPr?r%qsPGQsxNL%jXwo7mpohI53C$pjakdJ;Q3 zkMM(k{66m8zmNIcgTe5fk9xHE1DImM7XXaLoUS2c^P*3iOd6nvI=P69CQ8pe?zL)7 zwb7{s%XMOeHGGx%yGjDK;|Y~5ufgj#W!%2jSDlr*BfcF8N_wX7;Nlp^S6HzqiEvJ)n4vq-(`5g1m z0ZvjCaF7T+W-&;KbSZ^^KoCP#or#ji6#(+x0Re`{=}w462T+VACJpfpZ1B6N5b8I< zoN+gqNd2cy34&JOwIh6@C5c?tP4Ul7IQQ&=My*_W{h` zsVIFBnt~CRM=o-OF8^-VDo19`X;$mMd~eykgYe@#BFdD}Qc)f1V;RhLFOSf&=W@rM z8Q!@;WiZ9#s$L5gQ05pR;Z+um#>{eLTsMOpQLoL?D6zhqra{+rgLuy6Pz@Kh&RCBK z#WWceT$RquRWxjlpxQNL=$6kIzC|uE!hDXVY4Q7C|9w33%tc(jd>JQBp2j=xzJpu0 zZsUc|dpTZ^Nb8eWwLunphR>TJ}+k8ZFo6vN6<*n5$xN_~9jDw;2tHaTqoX6$P!2>+ z%&`r8?(-kTy{#YN-0`QecZzUkt-~Mx(Pywd-@@+v5zd@C3ScJNI@}%{BzzlSs3#8D`{)o|x#eK(mQq>A9${Hsy8hB!j zy98QdaN8g_7yS(Z0P9g8G90_)Q|rSc{+d@`eHAB8oWNJU`c=IA$}4#I;68rw`Y-U| zhgWg&>1O~C92{n|4uAW%-@$jk`#tRK?V+1{Y;LaM#TP#d2I2nwhnUSeFiR25rWt{# z0W%>4!h9YuoBKFVa!<07CL3R9CxA7u&ZCPijLfAO6&FflWBp%6&ZK!CUi1BVTz%Er zXw9&b*$C=jQD;byC>NIrG>{#`a0`pdfN?G<^!>0>xu5UEA4GYs3F4u_4>6EshF{H) zUd4o0{Z0T}V5~O{o|vrTmGe*HSli0%Rvd=~Y~B*GHx1loi)K6moaFTC zx=xbV8t%cds%o%&ROWnPb>OJd>e>cQt8F1b9Em~l8<^98nbjd^L@Gzt#4$i%CU~SI z_l;{nf#BzJaHmEdGpKQ(QG;2SV>;W9&R5x0_fa7tlH)t(7<5FKcbzzCmF|MioT~)* zz?g*&v$&9Sjv-FKfF?S;q?m*QBfxXeJU@UHbFiq#5CS}lvKlmBQV3GKf?0CziNhm+ zDLGME*T6GlZEb@2e2xYL1l_F;EO{s(4%Prj5_w08H)GK{*UVt{>V(V;9AQR{f5%=SZdq0MNQ7I;$k$ zgUi<9x6%MFm6V*`?fGR&Y|ax_ITdQZFH!#E9g>eFy{)=xPK3Pu(ikiI003gfu_K4^C*S;I{NC?>4YzOJMoWax zyz~-2{lfEL07s4-!(aW?U*UrfK1j!h+14Xs(FiiWz>s|-(hax_-94Joc06lt%RoRE zFd0w4`xdQ8Xr04}6DRObe*fz@d-e(3zjqI3&Ys8Le&;)2a`xZGg#0pEvVT4L>lk6)0qijzjR68&>)=|45Kq74+d~67iAks+gPyQM z<2^9%L?0kKV_>N1)1dbqru+MPjJ8A9by#2Dz--=Oe}9Vc+63dtB)(G`-9!8+$3cy* zhK`%(ZI}F~76qKYmmddb=H7ZM+p~78KWobz>s{CN@`DZ)7^#gh@6R?t%6As#QEAv^ z<1@Np)vaudIgWbmGC$ByCVm75q%HqeYl5Iu#lySszY#t5S^0lRyc@Er5m9_G_+I2;1V1H^D4(`8Yo z(^3{3#Nkt0=lR+#*I`bdv)L@tS^50n@%3U3OSxm8Yj#-qY)v+nZ7u7!gqch?E#65R z0;WqB8#PW&z?p7srwE34XwI8@DK3g@m2Nn6iK~VC1F2l4j#{IeQvVeDf_e(Pt&RNo z@^cCQ(7k1JlfPS|k+P4npH+JHDmpT{F8S`zxQ2AHA)U*vScuITHHt1$%EEH&HJma7 zy%bfj_Vz)};q=+FK;tm$I-EXx7AMb~5!{fZg}iq4D)y&S#WAbz zAQg{fW^}VTyq6$H z!q_3Kx8U&xnn>)VhAl^UC6br*NVG$6EYY7}Fj0&yC8x|rBlwt1!Is^Rpw%{omuR~o zF)llL2+zt9DOF=6*8G)<+eUV80tY>kD1}8_0LV*KG(S(#8ngKv?a0BiMh~?F9y!dw z5;a723?EVqqwAs(3Xm8TND6Zh3ttQw{Y~1IG{c_6=@wwdy{%K>_Kj;0pb@)sw zA*4h_h`TNfAO>j~EUdYrSfYg)P$#EE7|9s8my^zvZ-yRu)W&75)Aj}_ybN_FPzpo0 zBP=nvmHH0cU~7a@F0o`NWlXj(o^-*+bPncN5M@o~TT>#VVXuW*7|_qQ7Hb;gGDW;5 z%*$wovW;cA>(WsAM$C{RmA79i+qsfmK9eiGrmUM{S1RnKg1k!Uj0QtCt|23!bdq#k z*K-c%PR~5=X6Xot@Oj<1s6-4M#3;%gDrVRj9z*BT@t0!W;+*yzS^AHlX)XF!iPst) zhm_|bL!~rUOZ))ET#!UAA$UA}?g=!+_|Jd-mpHX~9FvzDO!xP3pQ~3NYUj17zLmmjj=zS!eI=@j0V8*%|rO+AO3fE z@e5zT-~HXIICSU`j-NP&FMjcNaq5XDaO=i(Jbd^7&Y7{A@uP?$QQLCd<+uqUplw?` z_0&^%`Q?|fyR(DYY>M^u4ZQZl*YM2K7qK~6#~ZKz9JA}U(76y#2QW)yrJP1ca(+>V zu@oFY=NK3f*mr0f2X-8DFd4Lj#KV{a&YV7tuYK)n7`H9<_V)1cPktPK|M&lZ&U-xn z{HO6(-~Ki(U%ApVz)Lt5jQ5P*?EbPfQ}Q_gJx62HG_p~eX~j96=ln4ldajy-E?>ju zu(Wv+{ittP8C-b;V9iNt?Ua3$MtI4?^ZUz;`5YJVzD;O+zzkfdjMOv~#r%}f>3}rl zy$3jpn$P;p=P;fMGTi5KMjbmgw%U=}a*PWO4$kHY+%tUAP%TIzfEP(`5r1ZDD4X#j z)FT3uE}%|1AXRXU`mD4;ME&B6C}~`#xU%yPx8Pm(JtnJ%^wF;x69%aDxB)&wh?q zUbz6Qw|Mi{zrb{_0WgM1h+sM*_i22Yb7tAxlw+#3QR2CDNLZo1h50SoEcJ@~+48w% z{9~CD=pc@mAwxKShk%%RD!DK-UAP#p*&XKOkIoPD@t*(7dBu=i?!mNVUY5N##AE8b zx)PC|-K+JD3}-cX^1f=cc2K+iAlHKJeZXgibj{jT>hR0nIY@if|L{HgWO0CX{H>a2 z`EK&I_tXL`L(b$h9@FEN^OF3k<&Ul5r*}X01%MdL1i--)KrUhfUI%45lG%A>^ARqW zZutB;=du#B5>YAl7!-v8gBmkprO$??O?Ch`0+<7%(R4lx#9*d4GKH4_un%xegCpZH z9^Sfv8&@vjM?d@_Zr!?t&CN}mJ8>Lge-A(S?%(4_Kl%}NA3VT>C77yV#%9~#lNX=C z?GLWtgLmG+@#DvF^5jW$^Et-j3EJ@(cdlMX3*hTt{R*bjDL63Z-5fvu$xm?o@+FL{ zz^(p|ASa5!0gW4B#B!pcw})ZDOZhY{(QlT`{p0Y0gV;N*ZRAiEVFhz!!ip>Uld0`Eh?3h7fjf`j13KUJjR=K@obF6$|4tQNGZYd744|O-<5YQN z5L$Hr^mS;Z1AW#xiCpr*N8{4Lfjmq8NDmM*nzjM8gl?bFZL=JhXNK)<5FsA7G9gdu zXlq;9y>8o)%$4`jrc0F_nK>S*(jOps*npM^O#mE|RI1drL)W2kEevUFh0rMdZQ4e6 zfdY6gc_T(vWfKJA0O}Yt3qW8vCauQnfVPDT3^WcjY0))6*8to)geDax0mQ*4Mg>db zApo3k{ z{YF9WD5BT19J2Dn=r|w{#i&!O-o_jvLM_TOfZ<%D5m};Wn^QTh$1F7fnStn{;U-=u z*1{d4aRwjKJu*R)oU06(M{-WW=8+<8omMN21r}^BL`;EvfOib`9*ls!{TZeVuxo&( ziSsB7UHTqk=tDfGrtBSrxepqOSWLH(w_j@~SFX#*(ONr0%6HlKs-xz4RHiu;@;*u> zsBK#iQH&TeIBNMB+qOm9H1M6*V}pwZAeiAb|6I_hyj;1h&4WY6s|FM@i@=Jh7}%nP zDH_nE{U^1JqoFSw3yU}(G8l4WtGs#&Es`2)@*MmcAEsht{LPdsnaG$k``w z`usUOdFDKJ_oujh=Ps^YyNaiuzKHGZZQQwY7u~!|xTKusfrjL$XWu?Dyw^u%_sEpW zAX3r?hUWn1#o5rJ!GnAEaQ*rv-u2f{pwdBFm`r!V_UJ!g()>!(P*!1 z9vl&#xo`o0@-O}cPMtoD?|tw4_}u3{hmDO5oH%hDpM3UNy#32x;nKU8@PO_Djq>UM zN%>LqR9{ni#8fN3H;ooq(tqYGx1)|`YgonUKmF4`#l?#k@uTm*hKpy;kz{jp-rBK~D!MJ}=H$`fE{<(TR#=CRr3XWkPp`;R5kIU3WC@23 z&R6)F@nRzFmo>+GWxgyh1CG$P4P5KM?K*C4&G7zrujA)eH*oCyQ<$#(H`v;`f&cY? z`%jp6_p$Zx5oWs{*Z=x9e){WYaQc}e*bkq>-k}fifBgA-xIR6GBPTZT=DVAi@)>|S zG@2SHScT$&bRi{<4Nv8|E9bnn9%|2&I4Ga%;d!8)ay^tV4z-!LJ>*Cl(lM6Vk$Ine zm>{|^5Oq$&Jblt<#fwI?cbr_VtOfI3RQmB8@I`|J~xR&QELqg z0t?9e0*g0mV<>syN~c!knN{yByN2|gA$@y@&W7&SzOT~d%V*1Xn1wIp6ELhFrS%@| z6Haos3KU;V1JWX-_Hu@E&-nGveu{Tq|0(A4 z4x`Zs`@;Y0lF``^c#KmIYMdwX&Z0CiqYNpry7z5Dp~pZyuU@9^;9mY&Lx)G$pm zLfbYH2-Y}^$74+Q_tANeQQP9t&JNuERO-0o_+fzI0S;(5VAQrUJ~kgi3Q&wJkFh|z z2VIl-vj<%(%ie3joOXRHfQWLH-3P%HynE1K&XtEkiCV_cRx$Uoy&;6un14-xJ}%=a zm4&`QNoFe4s0|^YapD{bO@rxZg#D%gH%^Y~Iu(}a3l9fiG#*R-uh#1kb4|fcW(25k zjGj5fMdM5W3*go|*uvek$3QvrDCSeCRu4x=-lnXCH?s#?0$2=65sqCHs#4S@Jl+lXPnIc)7 zmmz>1gDfQ-8074z6vZU^hAPWn8l4M}hz!SshQ+bVb1x2G0APu1Vy~Mofe|_%=fIIU zVbzJ=IH?@$SYa313*N&A)}v-YBUT+a5a#<+xXDOU1^|BK@&2PNygT2+z8&%JNzz1z zL&kr84Ke-FK(ya-W4_jowN>M>fiHlET!0rc}v z<@qtou{KSE(MUEyELBD6@MM;C7*iX7dpN2&B_b}+@2|WE1N1@5JXhTQkro`k)bp8m*Zr>Q%!N!eis6$ZV#L zRu2Mj_~;R=AKt{3Yad`I%<=rEU%=V(=Wxa+P3QmsAOJ~3K~&?`EnK^H9k*`X!b>lG z7N<^~#-&S_&~-^It}0_D8aN`PeRSi}DQ6hHp) zPw?Q*UF_`a!XiAgz;eK8#qsC!a(& zpX1W|mvHpi=K)99e)I^}Z`^63+H%|ut*bIm;}BeR!*h8=T=`CIjIq4})eG=)KSZDw-JZ8wel9PY zA1a(z%N$GP_ge11a=b7{Wd(mWZceUkDs;W_Y(dgXVWr=SB>U!cx4>`sw9ehQn?k23TE#SnTsV6Rc#IAFxPNg z>KyraN**&bj+BmtkRR_Dq7NBw{8oRabXYE+woVr{eWV=!s`+{xnNxeVhNH)oW3{Ws z`-Wgz)lRu?`tO+LwMY*XjZvUK2YIx6uH)6m*V#fN}ro2O#G#at=HbdqQfL zILt^-wlZT|^GK6(*!3QP1I83b4uB(^ZR-$xhp}gj9bx7q?P1UjbcX4ji#+wWU0chp znqRR09dgWEA3so|AsQuxkmLBj-e*&EO2BH@Qsb>)gt>8jkSk}VHAOUstHuQFeoYx& z5Zvm`k3K)hQD8ZCCl$In0_MzM=imcDa$W(+kktHp8jur5 zMBnx`oapFBV?dd@9ZQP*-5iMezT?zwJhO~6FW_`Qgjr@zj%*vu0xLxfB33OE$QU4j zEND_mvfoW%V2!-A`89YQM~A2*0(qNL)@VtM{A8Wms6gpXF9F2KX=`lO@b7{iy(fvX zx3-*@$Wjh0=>}8*vg|*Gn}|asC%AJ`q{wT;Y!r=-l0nf>ELPCAt>hNcs0-4a5TjQ| z4h<TB8J+OP8Xz@~bb%>k)r; zua<+jmcuI2X~BDj3MSaQC0s+oK93?Ojq0gG_8j9 zX)sMD6RfSRVK$p#Hk(C<=R!5^946z56$QJh9IK*{kGGUV5og?wYO2j6W$Uesa9E}w zFmv3Xh~G2%vT?_DM8jQ}m)cyBKHF1GiLke~hxgxm4~I53aOd_-Y;AAj=Iz_KbonxV z`Q|Ti{lgD&^2Bj$J$Qia?JdMb9{(g>5m{@duO|8!hE1{#2Z=g&5^XOL*Nj6OoA~2D z{v&+i*=O;yU;YB$eeL`B`q#gK(`TN*-+%WX@Y-vy;pwNJ#)%UrF`e$idvSQxj!Y(G zAj+z>wKX(NgPolnFf$(Q@8jm}+vvg^U-`Hje86Zt#(LxM{3kw%yEks*^7ZR7 zV9SH0?7bnUX*&85KZc-aju64QM&uAlwM{w65Ss(3a%}lwH#ax&lb`$qPd)h*KKa~p z`1lLY;l|CIxc~4Wh#W3lcoL^iKY_dV?rEiWTMz19Ut))q#&}MrrBkgphf0UGF}^If zW{tn)d}DCFigt4TRXQ?ryxaY@ZDqq)qttW0Rr0X>Z*9yuj+PnhL*p3IQCazlMYfiy zG=M&zws$PYXW4IA0Lt25rCaCl4auHm`h2aeS{~&ZO+-7tz)O8C_50~nLM-4koyywo zZG=U~j_O33^FA;kga)*+j`g#raqW9=T4|rp~-{tgz{Qt=MT|ZjgQu7t$c5&|7A2@qt|6LS;BA6 zuNrH=&0(3t%<*JGrIp^eYHck0ZZMmZPvD6L{=gbp#CPrfs($VFRXAHV&#Rs--(gi& z?LT&eS=WK#aQe&{oIG<*P7VeMq6tBQgt~4nx|yDAbZsNW==3uYVbr#m&1V2hdN>9! z_jCBU2Rv&urmWKtg4X#Ezl`_dD~bLHm0yxhg+YzO+;?I(=?Tfscj)GG_<4sA7?a5w z;2OR;>Q_V0m3_4#c!*sT0BYb; zsY?Ky1Uc6yyhlikSjPT_wbFIBOA3t1c4~^Z6XJ*$hq7U_2g2qq;X) zPNHwjq01~p&88&jqUab1npo9vB*u$ko~6(YQvgv?FhNK=#@tsVStda92@0{>F|<5r zTRvDv6#E6l*(Irjk^q1L_V7X<2M1W+n3#_z@U&K>EtnUMlynK zLYt$ORH$+^@CASdeZ@Dv~95bBNj(-<0L9{lL_bC1TUYwfJ2?+&UA#VYI;uB z`pbaQRxW5m+yQB|M;UHVUB)6}d!#nT1gN=<1T)TF4 zVLpa&8$eDX()XuRoIZ65U;XM=vAMC1zxnoGWAD*6{>T66e?|zv|Mh?Vd;I#{xAE*J zKZzSxui^UDtHS%UypV!xmB~qr_xjA|K0x!sHjP%9)Uh{B!fOD!&|za^1K;?@H}E^Z z^E>$IPk)Nn{_%(SgFpN_Is|;@)mQPx8^6Sbr!JsvC)j$pg>K$SN*F-%LYwoekC$Zz zT2AA&dF#Dbw2`k#d#5zKa>rs`xTnVZ$|Kh0+RqD5+GU#_hYlUW#fukl_3Bk@Z*MQ~ zsPbQ}xIwOf+vk$s)vg-<%=1~ z`$4ZoT$nB=umKvpRqz&jCgCq1Yn{*7=Ul!F&DD_ZT>EannSPP?wPfV+{~mfD@(XdMaIe~TgO+Q7p>fd^R=L%Li(4<)az$vWZJgjP`BKT2*72$3cuhzsT= z!!fqUQSjHUAtEV>utZ2nyyf?jK$j_1xw4txTgHTI3Mzo!?)U-&rT9OD;3ZN`1*u~> zLhBglm|+QJWu>EMa5BuP7WyKGQ%McR(iiS1y4 zjW+uDPT&wyHv~9>nWgru&&J}^0TtobzRk%j^DmB0XdX{9WEk}uYn`;f9+@;_j)Fh%}e4r2~` zEunYW$Iw`E<31l#4bJktAspCegE6lxZdV!kxgejoFSk}ILfNaq%VvsmS{Ny(zXA`X zL$fx3k=`?CHaXMYs#aQz9%F99XJhI4xwrO$LwfKXt{vKX_`6WDh@BoJn z9l^29!#MQBIh;Ft7H#Wr=*ThL-+qLqaoC@#^N1xi1aY>}r@SNiI@L*+SXyta4BcrC z#@mvdaYX36$A=$&h(m`DjX;tgT5Z6J~5|Y+z$^9S?75 zUTIiVI7jNR;n(MuB}GWiZ=B}Q)N~mrPCo4I?cw#;U&oz0cd)y=i^E3_ftm61H(tk$ z>o+i;&hX&geZ2qPyO__W5^)4p1H<~R(^gK8<+@#^#FjK&>T0=eRXZ|WI@fBvbjZM5 z#p8hF{L6K^(%CUI=JNNvz)_CB+FBpF$}tV~oj<>vH+xCdXCvYWMHLTue>n{;cAhtp@l5E+t5))Hy9T)b;Z@wb z^Ph0)BcH~V+gGuFe;czi$1&gELYPf~5HNkXg&+REe~Amv{Vt~WuESlviC_Kj2YBYW zr!ikUj5{BEfTrtIXW>F8>%&)b{?^t)O^)TVy_5wxo=W~WB%f;=ltb&Wb}f@f2VF0# zek;>d)KW(n%y#-Cw!;C$t{1Uflg}lOtm%3s-za%%%|;#46^G^{KM}u-4{9p|`e?L@ zqFZxF=R64iSp~R`pYRwl-hOt|7ldt^HMPt!jVhp6Lfe&n)A|>GPsr<#Az5 z_%pjHT6H}gjxw9p`cBs{tjlC~ZA^oFym+S8R(z&_idDa4PC16b!12|5`j6u*(pR&+@yDbvBto~6DP3l9IoBIgB<`fwJTayCkUxu zfMHpOqAYZge@X7^Q9B0A|G?p$TR5>ZttMBn5-eT zJ^&yfbOAFCS`0@9oP*v2SD8!1tvMHssb)GYBfY4Y?6XovinP?Wf;z)sLL>2LuyPNE zFp^k}SCMT@#(HA1yBR)+QZixW#gNi_)~6J1k2hd(jzl@_Wwd3aW=3j`^L(zX5g9~s zcodlGt?hg*M*?$M-`R|j&Y6_}&oGcvgHt$?;uL$16O_o_<_8h4)@Ou1Pnhk*3A4Vr zf&Kk`O!qa~Q^V~^=}7XVTA3ghvg5bCWBV|gT%0q5 ztv4=VFp0!96-Ceu3`Z_*pycnZia4qnItIs(Oe%kO1^!|i*)v3x$&$W8SLwvkhAHor zNab~IaN)#BtglV*;ro~I?DH?+^y$+$edY{yr@J_E>OcHd=9UC@r&3zd>C)O`6~n;@Y0Jf;l-C; z#N{iO@!8LQ7T@~Tw{Z39RRF_z5_7znyl$Ms_SQDuc>Q%e`>~JU_y60k;lk4w@$RL| zIDYChzVziU;?}L}*xlIyoj#4D$BtpQxeD$)y4gH#lnH)0^78tX-C-THb9yQx(Tkl3 zuubJl?_a{D_uj{I&p(H6e)F3cjapp)`87QE?5EIxaDQtHK6E&F>ICj@J-}=>m9{j0 zaL%1;dRb00s~i@!@8uEhvc1}S`COEI!1&2Q_*)5YuG8)A?&6(y-sz3m{?+(T31`l? zYCO&U4z2f6*KFGs4p#}{B-QF#H@cJ#Z<28(& z!>?`yQ1AdcG~_{!;pf}9^z-kaX~$^Y1k8l@e|80q2}B-*87wN(iV_XsC8yJJEJNeW zaZt+O{9Z|?rL!kr7q$KmYGWdutmUzxFI`ZW!`-?ORGwQNrEcJi^<@Be=}MV8^J_WLrq zQTx1#7uWb(4#P5>9YiP0@7v#8hpjz>0Vnq3tkZ-CATj8?eS0z=L$3vWe>omj%7)@O zdv-`}*S?pnRq?J6$3oarE7H|TohTH@?{19DtDT|AP#5_Z(!nD z(B3Zg2-tPd04B?CSK&3B1xztwEew#P=5t^W1&rBaqh*Xo4Ypjs?QVvt-$zG4*O;wO zP_Qr~plcnx)D7;RkSy@@o2=C#*>wGjSRLBYBj(N z!8MR-f@)}h`5sY3ug&ryg5!LOv&A9?Rq#+|rD_FfGuX(+WksoBieYX% zWdnpB3XE`DyLME)AauKJ#;8IS<2kz^#ibZ9XRx$^SlC8hvN%6mcKO9%tBl<@=OIVx zDLN{REf_A%&}L3Tno(JxIUXn}=Z048O5V(r_vbhnOc2NJqs&fY-#;k{?*dK?Kt0eHl5_!+{jD^LjsSDgcr9MtU}ff7TQM z8r>$J%j4-b-h((3N}T01G^B9G`voq6zRD9g2pc#698uEiqd|FyBSITwXBX_zHVvB5 z2uF?{#b`XjbUww=V<&OtS8w6crAwGiXCRQKJjwBwjnV$k%ClK{^dOuRJR_3o zjvN?=4f< zfDmxv$Z>rBvoGVx3s2+bo!hu_SuDH$#Yp(1w-L8gGd_r8ZLqAIaBpty+1B>megChtV9F`Momo zdl}EN-;CD`MJ1Meu*AWT0X*aos*S1aV>}+?=+UFt+uOtL?(Tw)T;gM>z4E!*7-~n$ z^LA>u9Ku8XJ)cA8n&iv}`V49S&fXEU#Yg2i(|MwNi$D#&WJcyH`H3Eg2DhxWh8&oE zrU!Ynjl+6NAkqS83-&>}P|^IwyjtLV$V zm%rE6U^%Dxv#XqX2X(?M(}l|4@;+(~j{LhhC95YQLelf?oph|+Ezom5hWuVh`eJp1TTH`FMxg%CBX* z>OtBV;*sUO+MGVlI$QS4vMcp#Vz#t@>TvArHn8^>k^9z6(;I83a!RDF~MZk&&FbC2NDy z$OxjNw9cXHZTOkMFuNmQPK>V6;y*<24CuK;IYDcd3abZ&igG5(4-76Tl1!K(h;y=S z&^g9F0UbHXn9izODSkZ3sYuRnR<8X;p0AEtqu_zubX!ak>@Z?3poM5!|-XRnFV*bM3%I*Ob;C#HNnsjpad z&Ttr&eEFZ{%uTIZg9O{XBG+e5Ic?-1BuJIUY|0FeI(NBZWV9vo6R`DQ3-jq5hYugd z=H@1jA3uR($ByIf-MiS@+LHH#w0bx&eizsSPAZ*8<0^z~2IQ!w&F?{S@|qz&bX3g# z<~f&2*|R@vwinfl(IdsVTNrxWhrm z7y@i%I5I<7ITJ%8_E>osVLToStX&7+rARL0X>EQkEA;P~7`Et){um|6Y^?~;ka0K^ zsrfhtj*L4gTH2|?;9#4O%*yXri?b0eL=rE%qI4pF*@Guz(8dU!7&GtDwvu0VZ+90b zP9Dd2GQp!qJJ{dfm%fQ34`Q7cXmqo+Z`@4A7XYryRY3V6B}ItBib3-BZ*Kq|lns4YV+v^2dzr3y+kw@UQ;+e}yNXJcqa5d<)m!{}3lWaR>l#{N!=K z5#GOa1#kS~m)P6eM^Zd>!!x7fp1}}ArsUMqAXUsPogXC)*Q${()4ytKV40y<@~zta zWloS<6j%Q4&^oO3VOK3TaS4CTI1V9THk)C0cQ-mRm%%i2<>SfuY(DlpvbB^43;Jl5 zU#!&IhsN7)mok2p^@FsToXd%R`$_K*11;GqflWg+oh-#UAi2oo)P{>sG*L#8o-$IMNA<4|Oox(FNRAj3=1B)r-JaEnNm!KY26sNLl0JuI zP>zS8wNgG`>+hfrnOgfb*m7N_>~E;eRnCmT4JsD;X+W}>Uj(Ks=vjp`rp7~-IluDH zwY662b|roeVyDKrH6qH@bk&{ZXI8->`f=cZw856?^s3J@Bij1L{fYW=pyVkBxymuF zvh%C!v*@=hWHr?OLB0>_11n&vU2IB}e9K_We_!^#F^2Lg$JmFxT<2*V{pa)cvR>me z(q1j-#GId|DJox!Z7s^}y!}4SCE81UV(|UtYv0D3jY?rn_cX{$37mU4;`AJ#z739n zR?JCe<-tC%wH5m=_%C>%zW4wDAOJ~3K~xam;7)u84x~Xt;;(UvhrkRUfZ*hx*G2;@ zxGTYDYaEbNbWuK1NcS8`VL8w6fe{D@jY9yTBZ7ydcJoG)y2i~{!7|b4k#Y_H=;L2# z&`{w3k~$WakJ=CuHyDxY6^VwV?KCfz-(7we2XC7z6M!18-bV!+KvBdaXYvLM=lpZ& zRbzI`901E0W$pLJ5zq&LwKP(fGF>u8p$nUg*s^GhNPXp06-^B-)}nH9950!piZKST zNkAKgkyA&I8cycPhm^@-uS`z~k6k7d0f~UoiqJ+h$>M+FH8GcR6Vv6 z$FFNjjAul8C>8i2{O|7WVt;=h+uPfiOeWaa*pMP60CskEFq_TNdV=yh=Ri#HNcsN$ zRF&i8#5UaY818_PSsJU$9C$+taIL+?oz+FUjD1s10eu|V=d#dG`FY6bU&U{8zHEl^ zvgd1&?{nYvoVWH|2wrG5sE{L){DOMfCX`gRPx5A{Xi04@n5?AQqT*fV7Atd$=56P5 zU&^h68ZkLHDfTLb_Px7!@Nf(F zA3VT(e~ODwKaDFNe1Ny#ehc&YT;4ZXXmWtHAXiq*$2NU(C3Nr-qV4lEW{lbvN6wta z$3F3KxYofl!!u)TeGS`>wh=tz%$YN2MZ z7;UU!Gk9FN`XNrAI*Ee~XxbL9zwwJADkn#2 zp9YCE!j|Ka4VeduBN)`vN<%%-X@~~s(6yA8A>nLj?b_db6T0@kt=Uz&;j(^881w#1 z+|-;;B`>y!%vv9%`I|c+ zvR?;u&~m)_Rr0B!_e+>-PPm+x)^Jl6WLpK>Dma#1Wt+>}7+R-&c{$t$7oTNvINEN* zI%BQvB*GanIK^zW9tb%t7LZe456i!&z8)9ni~4Zw`C)ppHgbom+Hktm4M?@3#VP0* z`Cur+sYxp$^x84qI^q&_dK;pXpt8m4UxS66ME+-@Uun2a3rZ2dDGJ4!a(U!Rwh&-AtxRA!_+`HxMEQE$>v0&`VX*S2(E4AxEl!GbJX3Ub3$QlKft+& z5Hz^Cbzs|(Xb5u$%pDM%8h7T=60I-tA;+ZEsY#ugGM;&mh?vk2ro?#UJ=l$uK_sRX z)adpV*|SPR73K6LTT882v)w0(?I6@63^8k1~p42+99@u1pR7NAfYaxi(I}2h6 z61B=XQ!(ab${P$0#bJyjWu*Kcc1e_j1$3)`=2EaiOx)m z%nI?HJ~}BfE-9rUmDFNhR8F`p{~Er{_{y6#3h-s z5WrpxskUuolc#MGm(J$4RKRNdZs;nN?oxRh8dv^I?S2W@GX7TLYdjufG#X)VZx6HC zEdKsDhHc(Q}9FXXl~sjg000KGh&q&%Z`oUCeiL8I4M=vpGC zDH>lxFz3H5lP_g1ywa($01tE0Tq=D5<}AtX={lv#l#$-o2GVaC0>t4!IP=s~xb z6nF6LXnEqE>7C6(8+hrnFJWzc4LdtKSX*Dm+S&xi&z-{7qiyW&?O|$6xFwB$X*WsRPPt{N|^@tX3t+M0{F%Qi(yeK3E&_Dt>B8lTT)Oj$9y=A5bV z>-^r3AykgF)=q6)wfB}8(93l7a=ql^%-^ZXWKsU2bFsV{D;ODt_9GE%^124=sxj3ZptUj9bgr5{7Z4ZllHc}PC0ic%eQjmox&Hjw-}*Rfu=MFG(S7Pa&i2CdMEUmw zqixtGb#xe>)%5b4(Nhn9gBnkG}HdAnm(!0ytqoVGusr0%EL5pr(ck=M%|kXEPBzSGDjV zg+_lt7FasS=!XpX^4_vGh8WAxGs~{h=oxyKvKcTMH}4KJFx#I3n_4(2QgPq0ImD1_ z%0`cwvxr;Aj6=@hXW z4x!t}Y&sR9bO1FDv^fUWNAQyd)0WXSfLD9UAy-<&5!wV`;t5R$aL4fTPQ8+heV)TR zh95axo$ukNcW+|4?f};yk19?gV)e0+WwcUW<@<47A@fEoj@U66=O+`qxN!nU@9tu} zF$RwtxYmgS5e#k!p>5zt4)YPAYk)Zd*opH^+HwGk)7g$-1urwnMB+^D0>a#b=M3(| zp&jOS?1Vv%;Tk6eZ!{G_K!9%uJR-~?&w2=x^o@fj2~`i~m<-D3*>KDf^-FqWQhygG zx;oS2;xQ&9xnMa2%!skW0W&Ahm}yY1ouMl)8uQ@6128kz*4AR7g}uGKxQ1EjAOL^^ znzm7g5Jz(a3^0W#$cKbj$?$VJIEWH|P$S>QnDOOD8o}lAAS1i z{(P?l-_x?Z=KRY-%59Y4DE}7Zan!W;PATIR80cW`_!ksmtx>5$NMI&DV0meg7YBR1Iy#^~8WG3Uw$O81#Jf>kJSy!sIGnb;u_jPC_`* zx`LjCC<31Vu5VSm_}vU%a?yv-dE4PX3pleyVrC-Xd5EoTdQ~ZA=DGHt zNFCYryx&y4-wh46XOZXi?Mr39_50u6^$tE#99%z$_+DgJRM0tX{0!yX86SyuXmEBB zC2M^&Pp0LA1@RMT6~=N~oLUa0f=n`#<7C|OZw`$&`;4`_>p+!DOnp{4Ef}!)0|~7c z=IkSvYC~0gqayx7D-z?6Cf!q3JKJu#D?tU4de;rMhAp`f*<#D0w;X5qwRxK3FbS+V zTOOiB*>)eVoB`pJQ@HPoq=C`$MpW@1ybO{!s~uWW8jbEV@@{x&StKz+(w*_$==>`iUwLNfWFn z)js9=Ga+E(e6e*`*U59`$=bR5zK>gz!*^_sMWlL&rSg6{Y{6qBgri7wZ*`t37LD5w zTM>&kPF{(cm?$6S{MskUX>G%ip&OhZPhn2wEjei{4NNxBT51P%2=tg<|53IdZNvpQ+M3F4lY9d_<_ zLL?vwoyI-h&me+FLm^$UeBnToD&oH$Hl6}-GzFqfz+k6R0{y%Y?wbv+C*QbbnQBF@ z?k;vEg!u^DO!eta*(~j1Tc?IpRweZUoUu1-A3}L_@*rj+DvMaD;!>89ZxKM^mG3I) ze@8X%+u=4k=2EIh%JbL>(8Yzih7nc9Xr|F4Bajd)41>dMOCmFKrGM%=hl;n;Xfjxm z*!_W+uQ@~=$d|s4O*IuENi@@o(__G&~QwUqGyb`u3K93gV`m>tdmn{HaDy%6ZkJ3b> z^9&&qq;na7zS*4BJ4*T#-ObDO4Bit;Iqh?ifV=#V8%0|Zp)kh?5D2K6<68D znN7`Hu|GvUJ-j}%{StdB^uIkhzbtyqFN5O_Jl_Z#_x-eTwfZ8C)Pp)nc3MO!BaSt; zL1kp{us5#54d>VpS#NGGJk_^L-xT3;t(*QytJvJ!G9gD1ES*~!@Nfu4qPn{)OSY9p zHx>z=d4BxWbT!T(E}n17*P|%A>C1HK(Q+h@^2Mk@3kHwhsRTLJl*I<+xcwKjTGP%q z`%?#cpgkVE`(pH?h*@Bdsdx8O>;97pM{@EbtG`c1;(uCEDHJsOn&%w}eA-{Nl9dEu zEAyLm7=+?Oeh$jeMagcE(U?zBQ*)?Hzo;oW{yN9iWO&)wMLS#Fd^5Eg7o=`3bh~^F z1vyBhv9F27Z=#kXy26RQW?9nrg#%P+Xk9pY_qjcYy=H%usCweau`oabw(aYwQ@`XU zIKonpu%lbe&a@I)sI`#mR#f|-{z1++;jgXkr{h{XC3}^n`S>ZW5l(cQ%1+sIKVlHK zqvq6z3}Rx9(9wd`*+kjs`tANm+OM|9=iRkEKt?dS@VGsrT zeu;jF$szIm-~eW8mq`Y@@Xh6gNv8GvnW>dzYgPRe?VBS*_=|g~)>hPbE!k|-txmcG z7C5SmR`|0Rxw~u4nUPK2)9^WwVVyr{O;f-{iywo4nW})$+DSQG5(~*+ zB9Y9T6=ycBxvgaPiRGJ2<_hYEq1wQTnmBLEd>~j4BQFArI?Pd z7{Lhx`EyBX$m>^AE-V`vRJ6658%6ZF@AK7vCqPfuGjK{-(jgFF&kYB?XkkAHQQMfh z#C3-<$>aG6ST6{}XCE&PHruy>O5=vyCDuKKV!#Z>_D6(O# zc{tuT(9x-J@_6M?(NWgwvkj})yiF?oVh+8<+Rlvt8|YE1V1o5v zk&mpiAASlhb0$IquCD{hMV~@>6!i4ks$E10nMYb0gn-@Wu zS2DNuPMOtsQHRQs-kWg#v(Iz8#qOSL>ZSvPJ3L*Ur|djiE}8uD`S#c4)wl+5If>B_ z9UP6UZkJdFe9uC*4w*Wge~lPXXII`=h&BAOTD6YKTsvL$t05B33UHnkD*PMVbE=xcqatd}*u>p%Ietr?payREIbgclWLDxqYK z=IoY*W+l^i9-)_OvSe~tg9S?wQH0esaNzQicZ>0udE<* z42d??c`#uJ<8C9(A2IqR>=obPo8-_k@oz1CLajoz(p226+#tdVXc#y*2d$%Yg@{Rhz*YYUJ)A6zRSmdx@lpv zk7(BVz13#+i+WQ>^U(c$2xjmNmszXA#q6LtCs9*J$4r(<(D?io3yGRMONxVo5j?Rd z;A{Ww*1m^`^~34l{wiS+HxZgV{G0A&D;0NGv^I16DY7`8H6ndU;%yb)uwQ%>E3-@r zLP907LMr7ZJ8@3B-4Cx$Jm$0C)cLg*zde0JpR5o6MMp^!&n{(Pd>jp9m_}UGUi2x^ zm(VvA;i*7NkuV)blE<|vTQ}O7$Y4oez`ZdZFb5Iij=jk!FP=hQD@jn!6XNAMdJ75v z*{QEWQXjHatV+gUi>?SMyF)p;j;7a&jXlLCay2H=cW`0u9s2pN)%GZZFcKS*OS1P7 zL7y@m^!Bn{umc|@hnSy+4Wgpa@R3dQXSYHtzcrOMf>4vEu#5!!CZonS%|Ni{45D2a z4`r;F`eRG3f-%p`dqkTO6J=$}uG{l6i4fsCQU5xiDBhr6c@**PHmLBAdG;TNz=r;R z3uI>Kg=?~o_%d(_VkZji!NH7oh3Yxj3;fnSFLzt{T7mBFEM%90%eiM%{ z8E1^hXojAjU?VY*A@g*J82#>`TR*U^tXF{*w(T-z36ib;5#i_(=el|X)uiaTtD7=I z>5@|un0S-p7&2}-d>Z`vu_xYWW@biSLrTtyh|6V|Y_lz|f`pE5XNkp`qkIp*=X`-U5vsZfMC0RQ?i}T8Fcera z=i__4tkq=4v$Oa?rV0Zna|l@DA@Fc1>6*`ySS;}G0Uj>H#r&ou$yn6WQV#O5%Qz(( zJTLnA8Ao&HpeFt(7If|3Gcf5L_}bC+-9G>UecFHPb2-iLdvQhF%VBbHvBc80T*u1c z?&c<$8!)4(v$n9*!dfDd&R$@ z3E|d&Vo|3xPexG|?Y(9T=0Y`R7Dp%SzwH*;xG-i7qBR&1EKgQ~oD4l64&CSsG)Pq3 zqRy5}Xi|!63`i`+_O+%)JOiG7=GsM*-m##84U`>HUH7%IKV!lho~aEp2D4@^7fBhX zEw^ER7`t4@{zKtdi2UpESLnWz{h1DQV|7%x0{dqu?z+rwlI34Gl4hZ|$+*pq?F(rcyTBM)qROi7qio`|06@cQ`}$X33lwm)U6_a^|Ox z9hi@7i8qjXU)sF)W5vI59a<{NDaxv6gkrU6_?PtidomF;~+OzmkUtrfQ*NznrTrpt*~)Z!Ccxj4X-9gtA6O3#(rJDXg7UjO5ld)JOL+H14HI&xmNc)R7 z|Ejz|(wMBBqrhvPiesD8jImGQiQQVn(q-zHu$ALk!CS4E^9>h=k|3-aN!ft!tgcN> z9rcnmZj#I5Vc6H@?Sk$`Bwmd5x3wkSt%t*P>3F4lZaNgwzWbTpEAXuD)>`?d@$dd* zQEISxrjCxUYtwA}$bUjCN*g`RBcl`nW7qZeeFDOztzA>UUM^1cHdpsvbWASK9w~@) z1Kd%y)`zCAeurHTea-u6U$5-IHLVHh2&1P?5YI)mOen0M@9zDSGo>#jG8tyO^pu{F z&?7`LI9SVozzG-l(!<9kzv$47Ivx$%p&t(SEi6FVz z@f9KBx$x>v=S^#O_p~hkMxUo0F8F!&KSe*cQ^Cp9X)CZTNp(om(B_9&QAK2goXwTr zp|Xl|inCa{{QSfxT{gu0bF{IQzfOUp$+mNyPrF3e%hZJt8%6TV=9WR$L{y(plri6n zQF_hA^rI2f;qLU9P(vF6|6(xzF)i7_@m|q}ZEM|T!f0u~Vxss~(h^qY_WOnTZPy%O z!pDxI8RbDEUq>{_rK_znnnE`{QCVXm;lC@LnEv;$f4)zkqc4>4&(_NU{VDlb6R)!m zR>N`0er9(e$3@q%{CuKR zLb67^i3)9jhsrivr6>&!0BtcHg$1KOD`F#ro=^M`;D^pR|3g76G?cNu1m)F9}*K8k$ksKE#hnsn^7F(V>aHtP=L zFS5$jG!EDD%=%^WZE1k*yR~;z%Bm~wE45Q>z}AT&?C+E+ zSZ6zQY`Gz7s#qUZz)Un*?KHi#%OZ9Ox$oeY1&am2qhKvCKgEq(n$dU;!2j&? zbPIBP)qP@uyWJ z!kD*4UByX|vmoH>p@mG%kgf;@`i{*59&rNkY=ON5<|sw6u`HO`3U`1c52{p-}V^Jh_qVR3c2Jl zqou=5)_$OT>?{3?r%V(@tB|^@^(K@aA6bE$z6-}il!ObSoWeH#u&(p-lw!_K=}%eq z`A~}X&oFvr1Kb75N?+bTPsOT2$_HD9zbnAHx_TACTMUBW>B!*fqmnO;BMzz)BNi)k za$kY;u-U@@SbVtdKV1uJ3zs`d2s~GqKMO|wDdkD7HY7;$J>|G;h>InN_)pagxx8%) zLf=t#F5=2H<3Z=%pyO5ho99@b3aR3amDhYKj|{Se`77Qq`HEL>`l_;x`xm{QKzj4b zuC7pUj!03$Pq8%%kxy80>}fpKuXov1>oWF3zMzU#2Uk84&K$6yh_F>YtnYj|ukZGy zx9=R5=TK*|4IGr`s}y2otxaQc!Oz@5Hy)Jg&B_FWp?+veylnRs3E_6xYc@R!aIX}@xd%8FFTt)vpjuo zp@Jt|$NIZE6>@k?`?2HJ^FgDofdLJYl(vqJ-aqzvppc19WC3ouCFSb+Ivj{COqDGg zQr+^6mTevSW1jVB5FSNrjM-I?O-?%bkhQwDK_Yqn4nes>pRJ-HE8pRu=GZ}7DF>-p3A zO2hfpp+?Z7^Wg6H6QxwzlHR7Tz`6bSG5X&1L;b&VHWg;GxfI$jlt>@UQM&Rrxk$Nk z|ENV@h*T}~Ozq9D5v8jGNklNXYY=L6U^N@{?zv{1q>IxN^8yFdbZOJ)Kl{(Mm26t4 ze+#Ea+gHomJIMQm;9Zw!_zgj$Q^TBXQRO_*BiE`BT6$5t0!g%c(N?U15Ti!Q?hX)4 z-{MV3>GlsMymwL`C#BMTdDHQ3E_}3nBko|>D-FH#%V>_xuMLF`4I*oie3N3Q(P~2* z75aMqpj#4FmT5i>b{St0*jICeF6CgF*(20)s|T&5WF0bw{cbh$f7GWW9eKdU)kMK5 zPj-;@CzELudy6@mrmVqggbIzd0)o@vJ8TK83-jBG?Pe!ORppp8AW)%DG;jjIz+bwa zy9CH0eK&Z2^Z2kv0?_sHuOF3VY2GnmONe730+E*5+By(vs(v_mSx}KQlzzs_baZ3M z7g^%RxB`rO?ZQ-9naW#w;;YSGsrNGc76`PKL`Z;1rGxVWjIt-42cMIg`|;O`RDb={ z4k`QNt5<#PYONj0&P=HV6Exv+wO^04XJk>Z>bshpDdP>=x}cZBcWx|u|Ki6;%3Zt0 z%)lV^IC?gDr*rK~EH!ME_}Q~XvAa&pW>=G6REsm?yw+}NqDhMhtQE)-4Ps)MA~rst zp2~1RMVTQO=JF!5;wI;j6f9%{ku)mV*RwBe2W|RjRxp=|FSRlU=^ArrJt+A)PLE_! zeo2Ff|^$9hiXzikL;ol-4(*CsAB4muEH@V{7PvlHN0 zk6LGXXYDTD?;fR|${3q8I6WO*Xg@dPrTJdtL$UzKlKPK0^IWjnG~T8HviAE_M%j3# zI7Ia~l2mzdOT8@81}d_PR1gmFM7*-|7+Wc~mw#mS70__uT=SI>5SSNq2F`^jC-|@c zTkhyQ`nK;V=k^8?jw9Jm8|qtm5|JZ{3884SGXnFukPahl5-Dj{7`N}zf-^(jdvsH( z<%rS1u2Z}HeB95!!cg#3CPLCIj>%cDMJQJ<^N_?-P%ptkA6j^zKGaLVK{msU083Od{zq4s z@YDZ13?f2@q%y{z*pY=qd`H$3LE?^DB)&Up{JoA6XuGV_^Fh>F*rEE{3#7fw^#4}W z*bSCm`hI085V93hXhCsfKy<39mB?mNIF!>#Z`P(^h9&&9ElwNb=t!n(yv+gmqz#e3 z{??LsGj^7{%wS2J=5U(lyR{?7rlf*9CnwfO>b?F~^1=4#%f{1R&hk0!7K%q`6^K-! z2~kooIM?LlWZyOHXrtdhuWH^hmG&En)4&ahBE)ZhpXc#YY1PoE*ki=4Sd7BU>}&W4 z+`>NHZFI9%g7|b%oECE!SRo%N*%gCePI=$S=4PpTV>lj({e}b zRo6}zu@E~8!|yO<)mS4soQZy)1Xext=-pV`ssv7Y7a#H0fUmFzl1Y1eGKx=J`3bR$ z5#S&6Xms(L7(k9x<;rPmN6#>j--?=mv2Ll8*?an-&aNoTI2rgYS7)#v<}<_v69%e;3iup!ZSga7~iI3DM5DuAOT@fjI4h}6>xn>I%pKqgKA_+v|!iC zEtP#_16|zSo6h@pdYU)632+^1#i8<)APDc%g))RCCdSTI+%XZw**(Xb`eb20K6SUB^G0e{Tb$V%)fJ-=D${vhA+o+gj zw4T8u?fd>c@$oy5T&&w_O$W=PX^sM77{g-7jd@{@-vut_YM(VIg?Y+bOYCS_q5err z!;5}aB*3*r+A-cK3g? zIhW-{3`dWiQ?>4u&TI2H?$q3KB*Y9n9*TGpL%1kz6{rx1k`Py!2EBS!aG+?2Qmxxt zPV=@rfMh!Ep-cjmcNC#tN6OUrel$7hv6xN^_8AD4v`<|~c^hg_S8BIolXiB2gcc<4 zc(Rj14YVb(mTx52>(5)#vnT%nhe`loSpj}bz}WD!VsmTj2uJX}(#v7dODrLW@!YXj zxBET@U{n?fK2Lb<3I>ai&o96843Omp%=A=ct`q0{jkb;6#4w9o$hW;c9GgX{R9b7g zINNe`0a7WBN?sI!fTr2uJ?64a9?!IvgC1uV*4Js5jA zK_uhje7P(y%xV#!!3-9ijN_v3`etTR`fd6t%Y_S7=yUH#oslHS7!nvX{s@%(I&}{S zn3DAVLdx$nf5O_-(*Z7ggAw$_$`@oQ54p9|J=QDxYp zqoe;$DX2K*;L+i4Lp{#@#=L}@nl#a6w)kr?6eZ@666fsc(S_+m*BOcUYW7ONDYe5x zzV9qwMuwP%n5#(=+s+jtvlMn>WUvq%oE=?nPmHuPvr^PK$rC@v#wv98v;c|swn|l<^|39yGvURifn4oao|~K%T6&pYtw+q7d)y&#AK=oW>!h92I9DEZg{_EN3Sn zQ5^?lnCkWNX*{=IU5A}9BnGtY&WaW1Z`RpZIu=B>ZC8{XNBVrd8K_9wsjU}TWm?XX!E!YO zPh54CzwmN@YTAuR)~GDU3=-|Te}ig+hSwVCa#d0M{y`r)8J4xFQSTPoXSf{HIpopq z^?YG)em2vvZzs}0s_`>Xs2Bbzit63a?|`p^&o{49$m2X<&`V9aImsvzmCwtQ;B(O6 zTCv(mJ3Gl+!!3Xn8FcFmAYO?$$&%iv5XndfBVU671kT*T#}pJ~dpq+*V^ z7W6*<`6N1nbeFVcizVrIHSKU`d!z+FAk$yBiPN;A`KwBD;D>p`EOSNer^(QIS6wsX z)@#KfSMO;ZQWrMFA$-c`th0q_9Q=i9F?v4QlU%G(sGdia{W{V6a>2?{e?{9!SPS5j zne0hs)!bei|M<>#gob406GmxjW)?bbnM%w(2_QPcDXW%_@~8eMzZFH%|4sfx<}kHamrCzcDYvTuLE5%%G_Gs zWn1V@MT;y8-|J=E4iIIq3Sdj#u8^oZoy|XQY~`PtB9^)DZ>aZm_s5>HA`xh;GwP38 zww3T_);)ppWLU1nq*zR!!=-*uR`&h?ATU-@{UKXaSJ58uWCTRNJwOxOpD$wwxc{Cdzq<2$yJg|& z7zy}#&;EQz<(UxrhmL^NSm8;=6kK(g5!lbRjA>}fLX18} zG@}WV4M`=bP;cBzb*jK~CCd}$D6N{)hAsk1E0y#B@s=%*Ew4cYwKS}0{@AU*`RnRV zLaXi&ZTamp(}<2mrQL*E@6cN2&RPK*I7w9jBdIG8P9kUX{%SkMX@OdBK4Fw*2L9HZ z5~)4Wz;1td+CPR*rzqOVj9A`1%2?{nU&N*-cBQ1>vy4KKEAB^C79N+sdk;4IUZ!#1 z*ah3fwd2OraYXVFewIHr3}&+ftb;qA#@Zp`F;lkCI>{>a4>x?Qi}_;_Y;25V72_h( zEf4MuRT=#yI%_mrq2f#`spV+0G*M+taUqPHG34^&e-@~Z!C6_oy+BS9ZUj}VqOQKa zto?MWjcRw0V_L0&!8azfD5=yRhBLt2PtVwRd#fLx{N*g>*UL?3pI>|QvK_1pg+Fe9 z^Usygc1%zujOJY^l3Ucah{=TKo`zl`=F6?VOdIEao>M~`xQ-EIiK;CTmz3>@!NHrh zJh6&7%FqN^i<_HUL1AG!1swew>-*hZ{F~cbfHbBI)rbmziz9)Aix*em_*dq^d(R>=sGo&MK{0!^ywtN)4urT@%ryrBZ3`e78HnH6Bx ziD14RNNf{8H4r1#s+cTB-abr_i5z3s2iO5R`BJ~ttrDve-Krj~opWZ1>xF{t+qB}2 z5Q><0hj(=wnTh3QNCS+{4j@0aFTvNo4;Ms{J^Hcd-sriP2u;oqk_+n$tu z-XKs$Uh1@M>rb_RyL#M*HJLhNejMq=Atm3>=tmn(T$PznJ?}&Ywz{uT5Y;Mp&4_R! zsmzGZ&$gw?waXWo-dWjZ9xLrRHw2z&lzshDR5C{_`j$AEac^qCeome=v-obNNNKQt za-+&ft0B(m2QkXC&F&oOfLh&&d9!b|gG(Lr{#J&?h4vD!YPHw(-Bnc$M^m?t}~=2?1;CO<*eu9T;R@kwj)h}JHL#Z+?ciCDjG_%;SkRL`3z8%~J?OpK$bPzx|z$$ z0p?nI!@NXn_V8lLSA1BT>`iVKiaW;EUdhbf@Q!=k03+$rv{lYt`hHbk<=*+STQ~Dl zesuDsZD-Mb3*?1JSb$x1F>uWSU|n$?nf0?ypIyEZi7+)zDrLBySsq*U*VIE3_&pyK zi5qGzvX08y+rM#bdW}U3^LKUkh_jNm`QZlnaQ$9A_g41D-3mU&xb4u6Sy}rh8z!d; zKI|aq>JG~IUcr|<3;PBKYKb@uL|UkZB=sDQnJNZ}-jV=2(2zzOHzuoBSMZ)~`JZx= zB_NMr)$`WY+HdswXF*kqteX#wipmM371OTd5SXdra@)%20fy2AcXMU(+SvFdr$~_* zygH5mOa6ysw18czGCdNy1v)a$?kC)fBlMAw-Ce7m_;_33a_Iy8J}}xt4hzZScfJ4y zaBIhW1>n(u4&l8Y_Nh%BZC}^Dz3)zX-3y_}uI35(Y1=W8;51;r{}0S7D>BKlaee3D zcg*c+-%N18bBYf9KM%4LWz(M&C10c?kapiQaT1;~Nivf2dQ^kI_9tLz4s+zq9;&>Q zA5665VyTl&`6cGhoZWG|vv4BSG9n-JH<8lg#07|@mL;F8$SuH4+B8vTGZF*n zp{KuFk^dKny#01$JG(Z|Xn9537Ptei`;7pUvT})Q^aT*8fC0z$&Tsqza9;bhCu}jD zk|dScA}~2DgoU)WuA$ytRK!Mr41iXp;QXOA0}<08@R07cWC|3%3$yN3DVyiyFX$CT zWiXVC>x~(@(zEE(4fwWlavFAjJ35ebXlzO3>dH+P5VP^?$5*FVEJU01Nyd!;ls~j3 zE2m$|k>_DT@$pIspx;1d9#9e&Yfa=i_eU<|E-sSyFOvT^Pa_Sq3<5{jT|scf&F}2t z+bNuOoMefI?CdRl9Z4xvkB=Wk?f|dj@u#!+*9X+CfFm~J<5OH&?1Gthi-+l6(C^F3 zynKz!B#ALTLROqaRMKDP$C3NDLdr&@IyySvNc1l40nIP)J*4mF|*2!RwXPw)JbX?q3eE%<2iyxRW{jGjl2k}`G5Mg0wXoTQEH75QePJ_3YE{h8@<9^l$KAsL)e zUqg8v?@;p9pg7_Sr~VN^Ep6jn;l7XTk`dzGH#5Sv{Zc4b6)f?dWv>p>ZqxY>M6! zLbxYiL^&8axr4oPCUs(w6u$oK)u*to!q~w(bQpR;*z=N!~3>7bdK?x zBJukOtnbqwwj+exem-AkaTv7{NXr&Q8et*^-KrvneO825<0FoV7q?57C@X8QAz6md zikEN6mg31f?r-eB{lFckhf5FclZ^tcS9$0lF*`;t01`BBSNCW6M10Ll(b+LGOWJ?BLxGg?AtB zc%wKh{)}29*u%od%T)32{?`Ot_~!%*mBJaD>2$Q4n(|Ord(H{w(3FKHk1ik0wYR_Z zp=lpd*k8SHL?MmmI3V`V=(k-|{Hn#-T+D{&7@M4pR>CyynRAG9R$)uzxu#W+On< z($OKCT8S1t5+E}E*-_hc0*Y7UXEXZ=9F8tsIN2+hig({@FR3VIUdXtEuV+A=cKp;m z$%5G3FG}462j#4MK0S}!dl{Fu7&iu~NifuSmi~DnNfeEyu{-iyR{JbK!$}BN*c1kK`Vm|b{@+iwLS1r7o=3F01 zw))TgS$-x7F1jq1?7!;tOGe6N*`7?vl+;CID!R1s2WuX_6F$+ZN*W!;;I+b$<<`wl<~G#$z3Qh7_GZ z2o)$ii{F=nZk?pnQ|yo@1W1slWcnx0oe@e1)|kU&>ryKu&avCU0=aVX0jKZPCW=;5 zLHgQk37W{%R`0ErA~GYP!z%8FH@eBvla+x`9SJ!+W1>_eGW;E!rU8#;#?k` zn5*CO3qFmu_2^O>zU1#Sewm;`#~fdx%!pR@!O-#L_M^Vn#iEw}_UWvm;BiZl-xDyQ z$w`(vR$(1`SJOA?6UcvtT&9(`j~xRQSy**N6$~{Kb>s!D&at%?!^YIEdl}N zt#c}~4*)3W?d8Y;GYE`maqYFpBFhkE3*n>GyCY^cm!m`;fyPInzPoWg`?UJ}a+zl= z@YrxpF4E@VJQsl?l`dy zs6|f!pSs<`VfskF&cEZ+=C;0w6zdgVoY5;pDd6M2=jK_!qjmwyb9OszUjgj2JGVta z6Z=JhvP4k`AX6Kg=!z3+(j$Ar^#MhZf-}HXHE-ulj3Ec)aOBq0*UK()LaY$3v}?cj z`vd=u3V69Eym|~&`lz`qw*U4pfk!dNh{J}EX(1ut1f}TYHH=bw;~VyeWe8Iv>QrLC z;HondNA8kNR{{*heHaBYU|MC>uotuIg zdwz{#PO06ITL~)2B4lNsd3OuhbJf|cWc+(4IxFsHaZ0p}1!N1SH;Tgz6ocxA=E^+$ zh+3lz&pel+j~u7|-43fh>H-f=^gWy$f}YK_eU@Zn5S64(QTA-V0IFHyqbVQTQ{NL` zCcsdn`;y$0Z+9zms6IcToFX zDwEU89>levN0d1}M13rQ52;=v*M(sL zT}c#`zOsQpdSSSJF+bD7!>I&MNM)*mN=-DY`;F6IjTlyf6me=g7>jZkhJ#!=CqH!Z z390Vg#&N(*8F!O=E$`}@+~bzeUiT;C!NMslSmjiV)1Q&aH)7}PyUQhziyPDRGwebIHWrd$^AGTFuw4J6& z8p%8qp-aE(`G1*Oc>*&R@&h)#{lZxH>9MbPfv}nBpH$JN=C(DGlW=gF^kN)3)C`#OUU_<*Mfpp~Fj-~~UyDS(4;SxIRpuukt+ z&}(b+<(aSvW~C5r_D|V2sO=GF$%D8#My%!c;WI4ZnqZ(JJ^Q zd;HnoZxdvzkcibFVYohq+&1CvynEZw`Nm`-#||Nt)+I~l2^r=Jn4Jc?Y95o*wACW3Ot%7prx)lTxE9`wTcXW$Hqfon9I-dY=Q9hcn|A0vUPj^I$#fhuN zyNdX&aGKx(D&0J6DuP{orj<;cxymp+X&CvSB*jZYhoJGcH|?z}O1y2lq5v}8ekkSi zS?57t3bR!~*-c_=_A)SA0QBx@CfZ(CK{54la%oEf(PpEk zd+6GLUqJXj`0pilj#~`f1J-#v*(?7*b-K^`3&-qb9^&~((m%dQVc z#1i^9hHppK*kE`j21_~oJM-7mO8U@Bs~JV|^R=GMTpv=yWn`hWf|p#$~);-W^9>Wt6bng5OPF*g}{m-{21lG5Z@v02RU zvB9@RY$3n|GVS=kKyiv#4cszd#p>du_teeNCYnLD=<@Mb-~?cf9=bO*#WO01S726A z@KnV5U>TNQVDKt?KY4PIkrCgc$qU{6Bx>_+_DCCPb!-ouC5=hBjtsmU{tX^AFN5@E7g&bviKu9A(Xft5TRgS;qM+Zv=dz{C*WN|x9Rg?;iCNoP1 z*wl&|p?Zw=2R?wH7NVYe3Dnm`K)FW}id9!vH|M~2c>B23wIjVmw2xk<$ju*QbKsS2 zke}S+Y1EE8KIJz8C(2#$$oZE9)%tPB?P!DMnedL2hjL^ZMZdTnT8>%HPCi?wPGEC@ zI1mR<4=?@wh14T{crBqhB)~9Zx%|fKxDMmLBZbJ@nMPDh_CzhD%`Q}adaa`x$-9=( zrDv`x?(84+i%+^$*PmR$i^ey;QTciwg1lJC?(r*H2B^4oe#8gXg#A~Uwej@^~&D`4|tfaYc6yCMm!Ajn3pxFNY!p-e^x=xS|M(m zu-ZJWxq0Gcj);XGP~6`XJ;D}z$FsPe#j-XaVa)J@zC^4d%5+G~uaCd7^*jUmCx{H$ z#W2YS4Yer#!un>u{(~)I{`Y6!wev@o6ndBU%Z3tDs|xooTpYqeeNrG4Ax`(7gW1ZCQR>M0AHGZe ziI%%4JsF;LE?G?a7umN%P=kiVgeXbMc4P@^<%8tu>Do6ySn7f;kI#OAcURye!K70B z)64DP{#rYm)8>Ym2+7&iI4&3r77!KPySl=wz=PQB><`@wmd&RC?RiAVxzAV{L$X3@ z1YolIND=V=-A<1m0m*6JsWUc>DHH=(@Fqb;{Ui&2TM)^2I`D`AEWiSjcTOTSv>7c! zCtWZGF3Qwm+6kG-j~i+U)%2-W!o9IPi(~k}ri(`jz9HCK9RW)K-ao=GjHz!`LPUl+ zYw0`Yv!V=*7*W3D91X_QSG&@k@u~Pl96Wu;Rox#tw)?TOGQy;bv(nmu-7EdwAchvf zh}}_kUIgO|rcBwE)luze)x*;&*6iVJauJLZJ)A>3@Lt@9(=iqPJue6u6^YnB*d+-riaBw%-m9ktLBtpRX|6)*b!& z$(!X@$Qe{*6a1j^#|HoH~wZP@iyep8RT)-+vtWyj0(A{ck@7hw7%gr|uejc!M z5vdX@!;~Xr^}2QOIlq?kr~r51&?9#oz>@-#coa}9d;~Twj?GIr=a%^K7bX#gaJe}# zb_b+Qr)$kd%v28ArHX%v7FDHp)MdgIB`l~GgxL20f+1U?8Z#)! zM)#~b#jHlNk#Wqaw874#xE?3FmJ}xbVeCmh(gH^YGyjjk?2%69FB_3zg|~tZ!^q_H zxT{&LN}_xv5iCdX0e3G3eb38B0k0$6qR*QY0zog?yOAa>8;;!M7!W=K1T_S8%8``a zcHYdao;D#zF!-ZL@WA9Du;8Wbxc^(*C?Fspju_T{&C9pEg8daR#YkYe`1uVN|FW_2jkRta zA0HBZZsq3dY-s7~3sdAR3Ve`5+?NuXGzycIZrv$kW)3>x^+t2Wex4+B%FX*!dV}0tK!(rD0f!L zG|HqX^r@^6oB8m@T364DGvYdW>tu9yxrq-;AaVszIOCMB3stN?-`lBf{4!eWbt%^w z^6{@~qio2mG8}5l?1V>Wa|IlnfMh#n@>cLEza6KLdXSJw_)h6_jc0tRP8hbvYT-nA z!o}r2Bgg<2+1Uw8oZSOLYYrs%TUalaSH~B0?D=qyxo_TY$7QPuolJ zGR9Mz>ei9JW__>9s*QzNgMV@Tl;xpp4r>f){`(*ZvRGCAyRs*Cl_9ZC<}AZrW&En5 z=apk+|Mx}Ac{Ivz%l9m;GBP?Zw83DId8Ss$zvX(%V_t^6Y|M3r$2z6gZ!@Fe){t3c z%=A6}wKm7i3*PtEz*Gm*MdMcPPt|XizhFow5A&n7Hpb0FkS372F|+wHUoxHAbu6-X z=6U|bc@h7h;DzMS9?5l=jhWvXrxs*(9!X{tC|sba9q(~4_#DPO zml>ZBq}kC|vNp7r*TLvyTy25OU?nH}!s&^&y!7YOn<8)cGA}=^9bYkZjmHxnKYnUt zRjp_=8oc}NyKHW5vazv&kb=XbL&7KmE$DU^+1}pfhewb2=9_QKxOm>zj~+d;VYf6< z6e6Xh(P$v0)fDu8U5Oft)n7ES<6(J8wdS?t5Z>C3~rwM<9>?_KRsDVJ40qZSHhcr zH=o%%kKoO&iCns_;y|KQCNt_=gSkgZJweAWpD!>WN-t+pp0`pLFBk_r2c>c?o(-c; zcC376MY*z`A$%BqWXfQvY1`$geMeEQs(UuC{c_o=HVSo@;GNf2CcBG}!KBIa{RK`Y zA%{l^(+RX%G2O*&?!39pzkmLSh#sNPOeb)39C0>0;bgMTjk`O%JY8a_$E>WabLZac z3_t&jQU4Snl6+#c;aP`=ha4Onn0pm0oc(@(=5oAVub1&ZZDX3JmIow4aEfAap~=?D zDkrC>?57h3vOz4&p=xDyl)`$c!zn>-niS?#l@eww!ysh2vxrLih{@Qzvl4C9_&Q!) zCDT>O5*kgaG=XdqwAT3MNuN$*~N&UX-eZz98J?bTdbUra5fC&M*lMry zr>{=vEVM|1RaUQTvvTbYd+9FS#V%(@N5qp6fd{M8AHt0JNE6sp>wMAwxJ8pw5BDv< zSSDY99#1WnF11y)TVGCf#?CU~DH{jfyBUGAAjoA)W2POSz~_yY`XP)_3d^Wdm7;Vy zGZs}N?DzRXgQoYt(B>F!Ie%pvnV@9yN?{zRqd>5+y2#e%3V}{}`0cmsz1*dx5~P}< z)0n|okJFP=q97!V6DH#^!@+=dGs+f^(gvXc&im(_o}6)fe1b+0$5Tebb0Rc<^U24& z`OcekJ8hmmeahDMHHPOSzWDreUVGzpHa9m}=yXUm3??y2s_6H6{ORBSoo6pzFzEN$ zUSH$Jl{N0%xW(IVyvEn}zoBovGkrw?M+~rYt-XEW5}l|LCjL?;lC|%B+8>Z9{33%( z7cNxEWl|^H`Z#fXe9RYLe9rRnGXLxU_P_APn{V*y)hqV*_PKTYE~DXq-suTR60^A2 zrPXS2a=34T?5D~ECU3U+@{gZ$J|6PP-~J6ZcXp6b6CnfR#e!s^+vT;_UgP@C4hMUO z?Ci=ffTg7_Yip~l zuC4La{ckuuF~QfB?XSC;I$LjUSdKFitu0*#`HAF)ABz{eI0VyDp>SgT?Wv zs>`p><*ick{dcB39P);-p)UE$&GkSjlXjnn_V$JJoXt|5Do zUl7n)YID{<=AFO3$L`@S4<3HQLcB=Q3Ru0j#p>z`dtZNzR1%Sp!EVMXSHiLx@n}*8 z`~T0{n>E>z9Cv=dh|D}o?YFA#zV+>W1JDFOkVDLf+n7eQp_xXK$xILW0(#WPk;(Kh zV|tZrIHZvpjc61I3_ze80K0)k-`jUzYu}f%WJb6iV#&y?bE+FOC2+f{&dH37@Q85# z`#(H9fKtO|zhd#$40d~g4{pB6-Fpuy%Zl4KZn1N8Krio*m71Nk-6$IoMTKGc`X!bw zEwlFEDP~Z*uE~Xbt^qvglnBtmN1{cZ9sC8*l>wy&>aciZzV-Kw6x zXPRC}-xbPAF=$(Fw0{rpob-8H9w`a&+qQL@1Dw_|I85!&+ih;wvu#h>wo<8j(aYK< z`O+q0+W$g*rr}u;i9}`EXCife(==LlQoGNSReRh`lJg{4G{^)cU<73qY6PlMAR4ha z+a+NeuAkptFT1Qs0UHGUW$V8~7-E8D+)qzxCHTIEL}{e8)H)UXmU01A?+S^4DFT)d zL=o&lltTO~MBq6+<lDf$ljD;bZv(^OkjqytGdt7g`Sa(Lqmo{y$HkRZPL7Yslwy8v4l$PX zSFboeI>wZSqk}`9KYPyV>M9p5ETK_6d-{|oj~;V)aNyGVh~T!#`+k(9v1bNoY6>Oo zN(ula)zVRo-#xaQ=9f$>v*t{uV}MCzBo(gTi^7mGFga^`LZ>Xq z^4qNMKV&%AV`Zg7x9IZn*&&P%=w+HBE2wOj(FhJ64|sL5%idAJ?4{f2?hM_PYpDJM z%Htz4F)m6!k%RK9LrT?ciC7@sQ!h|N8|Iavb=!-mbdD(sDyh4oVE`PcxRq|QeO4j6jQYXe9$3qrJGVI<9J9D|flHSza{JazZr!@UTd%*) z?Cb)SF}(l52P`cw^UIGvVRv&AGd!hJWGpQ$(Cro+9!pd(bw3hMKCjpQ&Jsv$3(kl`B`6ot@*+qepD-Z1bkQMW#FSdmR=R7TMT%#g}(K zXJ>1j@BRMo^S$r=E}L6BeEQ2@G8~N>GEX3i>o+T8I!9?8SC@hh5S|lA_~6O9W0F2l zs;v@T&OEI&%12FG0KXkJf=KZW#W~}n^49tcFHa{z6kcf{5tVjw~3WS4F_n{9c714Y_8u|WXX;1D%|>$y&0;xtaw=_x2{6CbF?!AxKEu26ouiYTr3 zhbtvICKI@>mF+05^!fB1X*5w=cc&nxUC*#3EXYE&o;zEw*6)H(%IG6j1-I?K^=!0- zsUxA1ZCe_Y7L2k~wsHsPXpO;;Yeh#X6ot!aNqzX8QVOdqrKzYYL#{QQtN;|l(GZj( z^Z(NSX%v~#WOlkv?S*+alOL&BZuBn-l-puH$Se=!kT*X1h^l8NZ&Gco^V-T~cAjprv9nD+a)wVM7z~~FZ*ch=@3V1wz{!`-xq1C6 z^KY!syS>8l@-jdFi~pCw%Tu4TRQagq$=GR{qtc#hZIWx!xNhsCHaR+pfJ*V#G=eH@ zBL~h9U~PTyRTwe-Ic;8st9_FDq&iO0gI`6+O~M)}*_!?Xw*^co)z|_*soeCtX?l7Z z0ov}@_Pvcf!ijv&E3>`0H%U&Zvp5OoPmAh69AO%q8=2ms3BOa0DS+RsONIe$T z@eg03=ZjQaPz?2nKRu36%C!%ZM&^VHs8DOCXC-mwty|r)B51GgVs7yUW)Y9A(gpkg z+LQipg*y9bEuA9g-48$D#Qg`JHJ ze*W{1dE@qN3Z>Y1^^za|{SO!n2Hd=To7ZmLWPfkpV?3c8yX}_uxdIb#1-PPg>e&bLvR7 zehs&`?Xpt!Sr;C3)l(%%BvjPjm&zsJ_Vx?vn$`W>(tPzj4gKV}gvehss~_(Z37M0` z&*igYsZob8zuWaz>Wr9ZJ>za|-j(9$6z`n37}e0(pLX)`(tt6aVL4falk49ZiMF0L{*P!&CN zzwdLJ2izP8O|RFbi7}#mwbmc3HbG`F&p8-B9yBg@yhS7a(6lP(!}vB=UJUr~^SR|l z0sh>9NXU7sqkSZJz(vtdOkxo@J0;Tgop(7st*J`k_;`RKV|X%PG#ofWQg;~)1)a@u z<=Qftu6X(4IihD6j)m^b9OKe59F7?chjeFWSzKJ^U~?Q9kTL)OAOJ~3K~$Y`a2j{z zk<`_SCC3V64ro+DW6(JxTK^p)a?Zbmu+rEB5Q%tHu2Npmh>> zO<^j|v~s5*=SA-GSw(7!G+w4A7M%>IwWCe8CVr~lQf))( zVu(30RoaBw7%6;|i)CC2YwNo_e*BV)SKs2j_upfEeT|)$kI`8Up769~i8d8^mXT$- zb5IeqvWPWkpx^EA-S2#xH{X1d;b_d-t9xv3?Vz-CLbX)P^j@bkJ7zd6+1=jb$3OZJ zfBG-~ly7|aA!HdlTU%tA=DqjcD90r)o;_nUIOW}U-eqQXj#tlLGAc)WFgK4bI({p) zBFo&NLsezm7!pfWR!}O&;}M8U(%A7%)?RQz0wa}Rd`c1^)e>e!6=IRMR zJUT*C08Ds3I?T3-3WGBdi8GmeAV=G=o#up|ad4H>d)7Nsu@4%Ss#*8810TwtRHtd} z4N}T72lP@KRf9$vvY19H1Q~^>qO@M=GqH~G`5@AC;jBg@jj?U*nepuaz3&gdKqj{C zuc`G&AWCo!Etav2=`PRk(eHha`%fNlw7<`l)l00*USRL}E_>?-NELF&xjCVVoX-3V zo%s&eZrx(% zH5WMC9J6T${Qk-u^UHJ0*m>T6<6TbH_j&c;36&XBWg}Ei$TZ9reY%|<Grhu#q#f4ey#e&1lBX+7ob~xb9 z#p|5zRvhaAvsFP+bWsSZP-H7}6qlA6&gC520XJU1!S(OI!B4mE^4dFZQ(W$IvVM%1 zjLf5Ekp%0hv(8GJAewYC_@wJxyp_(A);*K-*CcE-N&im5lhbr!x`n$P-FYq{7K~V` zAL6|d@x|96QPb}y>9*7fOn*GSc>?VF~%)4ddGcIb+(8OfTYh>-h4u-(8}j)9&o;T(`%K`2{ZbW_Yo^%a8v0|DxOP)9-b;cwveC zr_UIT#`OC=o_%?bhYuccc(Bhu|Ni&+@)y74Ip>{8H{`r8FUfmS0363zjoo53afDmEft0+e`&u<@GWB=yUDQ5L zY(xtpt3deGM5Uj;z9{inGd>Xh-;{M|TBtuN(FxY2S!jUd*g@`PVV6oQN=3OLzE0s@ zsqcxtsQ*4i0f9#r|A7dONJ~=*1pAUyOZm-_??Bgk$O7jUt+#q`1LV zA(cn-{V@X9t8Iz$Kl~2D;F=(pPOTSZH@70M4o4iP06!o&)MJIX63>KmX|K@ z&U^2XfA~I2SFZ8o*-JkA)o(aG9iX*pOiE*kj(u;_ zipnhXlhWD&!W=|0m*0)LP4S-yrZPNv`h@N6Z7yHE%G+ewVfDSNX{ge~2SsTs|pN8H}m7 z+{P}!>7-yV6bdu4XrtYUavq@w$+jGsq*=}y%jwQ^0`TH{>wc}b{{#awSwBrDEE^`` zY(14%CMxKaaMHA@K~Ve`Kv9SsQCL$ik0@~4!aZ*v9MU$iCL$i35uFt85qV|d9mv=x z?w9tK&31cJ{XIz@jj=CYqJEpP^8H=MvP_WEIaixVQLVPuWCFyMTvY?11b4^hr;Qo0 zf=y0!505GTf&eBb)w^-xXCmWhJA9oSL78B=wBJ&j+!9MlaHLmgrw!@8MOe5nPjT%M zuVf4T{yb);%k}Oxlr`MBcANS8eRhAa2PZDsq7|@On7@31Ti<(+SM0I$`Wp=HWn8}5 zWg+i!<Gu!2aewMX$%T>zC1e zVNPkT=z`vCpD#c86@w=VE*Cu8`z75$PH&;hl^aVGmuBdi5ywY|lutLgb|lF5E{AJ7 z%1{G+0RJydu&7a!+lp~io>#GrPt*~cY)R1Z3V;z zM=^JVWk$p=;RJJMkTpj9Ax8q4Ptt9*!ek1SsTf>sMOo;|c8h#I0C72AX2CE$TDB>c+gAfEik1UAF5LF-e5&)d3tjBn60WQlk zKvRuNw?$vXx68o{>kl0h1XEV74c62PbFLLNI20c}P_;+uPf00{i#AfL^N7Qv1NQg#0Y$ghWBI}o z%PXs#Y;160WH3fytqMGew~O95Qj>uJFF4%dWNXCwNU8wP{1^>=>*cEaN^$7_h&8lb7~{_Gl?`@bYK(?_+XdPUW@C*dysq}@V2TqL3! zO2Ic$DT;MZrv1{C0)ez`=-u!`>bu%keb#E5j%`OHy6-cs7@r*S^1-i>tZ-Yj%Q4%> z?vNfC4%n_vIMkZaXy|k-!tiL9r}zF5R7PPcOj&Y#y3eDX7mNodnd(^hGLGb_O7-8-!g;T;W-JEdNM1V~jQ zgw<|0Sn@2#8XHeCcIC&X&klZH#>DqYb8yC0t>gp^t+mfDXVD6u;$Ovgx=VWu5)_l0 zG^0v*@^X!x{lDe$qbK~{?|zSW-u-~txp^Mlzt2y9@)O3V1Fo+uQ;tR)A0A^XLw|M- ztqTSx`&8wS@BQw#S-rT*@yQXN{pvFwJ$b}vP`P?|KxFdHnbx^K-N4Tr)dA%Z-~ixU{;=<;$1II|XmQ_YULHi2IM8@Pi-xh%dkV z5?fVd1Y*ZaS1eXJ!=B-zHA5eGxEx#c3b`XREIyhht!;4lJZKBxzpYAwV|r$h$Nvk_5^?X3KO4A37hcGZC|ao zm#bF0BTm%1*_QCTO~5zOXp?(h)j}W?rW*H#@uVQhl{sBk)qNra2dOkAt2Zuk^Z+E*xv-@;UOtQO$>k2RwLupW~&9-pK{_XU42t zxr7mmEOr^}mDs*U8GJs}SfMEL@SpH|jnxAr6n{{UMt3#(T8GR3>^j-qFDMNB-oN=1 z{&BIxi^Egw7spgGrkm$jzkS`7hC6THVfD4ktZ%;J5C5A#W$SNtSbhBx-A5yt$Gq*G1{==WLGM?kLxjEil(p zd%is z$5a=(y!FQG=siny;z@+yqO>60q-zqXSC2d2cV82CrE_c2=U-KaK;oqYetP@XAmpM5(VK^LO zjA^`2;{RfZw2Nvapo^Lm1?s%C-Zll@socvZ)@M>K)k(4q@0fJ83Cr`yapi5a)@S>$ zt;gH-nMR~H9p``|6-L9BHT?K=m;1*;k-5}L3cna5F0v7D9;2?))sPYx%Gq<5Ovt6; zFxrzGwZTB)bDSw%k3SDywYLYEKyC%?pd*=bX`7S=mARU^KB@f#?YF)MLdUr()Nl|pLeQs}y9*IVeSzRf_x0pe$yg^l#RF#)e_@4xy#mUu%L$8z@+g5UsEZOvq(!L+;Ib{2*b`!Uwsi zKwhf)1Y;!_C4P5=AF1MGSJ}$x5Uo&I<`0+)^ULd3+!j@(T-`(*i7w*KaB!d5c%FmK zT*teVI9c5Gsxw_@I`EOr?iq>E!vXhd-$y||8XI=8rMS+|f1iWB66@RPKC7zIjc29m zvXw&R8P-L=kcnRqa<#xDBubD`#D(sa=uBe*##FUkXdF_c68lAc)R%IsG^Y z=@nF%0}=3t05pd&t^YgMp$;|XaB}grQgtAK|0c?>CZTbFHC~QZlDgE|2l4GxQZKf< z`CRV|iXse6^Vqh|AMW+JiZ$|%ndeHR#)m0>6tT5`Y)cp(qIHH=kXw+_$*Hh{84MWt z&IubLwHTx-L1_vgN3g>oW_V17kkyHSJ?^gk_V9Vwh90lDEuDz>+`^V?rxhDXP>li; z=6}lTc-!2td!ZhlSWXE`KY?q0Dh@eF$_ah0k~?BueH&e!YrEUN+BQ}BQbIpyvA&LJ z9b%)rP@Din#BnnioYwV1s}HM);~_p8>k7H+c&A9T&JrhDsQcAQ4r1QtCC+!c(m6=gYglR4Qk)2{JXk+q?J(jl$E zolxqsI=J#n<2qM?#h^q}#O>8MW#&mE6o}R?cd95f8apgG9PIMh&OXoY-RJw?|2|i) zU*j+S{r}0<))u)|Jb(3qFTeN=&t7e^v$My+;R(+kKIcFG_+!?dJ>z%Yf1A61vN+;Uc zbOz^v>GswB?!2)Si128L{9dIj%LyaOtwQ_NM~Hc2lnBbtT{9T-$xr?PI~wz^|Mj2q z#_Mk|9*%f??>=`w`Y5J86XZF z#q<(FBqWt`Cv0X}7Eg@zUto~`5Yw!R_A*RPYd>skC>fbpqR_p%TM{^ z|Ng&VX}rMQAKqp2$qreOqbj(ld%V87iq<)Yx@4?!#=4@Kv)DPqNcpXm0eA}jV7z)8 zW~h=QE^9@sP#7zWrQ+KAx9Q#N@#6Rerz?ihMa#kVKCk-1Pj+{>aOX{m@gc)3n8GfS4-_)8 zbQHY4xWKzNuW)esh&SKtvb@@3<-!VPm~mxU*j*EPYDB4KFhj%9@PNVynk<4yo6IwJ zxR-JZ2WQ9{XQ5CAC5B!JcNQ19GGDO%?0^@aeoFahpW?ajc>6O(RmJCQAd)d$+~l;s z&;R^C{TtRd)));|=+9*+XH&(g#=oP>C%O4{hg)Taj!x33oIMTT>Pp9Lz zufi$q@7lIB9hc|nf@xQq$edJG`_PznozsN~yd6k$F`lPC($AvPX`P*Dx4{Jmw1tDi zT~ln*8C-qNm6EMRQBV{G^Yilz1_MVh_~`N4X-_o1)Q(bUR~r2oBF_U}YrXi4t@|oJ zp&^$k{gRg1wuRw3OK6_`-q}EEn;pIj)X~>)&f>ciSY^=`PBYC@Wq4%)sla0urF?{@ zgy?x);{+Um>~*s(`|uBGHT@(gM#N4989M$m5bICAs4^B5|@u36e{yt zCv}dMBOSFTB(%@03yMr6k~cbcbINTAcO;p{1-tnDF_BONM_3`^B25K%;=6xm76M6a z7^f2FrAwjWkG2TW`rSuD62>G%{=~6NAeelVsLtFL#gHlhPr#~x zs|7rn8_=N2d>obveiJcwIzIK2+nOtW`#sN~5#P)z?^5rlU@Zwnf(jW*pARe3zgyz0L?&)9Fkfu^n(Z zuFX0j!kz5>v)1b-A0**|(_;s@L7>Fd(+_;N2;l=eysSf%xI5E8d0ZKygKe~_NLVra`l5S<`W-f%gwvCiN8eVrAF^X{GW{Y2+35GG~ijF1(y&vOyLR#b{efRt$K07G>; z;ImJD$$WQ~Kl;-@Wp`(rkAL!0cDHuuD8;K6uUK4J;qdT?rK{H{M`Lz(54iuEFSvgF z8cUb1v9`X=uRi@18*A%`aUfR!tYL6`!r=JCfkF!pA3P$@a-Khb&fPEWvcI>-w?6ua zsw~O#oP)gsY*}&b`gKqlhsVb}eDHuzKK=#wA3R_5&)h{_-X#=z+BFR82SqBbE{|lgsn{>K#^L^4I{*ILGY>Ad`@nX{1d` zY^H=?x%2W*2fzuQO(* zUvW}aU+`k< z1-%<{{KsGaj4!@?!tv8>Zd_dA&C_|_SXv}sE$9>j3O#1|LXT~u7!Sc#uFmuQd1kxw zoSt^*b!X60vcGjep)}pRL$}BsF<`8V3KapXQC3jc*fu3jr;7?($0jU{sQ}L1!bHys zi=$nZ=Vp21YM;5qt9<#z8uve4M~`~+$*8K5D_55I^Z)YuJp0Yx((jk_Iu$!>TU4i~ z6#9VK#Ve?4KzTAmREMl`_Bcl)M#y01oxcD8AOJ~3K~%&?Lrqeo$(TyV(O2o!N#9Sy zSnYe;2XM8YOLIg9;wy~#8jCgSJ?TAhuA%&4n9W?7Zn#Q3DOmX^q1rW~v2b)oJ|1|7 zzvDrFt-6Rv!xpqtDy3zo(VOY>Wm&SnzwZvlO6)_Ff$v}K9UQlrM>}UtaYl~q$n)f7 zvD`L^+`cC1=v3~L{!Z(WzNam#wvJ5kRUppL)J~|!%qZ>C9ZipVO`EF1PBrBgz(-0m z3ZMDa!$26-;24`IB8LF8?W^;p3{8&A8ooH=ampoi^u&OwyAu3q1Yw=oel zDIz(c5p+CI(fZE*q4(9a>DzVqUt(@ z9GH;|5B{FA(H|oC6G}M}Si9(KEkWMi4K{*-KX(@ET2;_(+%An4v!6okOo~Iuweu;o|Ft@LWy#D_>Eh524R)E_2N^kkJNfqe7T9B zolf*o%f4(C&hZFi40l+wTbK|!Wkq+UORlxcdtp849?}A&!mgiOC{Urv99#h`jXXj~+CqB#kRiq;>58cGX04#I02Bx#gES}%=K){&X` zPE1dZxFw3ea^ej`P(+wSf`Lg$dqQ*z-no!x?P5u3$<6)t=d`K3YGC7#u+a`XBk>D zK#`-N1Zy+IRG7hte&&?0cE_7D9-X3ehRzhD;h2#V2m)*$4qVxpQwF&etgYgzNHhlC zAojF7^hU}Or7S{)v2Z*x93LK`v?kLzpx9a8;$Umnoph}f+OOn=r0CIk)*4k+C}TWu z3;;ItajJo*%XwO~Q}{XQV5*dmmXF!#)iL5zmR$3Nw0f1iWBJ=R`rfU#_@Z}NA4`?nOm9_wqb*k0e@)!I78N2mPy^UpXs*yD6? z#KzV(_a8jK##O`+b>^LN6xfQx-CchC!yoeL&wtKvFkmzqV@$=vd-wRyb2AJEW4`#} zF4cI%gNOH7xUkHZ_wMuj*)vX0Pu*$viPIE>q2f+(`v6=EKz&E`pwFlWYxG!I5-f0uqX?T~~> za#qz;$I_86leKoL-`Wm#TK+WIr*lJ1kkzE?JUwpwcAND5?XuHQy8%jT;{g|rNfBG;C8Ze8Zh zI}6--zsG?Yv$ws+{l^t+pFd+myR)y#I|m z9G~p5a$yO>9G|W~XLt7nuYUHJ+1UkVOvlYqEpCB^=8AYc5CY|aXYTEz)WA>_YhONR zc;_3}dHu#6KL2US&hai7OF!#!_2vzh-dLeC z9B?^b;PS;Dx9_Zg8S&|-zhdTS%+lN~Mkgoy`JeqOdcAA>)qnT_Uw-i%+wJgp{eWu^ zce#H34&#C1^G~0%xAl?_-+qm1yu``z29?n+2e5VXrSwjN18cb_b8$N#(!MxjH~bc0 z#MwhLCOZ+TT}!VTF+V?K^+K1|Z(iYdzWuxG931hFpMFk}Y5E7p?z!zI;q?=lkO#v0>g(n3J{v-7U=#$#J2OnPP-p%VrPKEL2D!Wn<1WS5H^JkTf8?Gm@gzed+LHz>_-G-H{aTJ=#C#y!#*{_vSE`#cK=+ z4l=67^r60??MmWbWHmktxk1B=gP!!2bM;id1?f;%^#N4`IjXSzIPKG9QP=C$+OBJ# zgE5^GD)xQwWup88MpEJV%un#TZjsnXv}sH|fFck`A`*!k?S_jZ(Lw-Gk{d4uG$a`W zW343V2Wo=NJ!hjFC63Y1Mj()!$~Mx;dHScWQ+y)v#R<`7e&?I-pV$xBe1 z7F`m@xEhBH>B+p-sv+x|`1P_@b>v$ZJ7LVJP*J^=m>sf;lr8~d2F-wiwA*B4pF0S?0D$}{qDzyNEmg*1(h%cvLGVTjlcxhm-;aOXw64i^}}D61l=C@lnTqkv<8#KE(Ni#@4nx-sSVzhy1<(wz7bYK2KGJa|dc$9wev(_7$IAq*Jlf>(b3M zh1QYiu2Vk*TUC$hujIYzX;9BMKYuF|h+m>CCl>KF{pt7;sBK}qCu*EHhKM%Flb=#Y zoqi2ynY`QG>~cE9Z9Rb;Y?Thx@fw`V-7#Nhm?kk zKFNy_!{F{y2AQUVaI|&A$(PSC*0Mmw?%n(B+Is zJOFQ4#aLcExX=E}HFVa&R2E}OKHl2GDiw2hY2{3)q8^jt$8R*6UPiN8Ggm6IvZ7PW zz&PGePmJK;LULN+^!}SfYz5#NUY&VINm)Gk_v$H%R4Qm-0D=p2n|kZGz9lKT_mD`7y1IETP*jfh4FRB!EI~vPHotl<`&m1#u#Sj`WPcIj~LF~ zq&h0GM+U3L{Cw>vbeA%2F3vJMc*fg*@H-6h8Sbrb@#@#l*}cETV828v2T%tmr))o8 z=gnJ}d2Q|jcefAOda}i3D_nW)1HO2o=BBx#q=-bw_M9tta0pl^ zR8F;Bvi%QRJlx)+yS2viM+YqQXPKW-93C4wDr0~9h=2co{E(T>n9qK_gQx+6(~`_u z4!6hb9&d7dd!N*DLYg*KP5YAtg~J)ib>U`D~17%0GE#n*1hR0XMihd4edo5V|Rs zX^U{_bw+neGE)=h(FJ2_hckTp-Tyrj592;y`sfY#Vq@x&T5>7zCJB*J2Zn^d>v7}W zto;^R_e{t+=k#q-hHCYRuF0wR28F}gvDFHzRIHm)0V{g~kg(Vr+7*fk?MlahyUD0j zwW)|J!_F)pBj(4q*9-2Gdfr50*F9rG8$HM8h@fUIfT~KzKuZK(o}qMKlCPhR_c%f^eH)R`3$lv$x89X#2G>xrew@{% zg{7@P2sZJsxPT)SWvIeuD%D%z_lU5GAK>S3mbn5l|Gj@cz|CmalR2Sv zn5VU0pimySCA&AhK*Ip1Kv=)uVX;B^U6Y|@TC2EAQYn8JpYkpBy445NI$f)j%V}&t zXNtU^)5)BCU4G$Omd&u3M#rCf#hD)!Mcw~4q)15mCJu_UjNj6atF;YrE{Cx$lWuH$ z+A2$Zb*h0i`xTo9 z8|8qNQ+|qpShfa3Hphl5-+GTX-mA#6u(djnG*DqVF=HfCh_Un+dX(c4S`?jL#}S4` zlZ_m)DU}N^*O?<1Rh~f(l6iBheH;A&QpO6l5_AQWhU#d3LTEJtQD%q-Zhp;G>&KBHg=92v%Yr`Us2%+>*Re?(>!9G8@%G5wW!?Bx|keaQM9 z^0_W$CfGuebu%(kGC$j)$}_BAy>WzK*oqUzv{zKhDP0Eul5vxoBbBV|d$nWZed&1f z1|zVK2ud>dUQbIK6l1ZRNJY7@NN@E5fA+8bm_gnr=Z9!Bq?nn5ZpY=v7>_Yk<$&T$7djba7F$@1l#nY73X7r2g(?$DEiOs8_>&!d z{urBq(IB~-tJVW^T`BTXL8Vb-(1p@u7$xML9OnC&(HKe#!wOYdaEIEXb%B(^e1C=` zGxlSsLX9h~4=a{u3uag5868(NPVJ5}$;}0~T6q3KN0`42S3@;R3}D5IxE0IHZOKIC zwTkpzJT2N}s>2>tq^cv~{Y%et=-Rqx+CgUl5VXH5%d$z@2_7g(+*;DE`+3e^yKY}~ zO(PN8|I+VLaevkfbqC zs@@7W!GyMDIhkB;)~Q|*tvyc#Xt8K9EX-IISGzoWHKf}cvA8&5btz{sE*OttG#GMn zx{Dc~kkh5pIY8+>wl<%$yVhlOsiNN*kmsi;O2l+AqREUql*0P9E76El1RvPq0kPj2 zWGc(-Y@eC_GA~{{r?P(Qk#Y`Tujp`Pd5P}$2)#Sv!r~Ht_%A==+5DK9i?>lneSZ9( zeu2#R4T#=n(qb8I?{KTg`NOw9;PWfL(VU81HI6ORIXmdJ;IrEDP%q}Yin+GT~S}b9~UW`zUD_w-X+q2wdjH3=<$eOe7ecb=dd?|(WvCf%S|j9RV9eEY;0Cw4gbsk`a?Qh zO{Oxe0jyzc))M z9g&5tSV5Q8>`h~y2YnE?n6+k;Sc$p1C%k1Wx?Tvvf+`Iidyk8V@ z)`|GP!yFa-meMnxXmscN=236e>d!;uswhujl8!SKqb`GIrb*Ck?J7nYB{Rf3w2F|o zGRQvs2->G{I9B|RGp=r2`f^kniJJBqo-hg6ylIQ;d9R2$T^d0;)Q7Nm+1U7WQrDJv zL+>;Ma^`#G#z2t0NVF}1IPypX7b)r5>7p@1WLfP`);@@@l}_Xn<(;U76mO=r@|d@w zXTjMo)s%32G|Vjkm4^`+@pF1x!x1&dcMa<#pf*AuLtHS?d-CSM51dvAkB4ANjGFqE` zBO)>KHMY^0C$;cE`K`Sk3@WLk=u`1KGSVN?5_M!aKz602FN8dM#c6D!e<| zx*B&XBxZX^4|t8yu}L_wLOTLka1pPyVvG|%i~$L4s3dM5Pox!fcN&F{sv}<;e3fr! zu-PG1gUg@lwWlc8ep?4Eq{Ze~XKiLqkHGcUz>|65Mb8L6LJQONekFjXstc|+> zMB4p1`3mpKYn$wUQO*e0YAC6s_*3PA>YV_hCOPtPdQ5Th!)KGgX8LSfG$}JX3G|#P z|GdthP_NWDOaoi#-_c9+6&C_g>FzPsy5z-#?J5S8qBLVrkg43AM409r03-{ONhqt* z<#-7vtceBdA|S1?jI8BxHR9j>>_^NQOA+#I;LNsbWyu7@xI8FI zS;#CVcXA`w*i5<83Oz#7nMQIAxtEM}Nt1P@$%mR^tg*(mRqzS5t-+QCPD-*9gU&5^ zm7yvPha=3%i0WX#g_$0Mj-l#9HkTvg5^YPgv78K!QT>8S1_Ui+71Ukf!HKG!m`PVO zRs>w7B7hBsGIU?)r%tzU?KWqp`XoXujFXA_FzAM)9_QJMRR3tL$@4r$-&kwU5_ck0 z#*pXv89B6_T|(It+K@WbUqxWZ0w95PMe8k{L zV}~!Xra+fnzWMIEeDv;X?Csn|Oogg)lyN#utoF`_Mu}S{u|CII<~LT=GIe##vVzTx z4YDkAk(o{*`-4q#-hJy1-~8ZpPS>8Ie)=hTW|oDw-$cFqlD>k=7iP$01hNDI+WG|N zCS$RevG?p54?g{rd~En1|A+q$Ieo$0oZ{le4%zGwm1oQ_&+gVS*vc(tDR)A7|H3>= z*H_p$Tt~0;D9tJMRP*LH-sb6xr`Q5Ivt7U7k!=66)+fFXa4j^~$1r(d9mYva6(Mec zGmuqUK$Br~m!ayA%@$PVh|#D*E9+7$i2)0}g&Ag6S2)@`U~n|Vh@~^rp*z<_6(#m) z5ZBcLso^I;JveEO-QRZJy8{sFJa}mtldzd>$s*=Y4djYyo>C58f2xRF8`W>?8<)1s z=b@sGu8vNz&LYtq+OKfiK&`5y9@~pPlXT`JdHJDS&*P-NiUeb*rd>&8<%m|IGb@rf zY2oqoe(?*jF}0AzDwns-R0f-A)Qlj?V25K=Rye#Opsi*s6_yfufGJTX11k(<3`#Rw z%%hDXM=SAtXKSZDm5H$iBRCw8$Y|Q4*d)B2mKXcidn#$J(RP{b+^bQ}UNx!-{S|Ku z<3XG~w+Jc{i4X!n+&n5lvgmxP;GnsJe1X2+BA|>LTau9nyoriVXV^-a`RE=I{2>&< z5k3nV;+TommN@RhT(9Mn*iUM^mX1wn_%wdr#VxgGaY4Ems1ocf4)8SUIW+}>2ux_3 z>oyhb{Mm5^=_U;c5)j*zNz4;i*QJdy+IBkla4B|~h?iQjX!yN}a~S71iTaJWF}UF& zI3he%x3g|zzy%U%0@e6W%cE9xImt)<)pamUpq@h?y3%$0JMMsrhb1-3C&5>t&s5qu z&5AaBthmKo(Hi4Y-xtQ2(tb`QA{6|(t*RU6eKZA-c^$6ma%%D??UQ7e3?UA+*3;2h zP(Ds2!DvdTeaE_kiB!CDc&(v>sQWYh%mtApc0*B@87D#3LRxt*U3o{bPhQ% zi?EVqkmrcUz^3j|-(U5-JnwS2D4F|FU>;K@S|J=bCP~3fY<|`cnf~5pP|X=klsw*O zi_|h~`UcHwl0O};yjI|@HL3B6r_A{p*eIS_ytVSKl?)RqQBSwu{z|ii#PU+b(Q@#h zX=uOdN+(KM_CNJX^$a+}XAnd?<7CX3-pm|}t4nnIU39KkUt8yJ_n2_PU{FMYr8mhu_NpcD&Y8Uwl!7M;U|%5r&bf!@pv zRVQa}q0ez|hH>U{TZ9C@B7*fY(qhTHqPKpMq?6iOgEit;d^OrCm)BA(B14c7U9Bk! zjpSgI!I~rNk$8t!V$@hL3^5cQP~ys4sWjg*i+s0#nfpg;d@(v@O^qlEMTNmy!|d!V zrzfWjM`NVO80I;rS%J+%YHSNy(3v~LX*{ejwnAqaS*LKPdHSvKF1Jur+dkS-)9H4Y zneBrX4h{|}N2MdB{d{sOMwX&eptYvc>0*pwcydfuww&-naGZ_j*M)8aqfV1UOt~hSW@OC|L?eNRIg||07oTAF$=H9r|F0{mBvfAOHY> z|9{yUYqppXhazV<+yIauu@FFG=|->b_1o{eYb}}nkeO9kb?d&yP)-KX_f=)(a`NQa z^5i-8R4&hA>vHfZl(d-(B_;^56?#>Y=E)9n#-&YG$wnq=W&uqC0%2pC$-pR6+9bYc zsDPG^h|&O=N*W^IoM^Ju84#(Ey^|5+K4kp{X_%oJAe)jjPI)vt;B%RfGy;?`xPs02 z=Y)HsP&sz9c)hVIbmt5;mwO6745vIGum*ZZl)X*PBK>gB7QBFb;aB_KA0n5as)oAi zx77@=uxRkpkSB|&Qw~LiMjMYLoh!=A6iN5(Mnk7lsgUHmC7o=GKNoOS((R(y|(N4k5dSi88ntD%vFPFu2fc@`SWu|%yOGnK&;{- zJ{}lB#+q!2vo?SAqnrHZll#2%+B2NSCp>%@aP4}ZWO4wi#l3qeKl;fI>G*_pvxE4& zO`@LS0?B zfmRZ&GZdP6nlMWfbQqwdAXbWTJmYjeAyJtTcmoqI#9t91G(jNpWFy)h2)lzc9|uax zeXb{&!x=3#z11$4p1#7(y#vl&y+Qg=aCm<&k8P)=aqiybibPVm2<@!#)kj@m?4;NmzxbN*fl8 zS4&8OGyVjD$=)kUMwPr4LI}dZ8~~}TkejLpI^{iIUc)**P)E;vy14Ldg=PZA5Ky5D z;3O|yn+&YnhI=oWB{RC)E3}^7WaGp%ZV)M*61F7$C%5Q)X^p0g zxclBcvQ+WRmoKn37_j&A`#gB>9-(ZaHAUQg7I_X7*V-1>hEn}yG`9-RDxHTie>9*J zMxLzTmxCRe1(?>2%|H%(HPBs0b^F*DBLgyFJ)xwRU`JWy-^%00!_p?wkk(rkt6&S$ zo3MsaCr4E0SRO3-T$UCGt_obWQ^YN928a^wd!oI0Un5g2EAIOLf^75{Sf3>$ES;Xy zV{4xm1}_J!FUqI=vDC%JUsl{+&i~GPE}@MZWFc7k-#D2iZ$mFkQDCCk1NrSqVcT@| z%Pr_q-?=_fh@pjioa1&->etoD|1cA_I)U(};f#)DuQI=FzsUsm@kw+rsXMT^3KWO}r6u`__?Jjs)JU30mN;-PLa6b}L# ztnQq{$OpTRCy*OSO$S9-4xl)#upK&>$^5tQJnf79T^s^vkkaV3!kHwcR~zT~mE)Q} z8!bK9YP^c}@1Usd@TM?C>-?Rt2LlpqV`rK$OtH1IWMJ$q`49>k?dFt^tcJId_iYc( z{Kc|EmVv$bV{s&3ORrraY7PQ_+miEPHwXV6CH^I)WNmGY@pzoa0C!`mJwy`17&Eo$ zhr%CI=LqdO^@XP#k!t2xB7hzP06Y%_N}IzyEY5|_W-ez<%Ky9Wo4WG2x{qVLc|EF| z*=|mItxN|PWEYxFug%8AO+LGGi@|D-X0L%50+~H+wtWUsz}BU0RG@gU^MI?@uF}c6 zY^<+ya(qGB_8EuC z$|j^W+(~J?N)=3Ik}SyRU*4p#q1ZimKq3NK?IuKm)$5m;-{0Z*?jy7oWJ0231eok+ zvJoIcL8H^4-|KU@e@GB?7_6)yg_PdRPa*o+uMpA;hZECA5Tgey??w8qebveJ=h z=P{%phzg=pVRRC6Z69Hk8eLvrx0iG_GsILwLkKQs5wErdTe8DWE8^#e4>+98NfYB4 zE0u{;BEpDyDrU;Q}h)}T60|NU7;-|8Aa z&VXF^yZ$|R(ywc=W(+p&vXIP!0jI5J__LqQ_{yu_Wbfz>@4WjF?OvCStG~y}))jvG z?mb2aT{^9pweu@nc>cfP?Kj`xgS#K1vJp{pkI76S!VY0`ot1Of*}8a*JGXZ@e0U2H z9J76Ki&rl`L%4fHs`gBrWP4JqWl#v%rDQIEHe)?Hopbcw9oAB~(cNPDDBxHB*Z1jv z=N0zGV?OzGj~8#e#QAgQI5?bh_{omRZzCm2r(|Kq!7yfjyu<(dC;y4nOXKwv3OXwT z;-g1Q;t8F8hgi*t`VCZ9GCiHq3_GL(dqun8Q9f>*g^P8iiqmyQsk8RPBF5_cne(ug zl7+E-k@5Kl15%neR$7}l+vd3&Gbv5{?2Rv9V*b(!lTOOhPi^zon=#qmn6B>G{gyJgu)?LUUErg}eV)1g zGSNoNrHhw&JY~11WqX}y&LR%i6E2&i-z!^F`99dAlD)Xu{4A;%M&X5UM zZ)j~}vbp0;154*c6#=DB{^`ZUWV&R#4m3wvyvu~WCtHAtXgICc~h+@7agR3up+UI2>VzH}A(s}K7 z1zF5JM@zuMZ(+%^VlG-#D~i&eZtA(0r*^Fv-5{@D2=2BYf9$2!ENqK@ROyQ{Qn6UI53a|0J$&!gTw+oQd0jKkpd=T^YN+5Cb%k_t1^bYn_FF8tXM)({@x9LfoFR#)9wJk@~_IA^SKlozX*Fn zanH7Bw!CiABBF6)6DEmqR?tS2#mv{RFCncR``{Hkk2B z()lL!H(ymas}ttFh@rDOF8AHCI&}tsy16VA?fqjFMz=vnlL?&LKqWR%tQ2UeiJAd~ z0ws;7nG_n8rR3DxZXy{4q@-jzMMNMXh>4lbXY^Zrd(B&sNr6z(J3UqTzRUTG{a>Ip zK@g&C+)adpAVMKXge21t+O4QkX%U#V zwDT}BCjn9jObl(iM%wgM#C8?vR6&-a2sxgNQI|Iu_hP0e0a^q^QGtRxHq*0weX+5tzyBc^Ib7AOWQ0|+(8!()OVMClBdEJ);#)TCsHhM;$@$LRhT z#)59Yiej61DvLr;N)^KTo z7bxomo1JEKHl35P9rYfylG#IYHOn+(iWD>|Y8e7{zer~Lo< zNUJo!O-q_A>UKZ70M7Y_(%#RC66-5*%am|oHKam-EsxVz6qXsid_}HmJkVOBl`;uP zFnd?oKFYH$gh#FFy^C_J3xaa{m3ddc^OcX1p+RIyzmz$B-t6 zgc>}fzGJd}jF_r{qORYw=3lM13`W&?RB_cki=v1i2$;=grEiU~s~TFNJpTey-3c#f zEtsW>D_5Uk`^Goe``|-1ue{2qw^CH+lvjW6_t_tJ>1=KwPu98i|eX^iP?_DRScqTTj12IC&Rl2WHUwz%>7?=d<)W_H-%=G#+_Wx{m4!man-Wo`8?2eXXp zPrgd4+2`J!&-lSlKj(vcj~EYos6_C=rw8155VCjhL!#~`qscb&RMBpnr@0B8Zj+;< z9eX87LZXR}W@M)^Q#C?mnl5e9VN4dJJUrb2Es)rxG$@>++u_8(5sYw#h4QQ_QnFdqF;)Ii6P8i z8M0w`Wr#!yg~oUUB`PzworES3l7@^>+T0{kN)QH3bZB*iR4v6VzxeR*VP039@iq)Y zRHjhczd>Pe#u={s+ZDp`Ya#M0z&S2Ey-j;N-5bl}^fmwAWimvyO&=C@#@p)eO0PK` zD#NqfZdG4Bt2|CtDfM1q(~ILYG|2Bar=;G;Wyc>o)|^I&eBXOhqrl?DzKgXm?U&~5 z|6CYv*Xt2X8ziHQHZnUd{-iNO?kXqQ+mG4CUANReRfT>vOLbzK# zexJIqRoFU9gIZ$ufE{&+N?g$p<4e`yc- z^PxHY(ZaMWf_wk;P3^5h;>q$#85-EYUjODUo-$m4s1+ksAI^1c(rmo64*iB9IY zsSSQv*;6QGZ`PW=^Bf$Gu8Kmw`fUxFXCEvTxLWLi;+3#-)>`L!PPN^t&ge=gMB%Yz z!ZUeTP*Hl7Q}lEV@$gD+F|BOMD)V!tbb8<+Hg00}s%Kt*N$1OmW}P9S`nPZ7Sk{Lx zpw?N29ST-OZH+mhoo6Z9X6lR6gonF3h%g|GBH}pV{MIHH)-SMoZ%r zeL!oa!{+&O#7WG_@sRl}X0SS-({3{v&d5}T(&oCa7J^h5Pd+E3<(_!XT%{CJgOUNM zfQ*b-#bkj($siZ*Q>#p5T9XBmEY)N>HHm~QoLp=yAq{cKRF*4pg&RA09RY-4hzvr~ zOp|3P*x>SkHD(&4i@A~~(F9Uj?;Sy;72E9=uWp>9eQ-)gtP{5y5X5MeGKmw0v)IHO z+C3x_8OKRVC?f)8H>!jn6tuc6T7woURCK#tGLBSeac2c^xW z3?U(qf;3Gz8J;j$9nkDFIXxMXsFZHI&7d=2GMx|yG#!zIF*20Q(>ZaLl4hwv zm{2oLXH3%A#xo5}AY7XbFY|?R$5?O|oBX4J07Yo_veLx9%e*kLLZmLc)f)$$;4nx6}^+AwA=J6LZsW8{NylilmcnJ)Cx)1^ig-{tX&_pW%!`(w@ zZ6F(vNaN|C0~4oJ2!U<|$n!m>_xA|G6fL0B?$FyD@aW_K8X%ehDT+WClydtx2!e%c z&!V!&^LN?_l9sq?tS6H~NNt&m^+a$%yK4mSe9w7`tY1efg|t^lm(e$0SoaN=V3?n8 zEY-Rghs#=tFtPNE;ndF0W0r1p^6$L%|GzTczC+KZ6)oc8_fBzHytBruoc7|3N zHe$X}7PP`YE$}f^iijJ}yug#seudNHG3}S1=xBiIEuBP8#>XbhT?ASlNW7DAZBH>!wdu9PBM2;We@XZ=3Es3D`ew-*dIIkPT6 zD09%6_kJdAjW##$46`=tqzi@4PXwni23$?V{m<`l=}E01N?!9V(!|BO$* z{{eU3|BObnMRGc4|KSdsH`d8|bM`*`jOqM{?TvM=bk`Y+W1=X6nTdg$qDryj+??`x zDV6VPU3qU;t<0AyYa7V&Raf8OZs1T>!lW$A^e$_zQ96`48VC$^=h;{Y3^>2Ym46`^0xfNTi8Pnq_QkuCe{hMNagH z_WFS1n|qAz9wOTj=dN8~^Wrw2eQ=BE&WJz-WIFQD&?Qf`T_*!xw+8&&B%3{-9iuB>NutaS#>Jh4X2KB~$+ew{i4Xg$xht161C zrb4*-=3UW_D(SU_!63(i+M=l%6SV*4OjuA?L%6owS~v;1Afx3|^|DS~G_Ach{C%}# zBKUB0jrrrLPI~cs0gJIQ^L{J(`LZ^a=(vW>v&oQtE`mYFTzVSR#rw#Ix2AQOB?s{&Kqw zw3IYEZDck@E6u^)KC`hTYDXL$ACb%wM)8R8{)lXzk);|FPDe629WzcQwEJzQlc~A( zEMPDk((QJcjpn3DN@Tr4v_;%BS29Gt>4k;>O{4`6Y9BW5yWCN%wa zm#EcdI*%ES#^@|11O|gHVH7c$&6teG2q26?qGppIjF`=4OeRx|?CJ2rdTyd=M3Q#9 zfzrZcx7LbC3Zf`P+LMVmHrI8~?(_+e#w#!ol8ttoXRmD2dEz|rXoBhoh*63_p`{>b zG#K>yw4yc#hx?r0*kpBel}8UBF*+TAf-K3{-Q6WPk;c(cLo%PUdHy`{EM{^#rWWX{ z6K2*}AWeiOt?iY4jDcOa{be(EYq$56IkDGH*w^kr!N%LuW;{w~Hh8B*YfrG1Iw!_C z;Z~5LG0_$jZKb%>?(&@I(cK#(h?vbXWF~31x`d4;r}Nm%T`A&t&gp1`6p}Csna!sR zk5A||dq^Re&u2`=6GNvw1xX0GS&!&CBcD%Glve1}1mV^~)Y2elh%M1f+m%qdi0@_%bmRh^MwcSu7vpoZdl}QH z8s1#uoH|CbY>xgsAJ>mxss}~OjHiz)#v9nc)gf}#u)NH0Sbevw)bS2KR^T!NY_)%P z-L*Ov|NDc31DhB}l-gT1K5ge*C@0Pv#qebbM5(zEu-a>K>GE~5I7aAL$KcBl`G$7Hr(amosUr2n9*d!-tHp;v4+k9_U`YKB&WRe z)u;L9SHH&J-TrID>=4;X3-K%$mYuZepwR@4E>i})@%BBktj$wTwU}fH zD(Et$g`k6wA?85I(tbN$>BLY=GyE`34!XVo88sp!s9G^Kze(@u7G*idW_Xke85-Ig z>4-puWRnaPn8UQw`4F^;W2b>YMx@h(;rJLCh6F)qGOLeLMxzsB)FZG72z4pZl#fr^ z3HmPC2h}}WHe?p!zTE>kgp~DkbZ~NtMWGa#Hcf~eb0BK?2|}2pHOjn4yQF1~u2e%@ z6y9YE(wM-+!37(nUwFxW+`?&8<|SbA=)_r9OG_}TJga_NNBb5G8k8@^_*MrryLv~kai6GWtuUVhP;{%c>T)L zjGHHPW75Gbm&dT$2vV!B$PS!9>Eb{TXqpIalk(c$3T6IvnY#I%#igw@y1lf2O8Q9R)fUi%h*^56d(RzyhH z3|V{m92cIu$gM}8kUg9s2}>$e6_2#mc_O_WH?(IAsbLNMexE=F91o8RzT>uK4cxTb z<1P$~lj*!$D{hS{{%eJzUNiP(B{!@}U*TT9zY3c~+56eNu0oCqewJ?{`UXiC{wKdu z=4iW%Bd3em7XLbo@#7yGKWVhEP6FNy#|m{7!XA=4+E8dv3$V*)wQ!x6%2)N@pY^^t zSg{*&fut5Re4#53E_Je7dGO2nCf|7r2k)+~cV#}W?+v#!WP5n%@Iwk|_lE~5?X~S> z5Xkd2IP@#(mmdh>{OIyQ7KvV2lywW8qi7>1a|`(B+IKiv9ZZ#WiQ2titz4xk`A*Qr zVZ-qnHy>A2Wnb3fRh9TIvgMr4nl`<}Tuv4#D!R=_ZZ_1FcHG#&I@}cW$xp)T@TDxV z6ysc`SyaOk2s|F2>zj+E`pn>|-COxO8r@R5tnfKcavrlq-wS$)KPLZ~C}cHliJ26F zCLsxG^;Y8>LykPgfCM5A$6;L zFC|bO_tq51DD1Uz85sC=Ivx6J9nwhCXf-)G8Zw!VnZ{FdEm;IPGIzRNf{rmtt*x$^ z1VMo$YJ>8*l!S|NQ+QqOyeRS1$6(S6=1Z z)+K)Qlb`YS+rKJsLbxn=&}l?GcjFo_z4|iKIOA{s{wEwB9x~{(`O=F|bK{w(*grVs zhd=y#k~CxU!Yb#_Z88}2kYT`sdpq3!VS9_6DJ9y(-Gl3pwVhDN>7=bPIx$fNPr|u74hkPgtAGQgvs=2 zyiFAXL9^YW6?K@+V!|jO3__wPWHcFZ^OKK}S|9^qjI>&l&NB{okMgYcS(-9D9WtLM z%uc5W<*o+UG*B-2k4JWW0Wr`J0eu0hO3)t8iRPNkvE*teB#RUiOuR=e1xgu4E~S&v z93CqWCia?AicF^_0f&vNrqT8avguh$$;R3m+gqE&Ny>wrT_%$mt!BhvFkoe6K$6DX zzI~f0XwYi4>34c`noY9#l=~Qv z0doTF^_%O1Cep_m001BWNklPzO4%y$6^tv;YddTSL4sZSKXKY`&PH(V{-k)**{sW>WY+Sj_umA3Iy3v?+t3x(T zkW!&E%ubK^%^N@F;+0o9yb1Si?+`L#|Mn-`Leie@617I?`IInI1QgljwJ2^inAmYx z`eTBl6HgHFkoEO-qDhPxCEPf7fy35>d6FU|$ROo3eF!q+7 z&>)01@$fLG&02sC1Zk>BGjs5vL~-(7Qoy~H)42i+BG9BFqZJuBN=iv&;>w_$Vau+u zKof}u%DBiO6lVNZCd>UsRgGp*Q}S_%Dn>cw&ck0}55scHlr`MqJZ^={iJOl%_b$X7 zt|5%4p_V3#xNwF?Gni5wFRiQN*A}*DXb zWyX}>zKEO_`3m$G`_ivoa2WrvOXA|<7AzqU0-a6y?$=-AOV^vU$Gd#z@|XFwdYd2r z+rLDsK5f||iUPj;%q70{@)PuDhpe`nY&`QETkF@@x&MggUcAPw)hGBMxn-|!%OX~_ zENHnib?Ew4hMS9h3P7K7SIeMl!RGxO3+kH!x18A66kFf9tY;SK(ahx1z}ApqJ8k0|RNAmW&~bBX^_rWL*2V_Vl81 z#5m){>FaKW{%Au}wb6y82DCscVfca2=HN#ZP|S5dx5u?8WKHGX%LSPGbEto78}Vh7 z{JF($QP4?Xp&cQ-@0|+n8F>BLqN1PM2J&w!Eql0^Yk(ea)Aj9@X66~y7DnUGSLZnF zLA%nst*msZ6;0uQ<#d|T2JrD?LCm**lw>uz%wAV3*ZDF-KHeHcuI_<_UFOlslHt)k z_WwLXRxjx!`45XjHRD=1T&o>dO{vJ=uAB0)fXFK@@Z9Cimg*a@lyxn?hy1(B9$MCZ zncpsX@Be+4Y*5#jHu98|0+E^bIX`#u$&vwx((JrW?16+cQUe4sAP7RT%-qJ+Xd`3i z$ggY0SMOX=JCIoE1)GM#er;DE{g4!u@`^XJxSL;+bgC(tqD{rg;e z?JHcndW|RuNOejUPuN`P@Y-uH^Xct7yz|bx27i@{m0`SBBB5xBgd1137@WVvJMaI7 zqvInqDV?U~>tB75gJH&7zj&M7-AByh3C)J)xf|EG@$|Di*xBd*{5Kz)Y_nRSM9N%E ziFH7$9ntG|IGl})fRUw4MhQYd)G$H+3EUaw0S2Baz@3?dpqgKUOj)uQ`9N$F-|hc0FhbS+G=TU7az`_W-G@v&;}zEk48ZubI^; zFRs1ClezM>^^($SNL@jHyxCSy0hgWl)s44Wmh!@yzw72!2d{+_&BquoOT4ryD3@nJ z&YkmDh7Tw;=jiYr`}_CML*((ukS}0*)RWf|N9akPm)&lT;aR zPGKx(Os5Cjy7^O(Ewf*jmeiU^ zWNd{QPm;2-yT|^)T6xpN08 zB6Jp$CNZPYh|x4*7VmTa-Vv9tUniKixpQyGJUKui2$5JnMzqdMs1ENcnOUO(7iDJX zY9Is>%{)$+Cz>!2G&M}M$oKgI4r#YQ8i7V=MTCGXBTZstrihdx0w>8Izvhq8Uuuga2aSpVk8lT3?y1A zYdkVHGfdLILcvu+8za_I(W}_sj%K-MLTbBa&g-j4nC9wcSyn68`FrfFgLKz#-E;r9 ze|X~?5th}@XIG&k%RFQ95eV-e0iZ~;DN!pR2t&{bA+XjNu-a6_Nd&2&69ugHCEa!k zdQ2!9^jmFuoet^UjF#xpYjmwMwX#>?gPfP;hb0b|ta6Z#Gv~qN1gTCz_Hoh8-|;l% zXnYOL^uH5UIdRwYtFUySDXvh8+_T4f0>XsW(e^|zAV8hW*x2l{l1WB;LpoD|XbMsU zSO#`^ z-}-WpY1#4awj~7N#^S)Z$>PKM^5a9Tm&i7~Dj{C1J_Tv{dVxFlxm{}*chM5yItZ?a+Kj$Ijlt1B3sH$9=Qyvx+{AHU+qw@gqb=Kyp0JCgj)xMV1 zJ*!^bH;*%@E^9X@jOK}Bnda(nqk$HJAdGNcu2Hi=*a*==6NVvCBP2)ybn4o&&!Yyi zX<|#JS%L^8txn6txty77PeKM5Z^1I$%O@T-A&DEMltfX8(y2)@fQii|0ihL>;gBSr z5jG-Z7=jManPzfyL>9*;2Bi!kOiAMz$@BzNLZi{hz04LitWt~)k2pO%V0G&Xji`mz zk|asE`}r+~2Ybl&dBU(smTHD4WA+b*WEqSn6Yf8}j|@WQ^O!Ic=pZABGE$wevE_x3 z1TvsM7#ITzA?;QJDI>EM5`-$QjjXP&FlEkgI;PX@6GROLD{CANkI00k)$byr02OBl zRa~hPF6VZx2MA02%oWini0CAO%R|A+J_PeNtD?d1DC2%RCfd~KKy&@lMZWW$Kjhq{ zOZ^P`YBQ>Zd|{?AO7J#B0=ysfAa%A`sgEi-3~9k_yRAz@(R0q2Ym1Q|Cz(X zL%?{q#qpd65AJa~I%REbjaI9LR*K>A5qmrL=y!gX%a<=Q8BNI66t_P644q~4J6)c= zaf9vc3miuuA*CV-rgWd{6SpLf&IO#DMyQEET7!P3G$N1$jeslHE^{ z#D&Y386F?g@AMEHa{TCoNCd_kTopo3u9_?}{#YxD6T)(M2O(kDFjv*{^%NxmUs

ZKGrd# zv*@z>-oIvD7h}@T%3gg}2&{{YuP_J|UYT`ssiX7N`T3bWhK}mI zYv=lyckXYQ8*-|3j1m^e1St!pA16#no4HnJ2%SMD3?C!1Uf!BeB?vUq+yo{I6k3mv zVoVrFnj&Kmr@Xe^<`2Gn88!TzY@QLVU*`Tt_c@#-)|hFo6+49ht&K;8Rv} zBVMD&`tA{}Dcrw(Oh?ao_j83_=_0c+Cl6ze9zNug`MX46lS~Odd0)|LN}7$-Bt{7W zPLB@w-keN!6TBKH4GqP|^rO8ev8#O&mb26@eBs8ZAT+kfsS*7fG2~%{Jrl zge*-#X@LJpcPF7mzohzb=K*n zDUCGXiAIAI!95MJD6%H|Mj>prba!3NtiLl<7`*b0seTf1bZF#*OvjSXdz3dQ=xHM%P;X2UVN`}@ddleG)y znVwD%M>E1CKq%w=SE!S2%DOOM z>u8Km^L@IqOony`OLhJE3c1MNd$d02argd|i`Tnc>JPa2=`PPc{|dkV`@h4_e*PZ+ z>94=fd_HIQ(Uko|$<=F5FiQk?A3kI{8*}mE^UUV~`;TTQ)j|+hgJF4oLI@Mhv_L4q zN*ZvUE?X+%vquwpb0hms?w*nblGSLH)$Q}Nw^j)5X|@2pKtjJ&ht*j^cdUqlfaue^ z^k>=}EGxxyZ^&$K3bFI5#wwFo<+CgY_+=R`mT;nqFE$C6@a9-D09cvE+=w*v3L&jb zLoJV8fd7)cQ01T1{j&`3<2bI&SChvW$iFWPRQbXcRzfpp`#kR-6BHv8id3Ye&6Ms| zmu9EM!Oj7#Mvv9r8l#;N=`G}ZRW=l)M$Ulw6oPlJks8`FY=hi&h1vWPZz0O5V>!0UrrxQzpr`>a?*Y6)^kUGnLQ76w<<=V4t2aD@*)_zvcD-TGM2uAE>u@n z1!q)TwzlPWwoOL}wbzc;##yekkFos49rAc{t<1ZwJP!BD&z+uDCelV0Ew|y%EzL6H z5D2wSH6d20#akFBl;%nWo}64o5*8Pyjv3W$nq_VLwu<0c2v|pq>Ih~XA$nX{7^h_( z?$p{bCZ&o1+lPlo%ZNt4tB9JYQ8IV`}QBwNJ;a`q|h_3wB-M|7JAtq?Z9RoG~7Zuv1|dEjEHp1gL6-~Hw{=(O7W-H(6FhaY}S5+}5KU4)Qi zNlGZBNz9`)B9w$tK$ZxUmNwR-AWc%n(>Y-?;P&lXj7Af*y%MFQB5H@6+c-~_YEF`& z6|e=uUW#+^fwTulfgqkIJlff#6}IU%d(5X(&;e3LM4=>&6LZ3=HT(NVgxv;#Xfm6` zB(sdulL;yXS`lfYG5(THdBls)SY|FR=8i(6r6dv&=_ExY0Rc^rAzH-*X+mhnI~xy? z$&~Atw&{1;##1(ssPTw%oi>|KKEN18`pa+G(jk6wj#t7o4_eVhJwMM&&lx-K@=c@JenO+S z?&(}BlsF>lGQN8!M^{Jz5iVUkK}JsY?X~Kn~|j^$(+{3o^Uc@ z=_;K9kyS1j*VxYAlsR7|Igk;-yR~BjXh`aLqL9S!W|$9VGQ!Q zSE;wpOR)vL_a-KBnKrpS9b}JJ7rwwf9FcAgQpNCi#<-Hv67MS#L*g*tmCCsLz2I4 z%k;9d+O4jcYeLHMdYAFZT?H;1{k(?ILbAHiqj9N0^jx1Of9Gj_@#fEIj5>Vl55B|g zH$P_YuXd4hZDRThfo>{peB&v$zIKVvhdW$+;s(F^(XTlAB% z;{H!=5hMbsMV?U9g#;>Wtp&_|gJIqNuPXh{*r>JBP@D|t%~*)a>^-YS`5*6nxj)ls z@#;)EC`5f<{{_X-|2q83$5HBc@qIq#d{i|y2=9G?tL1uCy5jdy_k3A-T%EJ_%>Q5I zadwotdXK9MXUnN|Z9LY1sALNO>HxowX^>>Sx(^ z%bq*KMbV@qh$_z*D5Qyl6@?8WM5h^1D>88avJ9EI`P=Wc2}(a1PwB1qQA#tP#f(lz ztgH@L8FZMB6I5p0Vl-s}>y4Tm!|n4lO_;|qQ5X_xNvsqqO;IXiGLL!i=zzw1A8;pvoiw?ne4n5D3DIH7y%9!h1b4El`6BeW_`0TLe*v3<3}-$HNoE6v9Z7s>H@>GqDC; zmhciYav(&5mdxjhTelw4?)Eu3$(W54N;L?D&6X!q$Uusa{kAc16 z!x8On%Uoof&B$V-;2PDwP#CM?huf0c2EJEllnBs)K%^0w7HAO?1|gvqgu-}Jj)%wS zG^W{TT9@sZWHRM+Z=dr|K9zgb&L$J~9zI|^JVeMAQW|5tQ)s1>oI7`(et&>ciosw& z6h$-w!OHq3i2@-d*REbAj$>Z^%2#;#)mM1*;2}FZyG*As0TPjDm@AUwG1EpyZ?J(> zBV-T}G#Vsvnol^MryL!gFpo2ujV5!QF^e+}4^M!QB-W%c2#oi$**;!|N=wX*BZzl0 zkAF$jG7qP;1eT%MAnOugjjJ}n2jzq>PT?_47ugbhgX80huWUJn;Tz*h2_+~D%Qn2+ zf{?jP>8?JnV?@j9``^?t7H2f@jg-q2lxiPUgF{sTt=2CKro}0zuax>s*oR-$%knG( zMO~?*waW|})wT2ARr%#IA!?bxx6Bw^ttTvkl;Txu4IaI5;*-1H3&vRX#N{V==GkZX z+Bd$&>Y&em_pkmH8><7Jx_*u4o_mJtPd>@d-h7j{fAKaiJof^xyzmm;PM1t4+<)hN zc>C9=qan#?&v-b`Q$iGh2+XFGo-%HTn;kQlTLYRPP0;fZ)4e^yVFsc@Lyc)lK_)_4 zQG?#!$(3+?b(&=_-MlIr0aXK23#Ma}@(g$;$Ipdk9 zp68GM_>Xz&`DZyEo)CrsSFT*)AOGX;^2#eOGZ?HeIvvw$wfN@Ozs{4_t`UX--DVSp zGvxMpnuimT4vA({+xpNyl|H2DQCo|r9>lX+C&prPf7cX5T z2m&5F*x{|W-eNo++e060TuPvHU_ha4Q|zAOb<2H`J@`e+S+uq^{+fQO2NH{lMR|Vy*qnjG zD1H(lB-2^UwHq&T8a>NL2iIt#nmEr7)fd>rod7O>;J7RnA#$#x&Fvk+soUvl6uFrUBhF;Q4Fo zJoiGEXD?~yvk@yyxzL%hd9F!%Fe1v@WC)rnLXC@?2SVFuj-Ilf55qIoRPo#W7WTbM zc5n8FX*XBA@pyXGh#^F-=$3Z3yG;SP=TAW~TvK}C|Ni)v8JhihER!4jHjJ#`P|e(! z5yE{V7WR~bp+6%bM6Mo1fn;rCmG-p`KRbDsUb@b`(|Ziox*RtS+1HQAnkjk&0TLO6 zh%jW0a3G>sdK z22q&%>vZEB9$@473-@Jt{=#Vyg)Zj!k1lP&V*Sl{oJ3&X<%?W~Bi6X;iHyJO-EUL!_WWn&g&ST zFgGSdY3z`x45b~7HSSb4#cK`k_x2?v6nCB3x1wlI7-MP9QoOxb<&Vo`k1tZ{%Xb<0!7JeN+WwuL3dF_pS@eoMvDmFg8D zSO2W?*tylb_xF?g;kJ`f8X+}Vnjj=>U)bi(?tP??blM&E5BE{?)Fwr+#9Rv2&#glX zj!%x+Jike&+o9cFM{9G%q1kE>L;+cn7S~{%VcIedku#cN%&CHDtVre{S`sxE%u|@h zl7r!l`@ejLH$S{ZDniEN2}&zYMhV~l@mu`t-J2+_8I8s$5wNp&%=iBGr~LHo_le_} z{k=VKI4H~*l-5ksjGK2J@M!oqM6DjvBqd8TGNpL${afrDoFZk!(czGj;~~F(=Y4h_ zJ`y1fQ2+oS07*naR3dD)N#c~_!(--JaqsjLCZ}A~niu=)#4C~=Ibx16M4I)MIq{c) zq}6ZH=r%dObsi}X*iQ~}Jdi}A3@kMAq>peqF(0T~7mh(d;QQjX`f zUJ{~&=VcgbjfXGEgSgZ@1Cl;-yPm*uKDxFTKRUc*GC?_HW7JgiBjn zJp1g^T)KXZ|NVdaKj`(ly!hoWapTD+>9)HZ4^Q~n8*lL8haZtxPcj3E429<4;U4>+ zKjeiMUbNoUii7ciw%6bC)i$ zv$M_0$^(A*{U7kX?|s0v8`pXG=n=cSyDTm)@Pi-zfVug3=H}+u+}vViWra?+>jd)B z+VDcIF6ft^5kYx&CfBi@Eg>c8NU`&5j~iECL1l_GHoWrPH)!r9Jh}6Tqjri;6&p|2 z31&lP7SE%GUH10(*xTP@Ze|{(QqUQ~XiS!26i6AmL#J`NA(1)Vowj$Z1*Yc#AcQF$ zczF5EzdNpo=4Fb46X$|nc@C%0gE7D@=OM*?Aw;RDaXGD^1$l|3?+ZE4w>xi#HqLv% z&WRBMVL~zjS{E-9oW03@Fvsdgcey$4n zOXPD^2Cb$6l*K$2uGy28^OJLBw6RwHB{?7;b7Q4B>EpTGy#dCH+8DMpF6&Wbbyb@s!_NXaAEMhvDL04m%`JVRG7l4rw<&;JuPabG|Tl}6V{co$B zO{dL;f{(Zo^X&`_8`JYkFx4nPbd+_aaiT}SqOn;q-JNfS? z$8gH~<+lEF+k&UNw$K;Cm2#avC!FM1L(xAgn~xdDO!UG1#rOk-M@@deOuMDk`wJho z;=VD(K~-6{nsx{^y`N=W!v9_7Rra0`xbRBH4SYP^ru?j*E5k&4i#ZI12!V9030{~g z$fgB`w9(eu>;RJj-ZVqsU*ZRG1~{*1X9vy)M?H%IXFBZ zOBKy#l_(6*LQ#xwu#+oIO0v46CWloBazoF`#y26Iu`Y<2@W!B_|ol4@&~w}0>_JbXN)H9t>t zp~ue7HoDgVd@=)rmXgb_yiT%og{^kV+duka9_|}1UO0;sP^-;Qiz64iD%bNw8aCNxbOl|7$U+HMMkJY7>)=s8|zIeO{NUHM_nE~S|v_GlraobMOSC+ z^g77(O?ra?G7RV&g9;M%jt0mWl8UuB&_+AAM#Jj*2G6#)U4kH$D}{Hyy2r|chXhih zQjOAzTeoiUyWf3|F%W1FnWCx{zdJaf-51=vc!fBL8Dt5K)-u`FJ|=Y;BmM z)_J1(9Ig2?^gBJe-N($-=SgZa1X0YO{}|JK1|qd{W^^uPYf%z*OhNIE0a-?)g+>`e z6xZw=xLxH>;QVhd03%Amn!9}FT~8lQ0?*1<8Q3cWgi~peD1=)aU6~e3txQW#0~M!z zH%)PvCUBKO%xP_&c6o;B6{(Ye(n;nyS$~iNr{IwSPwLEsW#&nIWg3r|1Y=L*8 zynYd*6(&n*Z|tyn_dYjXei;+R$RK2AXPck@?cZ|o>Q(;aC;x(WyUnA=>!g9i$QYtJ zBB^7#Ju-GtSkT3QQ@}+&i^V@}sc~S}myAPn9MIbZ8N@V`8t=XHF5mmfA5(A5^TqAk z)T&jMFJI!PKmAj#-ndE}*Z5!l{lDk@`J4Rs!ymD@xIiV284QPPZf&x%a-TFybEV&I z=fDnt16JZVW`1s-YNd)l1QtM}QD=5;hMPC9GdnxW?$$16XBLUWisL09qX-eX&<@66 z%#?5hrX*qt7ZY>PBM3803?g+d^CkjI8fhOKFz9q?&9#VJG z{Xvf>t6johhrIdbo6NRmP&#v&iPf=;w_^Y+mhTqIXUQtqjhFq?tFID4MEhBr_J8~* zZv5y~9`CJkwASaw>;*2IX))N@r{C$I_J`d1Prst}&Usq;DhHc;m`4xU-wU{T{R+d~ z4!xrx3>DQlqF$-f>kSxbMZozy~BMrwsshdG`&HF2oi=`&>sbaX~fadh`3&7Xbhime>p4)>alPiI_^`9v74qfOWC0>mKMW)9sF66Xa4mPGt;z*JNtqp0Ky0XXT zJXf4_sSH2z2IpFA5KbN&g%zIdNkroUt>zVW;$8my&;OJlj``dF@=Mn4KjhQ9&p4Mx z1heN+sb;7Gj*cXa?ip@gf0H}k{MxQn5g>$(VQ)ixSxad|h>#&hXoODj2Rc zJmnZ)4Vq${^LM83v)p~$fRK_fj8K^a*K%3kAF3P2YP>QmoRuc|xR-yQ6nf=FfsSdi z%e3#t!s$$TZjxW;avZMd#<0+w;jQnSx{=OK-=GgOsgSAR&Vfl^)ZhgJY;5}#q#npv$Jz#S;~V4582(@qnwSsWT%G*6%C*?E;+FSzoj*F zC{xJtI`Cdd0LJl~Vyp^2Zle%>a(c08prPGxoN z%9C^!Mmoh96y+MHYZ)PP8N!Z>2SExsS&MO+;Iv~qGJ+iW@U=*c9aCehKEd%dGzs3F zBwtRG_g?hCdfKy-GX9IcchYmyuIB-hll$u=+-92oFsT=v4wOCrdS2a=pQqK8>r^i| zFOyGseFi3^{jx4$inS>OAgvrBjX-H@_+~sAD-rbR^IYzWuZ-NAaPmn3D3|;&-%TYi z7lE;1`$XX(Hyr@>GziLj0t)@3EJ>H2*I;G&DY~t<#2lN4iE%#G9%Bflh%ls%5ETt8 zWCPFerw5%L8>kZ$<7o`!q|0FPfcPhE5Fw_=SQ{(nIg0eReXqQT%7WC%<+%7jKK7w3 zOxL|=vAwxX5CrH4+q$+N1-< zpCA>Bu>djc4ek42++vf0!6;>8ZI_EO=Gl{NzUlVaj3S0Y+Dy{A+-{fo&3 zgbjh^W3>PUBgw}PkO4*nckf1)d;AP8)~g&7BD?4Ckr<`WI>pF{`HR<3vlrOie?U0fB${1h{?aw}clS_X z%zcV*aRU2A(h!V-u&@T*y%#pn4_iyZCU5#H{W1mx68uPS<+yJqy094ZdlLYrMk_G39_WWH7%d~bkED~9QaN>G9PTaycUX6(J<^7Kki(33KKnRGdxOnmdA zG)x=gY>F*f7_(-TfiYv<&>(}b0inO>o^Pd+z|Ka3wBjQ=h(uktu~AZV;T zetaepCX|K0N#Sx@^0`SNY#g*aNysZKizdGZUyeN1_sKe^eJ;0W8jv%YiO)W(#@D~S zQF;caEEPh87jT5MuAQy98KOu488U?B<#W(iAOmnTwvxoeNkqL-r%gk(xdw0L# zm%scKFH6nQ&=AgCpmy#m2m9Y}kUk|d%8rRHa%bC}RUD)F{-;7P3FHCyB%bh zFgoh;_|Dh-=+FO*uoAi0kRhAv>-2jal1c)?5F|03cAI;*@1Sm8=gPG!WZ4L%6mb+W z8l`p|+*k+$b7vP=yl{qt&LNpnoIiJ-t5;E-Ft{Vly-*IJyGgyykz zXbO6#ouHo6KjRM$ii2o>c!UO#2}bwZeDUzVl0}AvWQP955pVr>KgPUqg|$a(7$rzI z2dw_zpK`DH2@91f|Kktd=ImmN^{*b#Y+mFyL&jh&YMq_xmg_pW{2<`40Pgd)&Ksk2D)muhr=d1|(6!+`=qz z6tlj*!HpZ&(b}-Hv%}on96=Cp|Nect-R{XUqigzUN>{nvxr}uJ0GgNK_L6bsX%tdu z*1o#KW!XRn5gYr8_WlkxuAk+d_g>@6+h4G@w#oXV`ygs8zxFQer<=4l59l9k@r%Ft zC9l2pCcTYqj<&W4rD3G(9+r8c#3}S!6p8X=;Nno{@QU6l&^*7rfDg;rT zN^_Cg>@0)sA-#ib^l*R)1I}K&Mi|uDSbYj~kfu*A0G9ZH6vr)2{Z<^aIGeWiO0td7 znotUoI3`8YAEefuy1b$cIke;tU6>*(Os#GTKbyAyCV9T+&o2thOaqR_FE7JcFDQed z5(m8f$|e5v{i}qPErKZHX;R^rzuw}Tzu!is5kYF$>x@X=J;$qWEwlYZvADFr!a^Nn zhclH1k4&Af*960Lg?N0xC_duqwYT}#|LVV|RcY~8|KTsW|Mh*YUb)Qw^l$$yqcr2M z{;$8{U~iA_zW+Y&zyDq47Z!N<=rQXX8^`IG1G}x>ZoN~8W0IswkW>+2WalQZhukt# z7@dLAPM!dm|4plJ!@G|19oFEno3Ur{7$k+7T>p3l1`LAI0R^2ukXUDIUsY4ZGwmUq%smYGijR3|@wn|7D4X$LW< zfs-fIQLgKxvVNayQZJl3kJtkM%fPGzC50f60a;F2v3t&oRMTr?`d!yQfm_RVvrDJ{ z6??v@Kj+(KqLeARmK~$=+F|T-v8}(o1+X@SCAslLs$3}JYtDnRVHieMNO1qWQ0;*z z<4!2cr#X2?Pg$6g3hT7rlVwa3Jf|(}(~$K^CHzGJffoS}rYXS3>IMASrTyLuk%cRh z0*r#q^(`v(Dl!h}v^!LS3bkqtp#+2ez%DqY+1uWw(o3i`DjaMc&~CTMbVj98K~yE} zL7RSOKxk}6bE&jN*0pt2^OV^rCwLf}xzV2BSwSS!AtDRu_Z2+?bPynnWqQ(;VYDtz zW3^>&#w%Zb0}Cf$3m023pFhua@{z#{j`l>NP+S88LMs<%#hqB790SyL@Qib-&J0l$ zl2ihM&Jq3nHl{yhCaHjy4Eh6%){G9@9EOI92}!$sRDXm_4WmwGsp<_4JtU5V)T+GK+sI4c<|(i^1KNT;?53i=~md-_7LN}t6jqdpVU7(^VYA=|S7 z4;lkHLSckNDZ}3OK6_>#gh5KHFsDkha{nPJ&&Vy2BH-Y#&CaKvlVu7a3{r;2*8Dj5 z=9oWN09jknLTKA)3re{7o&Zv^l@2(ZtuZ(^Pw%k9s8vU$2_~8&Py(rYm`otzfVh${ zbAFlCM~4JP5G4`v>@wABm)P0AN23yv%+5nn!HkYd8E)Ou!)rxl;fJv~_M6y`eYe*G zv4A$^Q(+2fSkfiSd>~gaO7S$yb(Nnv=_=Es+_IsN*~VxNP7o}=H(7oj8$}fR?j+Fi zMGmG;`mX%`N#)wM`R{Bmyj8`q`6!p`UMTg7Nkx8AL7E)%@-%lR)TdY)H^Rd2p1r>x<9GW$e)FCyB)-6M5S6~Zf>48Uw@s8moK4>A=Qeo60^GVj9V*RF1_+HXXmbQ z->mUWg=p#GY8|u0D*9%OVH8T%h6A=mh0AZe%gW#0ryr(l0mDj!saF^&MO8qYB-AQZ z_74vk^@c9bk>Kq7LT*L2wYACO;yiIuA&FyxARvlDGL>PJQ<#kA$=WLa<)fdoc;*bo z7}nP|c(%ETQYowJYlJ~WyWL@9eUq(cf5ZCv8dn#VIkP)t@yl&yg9c{y5`&{n2J}ej z;39&9)MuJhtCDleGiW_xXJd;?b$IDQ$lAt; zN-|`a1;m8Rg)=NE$$4Gj&t7|-b2lp73HF&?yg>W$F5PCr2XCEW`C65K_+=s}7%%s%F{a>)t_bhmXTcML^_ar( zUO+}14h$>LhP?Kp_er$GB%6Hx^&x-q(hcTsHjqzyT)lLb*-JOssVvc^%G1LYRvtYB z^MD|%5G6C5z4{vS*WP08;(26$pU?j5&*<+zqt=?^)%U(fy|O^(=p)k36SPPzprTD4 zzj7MC%nzk(Z;$b~Vy$Qwr%jPW5tT|6Atc#wfIw5P*HGFpO0AV9#*id2)mja$6un*_ zV?6jgQU7!FrE+_or+-cI<#HY6XC~`ER%b4{l`VShPZB0vI=@IYG#nnbxq9g;t)xX$ zXb>SIlQ9_e5WS3=l0+gPjv|E8^bWhUS}}>K5vDaFRYwt{_BDYCxVL+sjd!+aT%RR~ zY9y5gD=QD!+}dGoehwK(`n@h|t53Lo^$L|r!e}&ba;2GQzsUh=Z44waSHANqSFYcr z(x_W`Kl30r1WJLjVLF6SUa`jrLz-nMt(~lFy|0AHwXt0D5CR+O17meqAC5~aKs(PT z1IA#C1Fj?gs^{`VqX08S>4OiOVCbt4Jcc6`dPIx1`D9n z-O}2%z!@4y>yw)Kn91IV$`7&jbVfp&(%#!e_j?#w9G;&fzrP5GbNZpx^RE{LBEQYO zZ`+R3WYW{F)B1euKyCs9`hCW08jW}!_+x9VLI^@5q&rRZVyx@Ar<+bg_rlSn((^oJ{CU9Tqyk#Lf6~2^>YDc1 zmv!3v-v;n}-t&IqeXp&9oVAYg5fCws{saBPz6Cn9VL+d(ljQ-RZJjPt^+3@XcAe4# z!bnExh^(z#{2^&A2{8mfs12bpgj$oS(YRtS$d%MsLYP@FQw|JG652XofJ5(%b#OM) zgHZmI-}O!ikP>CA+kwWQ{7D|;*+YwQ%f&0JG#Nt(Bt{Y&sKx=tF;t0k9)A9G&1NZA zI-sf*HDwT~B1;FDoi`?FDjvJaw-{xWCk*A^6a=6D~Jf; zl|oN|Cf2+$`EiG(qXwxBLV|R0%zR8r3zwY$gB>N<4+raxfV=@K}B$Lm97>Spr4$M>}F z2Y4pnemscj6=YL9Ypi}#+Ut|7g|R&_wvNh5-3fGWTKl~I=6~Dz3nD*_HhD$O7(4T_ z6j!QzO*=F?d5osh!f!)oEz@T?(BVUedU4m>Gb|ZX!nZ%x|8iJ1CwPd z?n%p6_tw;YodyV>2FR8{+v$L-RZyHrj$g3C?LBL;@4y~Y99P=Y+(OCoD)EE$HU9eV zeon1cBU75q^=BOPx_takpK|x{6QZQb-tHcon>+l@+@;+c5XLdxZkMh7U5>W5Xdfhm zS6`(ORe73@I6xzUfWp3SoY!kZ9ss=)!pgAE&03ucAH2gqeDoP@aZQ|c-2ea}07*na zRLEXU(63jRyL<^5C5(nC%|@N(%nT+=X|!fhN`r!%H(zG;@e^)byUFZqi$F%~?(T7P zbc8X+TID0jv<1k76dbiX{OXfW2!qf8d;+C4&o-a&#g|{&F>>z5N~i4YY;z~9@w#Yo z<4|(*-9Mo9@_8QL`ZcS`E{AGJCPW_d*q!y*@pht@29AW__HLJ>XFa}q;T7huzQ(7w z{xd5>K_{>$_z*A{3|V>b03ARjsWR-1NR?)LZ=c`()2GBqLZ{oI-R_d5DR=IC#lpfC zyKK+5=Sj8VV1EylYSuRo ziDpJLtCCi8!29nm!JtL6c|aI7iQ>T0)gp>Wt`(F+bxhkcll$_d@BGipFTKR8ufEFX zpMOpiMtu0;hiq;=V>C)xoL}G%|L_mFeCZ;8_vq&w?C()YV!B*JIj!93oSFUO2kkFh{_xbmu}LpF3_*GsLWnQ z%QpIm4^3zfQl`=j+?IY0b2_ z@#-tAK3(JCqsL?_V{2=R%a<QVR7(9>jx|;nz%@nI^n=G7Nq|s`zwcRHOs(kR>^E`ag=Bqnv7-AdN zOUDcbAY>IOn^dcH0x9Sm3J&)&)Ig#I2x5eYSeiRWz1Bno0k!4~sW!|nE>Wx3kwHMc z(V$kVvAD26t2IL$$HY-g7>15dn;h75Jw*varlGMg&zpbzAxme@a1*0y7KatxSNZRY(T|KL7Y*j@H)ffsHDxQ7u05?L4Yp1RMG1Ue7xeKdH`X^5j1U zw4L_OY4@I2_tZn1Z_$R60JM|JFWs=erj&8&d(&G{>|g{DW=Fk<`+%iOqMP96>4Y+$;BvPE{w=jil`=#MCbrA zosneDH74T1mjG3k#f^pCj#$2s4QuWvEexz@X9$sU=}(V_TA@%m_m@ zj+xKkwZ=TXiX`)iC5%8dG)4g#$e|{C)M0d zta6xC(1Si%_mIHYcuvx;5F6*pW!Vj-#C!N78H+F5F)0VK&+Jmjet*CN4(ZC!#?f&K zg0rQy&ir1$aD6onc+ZS>C!0llOh0c0AuF$WF9&E*O_gfZ$a?iq_DxYc91k3kSjReq-R^`yQ{3a#a5r;mm2;fo1CFM3*h(%LH8GqK*l z>oHDwDFmHyVW*_x8G+EY% z38QobQNU3UF$h9%3D@j0ut_h3OL}3n&K0Vn<$@GVI|}Gkt7J0?-^7Qc3n5)KKxP?F zo~-frAN@DtB%wd(({3Lz$};YLeUGiZU4)QiO0%=I!!JMj1vg*1i3kKMj~;UG>w9GB z2w_xi@$LcEKnk={q)J=m$-B2{LpmC|L30YZ156G^M|`dO?2VvyCPch+mfzmh+zy89 zMKSA6m1 z=Y(N|6d{KPhkW+wXWaYxYl1LD8$-9-MP(U}A3t{1SAtHbgU+0#nqQ;&8p#(CIDxU% zYVn8P`+)7OE&lhv_zRMt#cWdH?U!FBS(s<#{zJC6cerxx3LpOPLtcC1buv=^kH7fe zc($>M9t}vB7a6J=&E`33^;xp4!uP-bK4GHyPe1!x4!cAC=A$opuYH-fUcJcvVVAw# z9WW5qYs^OpQCvqebaIvR3UHP*h?AAJ>HFmPA%>Kat?g|-{?)H|@4a`KZOt&#Y_hnt z#HGuZdGzQJzxw6J%r<6Fqm*v9$Mx&idF7Q?&{~tkF*mMXLrBTpyLVYwSfr9v$dq+a z41$0_NY>ZaIeYdjufP5}|M>C8Jbv<+GM_olLre+8oV1o;g5FPD#w$?HTf$hkb7L|v zHqV`PLo>9hRetc@_i41|*xCLMbPk>phZWv?`#b#b`+vml##3&8ew%90@$s2FK z$^Y}SpRvB~Tw=}mIFIu%GY%wq-s(U%gveR8F*wk(;9SX{={K5jSf9E^= z;fFtD_3>kdyL;TYdYvEs%YVV`J9lW*8)RB>`_3KSfB!vh-ndR2g{0|_xw&~RT)4<@ ze)Ai?_~LgcrN+QaTaO~k6k!gqQ8M?>}w`iwFg@A=N1uaJRYduxX*RqX8_a_6gi%(OOny0-4(WqLB~>9Jd> z0x}7@QX#9?SZ%kt_3fu-{{nV!k%g5UR21f+4uxi?>-zA%Su&5*8d z5SKaj1TT5s8lJ>AUKAr4Tfg68uS^AnUKG4Lp)J3Uyg%)k)AsE#{pAGNdRloe(w}cL zhNo0mD2kKq@ausRUn!6ISX;bo?qYeneQEinEyYYJWAYm7lUvKVn|7%bpJa&AzVK}_ zzCk6hb<7XWoCIj*Ej$57E8ZFRRxA#?%I!aiCwN*>&<>IB!O$v-0Ya&~kO{+9a$T&{ z?n$Nnq-$~$oV2;7z4!YfxRdWZ54=3>{pW3vY4r$SzR7i*A{~#hf@E$9qXpy{4DI4N z1{Q0t_?MV}c|uCyi@>^R&x?>Zqb9GMIl~|lq%#$msbVCkEM+ttVY)+tK}t0uU zUtsni#fShIhae0l6c80ZrV44rHW@$$A>V9l^G|CV>=%V(MoC&glMD-;i13j|qPF5vxGr?4=AX1KoLu3?j`OG=eSYQGdPX%EU z_GA#Ig33XL-ra}(kO$o(jxIzDi~-w=rrB9X+yjB{bgP{~5bHw93kg-yDlr9fdVGP@YM###}h-8O%~IbkswdzO@K7HtCNic;gIYd~U}XP1*jVU=HLw z>J(;aN(T`oqCHJvE?zl)XflmP8dET)Tp=lao@4}*@8=5Km||I!H%SvuVl0!&(DMM> zTnwJ_d`T`Z(T53|ZjLkavZZid&k4NcB!TE85P90Olh-u9R(`*@UoJH#ngeDI+f&o;D7Sa_UK^m0SWbW`F5R%Zj z(53@xKOu!gO9+Di1-VO?a2ko!)-4U=4-QJuo_Br2OCrKr;~7DQLOC#IlxlwW#pm3; z^CfW{BLe5nreW*ZI!0RM1T7f$`rP{D7N7mor^qNI)rwS&Z0s)#fwr-By(}cPv!8T| z9WI$)4*KOnpUgii;0R3+ONMnARU}(Nu_ZK0LZBQx;$s)(Noh4X`*NTX5^+Qq5HFzWTmteKEa7GgA`LB^oh!KE;8>jVb=~_Ai)+_xyv!s?00Tk%z8H;Cf*_z)t25{i=ym(f+r^eI5e$X{ zhQlE{J39=9gIvF`YtMTV*iR-8Rpe=8K|4-USjS}9JmibU+d2uX7mN@#+)pS3qCdj) zdYrj_jYhSC)QW7-LuQJziwi6-F7mtK5YtZxdKu@>EHl??5y-%?O54`4nB&G17Z*~f zq^yd1Gf<3^^$m%KRYASNY2C>aa}Xl+0v-Fr}9+v)_4*d-reiXyraiZ?k;)RbGAX$9(hk zT{fQF;p?Y{G+E^F<6XAb?}N@zYJ@>D%p|ktu5#h}`y7odomxLUWc#-dm|s{RP<1LZ z=ec+P0r&6UBaR}J$`}m?{Op*g*ihZ-P(1H!WE&yq?3ehjsv{&+hDb zhHH_FEicI8i0ZYA%tsMXr9x&5QO+mEnFQIz=R@Om`V|=2h$?lh2-E}Ox==Y><^4!*(y2eWTi1GP;;rTO#XIf|lrNDMH zccY(Fto>xU(@FQlw8~!uJ)d0M&jT}0x`eP9CXF!+hr_A&Pb&X;Cle>V`@D9$JB1M8 zd4Usrgb~ORL-5PZlr}`Zx~A22LY*EI@S7q8X|h;jq;?jH5)lTdFn~}xrf2&p zwWOj1XH>|waE6yIo#)~GeR7f67>t%^VUVK8+?X@^321+Tpp8|G zy^!tqju084pJk(68-(*Baqbt`;SUgmz8RJ2s)4`DcKtK_+ovXjZ~q8&FF5|f%baB z*f>lsc?2Xvax-r5<7A1re~74u3<8^k$6JFMY)mqYwR8=GfNCvZAP!l+*kDQ7q*x-U zAZxSeWN1P4&}IS-!YZa#0x8Pn|~pCqd@&@5y8Y z)4)+U-ab24MY5Y|^_I&ke_sY>PU4#wgAD9xlK`Z#3AZGMnueM(L@tS&F2NcS=fY|X zN`P`aD4zli(j{v%E~~9qfO2SA+Zez249&uzREiOTC{r{-L8vlJriiqlrUW52Ca-b_ z5XKa7Ms2D$d%zMJ$&A*-nL%WVh79QqdIUm~8fEFF6iAg~T&NbUTs$}oN*MwX5gKb{ z*w+S>AU1+oV~A|7V!*h>B;<5n8yELlxG~mF$r5(ptUJH1&(Z=r`^2440O`QzU?-B}wNk_y=QB70gR5|*rK&YbS5~R#*+)rnzoO4_O z-ii@nkfV_W?XXTBnUrX0siyY84!E#400A@2CP5HVtJZkw=F99odq%T0OJk-*94EA9 ztOcXV6rEm|`RW2uwa#lF{DgatHG5m1@pxNdJ|7S?&yigC5$WbGzxenGI@_duDAA%q z+_=D@xyrAtx4jIC3gB%js{m5W?hWwT5DRY3sXJ;B~_h#C%Es$c({r& z3X_9jHWujI;ylaCX9>fQe!tJo&NfS%Xi^<(GFEBccM{3W_#3fbH$^Dw5B z_`c>kOwntBc6?H}WM6_L45-E-l_+AS(J11dJ1eYaYX&JpVi{1;f>21B^#+wBp+6WD zz(Y=$ijRTTM42Mg4s>v$X4zZ93(nflqw(u^LW*>th;Xu34%~U5wqTIs{8WfyeBC0D zWgy?b<3N$X-LQubZoGsb6rc`vSo!F$L4||{qEVl<-+V+E&QKd>4AM4xcfR4^o@6i@ z5(&-v!>@VzM4(1}BpKbEHL|{<^Y|NdufyV%1p*B1{e2#ed0ArYK&2st56~Zv&{{07h@|#bXotxvvwQCH90|FTkh7mz1+1=eG zNh-Yc_S^jC*S}_W*ILS##mF&U?*2O#_cfoI@qU#$e5|}l-6=nxFJ8`nYXhk^G!`$> zIP(U<<1T&u1yUx|7cS8_dj<9IDVeM>bM9s4F22LV)gE27fyla`V>`w{Om+4gl}oR2 zFp3!Jl(f|$h$A|cDiqSZBq{Y^&ch~aSL!Y^5GrBrLA2nw0XDG>!YXE%0kH!0kK zr3*izLRdI|mgdp|Ce0Aa>LW(GbVruC?Y&GR6Oa(sVmh>WL|Cg(TaOH3l&;e_#! zPEMDKgyJS&oI+3R_ngMzV)y}hn@m0i#T2{5MtO^HdYmV4jgYs!lO&ElxiFqM*%-;wqERzDTm-7rmY*|NC~7f1lLW z)2`DFHeU3d$@WdVJbV1MSKjAxS@zDfr7^v33Vv;Lz7q(8fPt}|IfBBn-OHfQyKDo{0P94i;qtpDZ<=zS zfD$9N3WFv#*1FE|4JUNkPsd`qPY{dzvnR;;bDm*%24okpGMa-?p9k$jZg&pI0*OYt zz~RpQ-OYJ`A+TW$b79dcLQe2T*cdEW_v*yJau`vK6I1{qQxFDDp|h3}Vj>fGF|UQY zvt>J$^5We=Bo@si9(0WCb!bmxlo~#w^V>dHUI1j0w?3B4vOOAv)6*Xig2gsfIKqsaMcPBj*}rg?*WX z*8N^88$U(~LLsOIAxmfGx!DNV*xF#bmy(4s+I6_tjitSuuWPS3*}nro^hY!zNk3CO z4N^AaA<89^3Pj)l7PqE-*Ohe*55pLt-QLV3vMZ1SE2ITsKw=E7IO59EC6;;_aeBl+ z*zf=X;SLN6t35wwW0uVBI*EuHfvA#nm14b&XVop$<6b;%txndR&pDQk zNMo(_69bn`$g9m3b4kSsbI#sTTOd&c(uNa>?2oajPi*4Afh$`sRZ?* z;Y^ki?Huw4!3^DzM#d3ZDoh|Sk%Vf13cR9h$h0O+Q_M(VhK8)4ktx^-;V3Yiixb|M zpQV)qHkP2&dB23SfV8oG++4W%5}~!WcfNI45YX6zCM^ll5Mkh4rNIml?<}0>^2I)Z z>LJVskr|BA*3DZNAw4pqQQFEj6#_;xLuOJ%{ix5owRx^Jx}Z`!CxO6(Hu-|aV5C6? zk}S*6nZhVH!)^^JZG{KzvVjR9QARV=5vy6szR+Z$v|u#3XyVqnB#;63SH8g*!@~Rm z+Z&stamf0?9(xb1SHaQYA;0*C004mhe_wEBWrZu(uCZ|CWm;EXBAUOzXzM;RjVfop z^L_TkETN2PR5p3=kB=F4JH%m(5f$pKYn*xM$2{8GrBWS`KI<|&+e9qB#g1+eE-k{^ z0a^DUfm@Wt;h@RMa4wgd;*iiQ3FW5!G6aqoX#9^9xiOF<~ttY$S9CJ?f1H&1RF{pl_=!lPou|k%D;@a3!oV zgF)yF1n2-~EhVfxrmRJ{#$W=_Qn&<91VW>GraZITP!XuaO>7B*4Ne;4Eh}GX$Jisna&7AKoo``?0aDpbNB9T zHn*PToX;;2Gkq~6U1_m)QL5Edzkf4Ja0~L`(b*f2%N=FQaDcwQJQNN3-Es>g#FsYEDP(mEe^TtVSpVUDP^f1FIRXS$JOrFexB*v$waxecUdC#fFXG~ZkOUg)coMX$=%u! zr*fTM7AhvtWhTeu8JqoKuGc@kYkN69paFs~BnSh0(5W=aXnS}oBvHL?lj*HILXC#D z%~CKN4s4m)8oW%3Y^UKLlh-u4`LyfX06voj1~2l??^E`Rmh(LD@}zgB83DXVJIk{C zBsP=N0#7IH3*V|;$Lpp@Lgm#QS5p}*FRBtnb0FU_xfQbeyClWi`!C$ga(@c~DR5TQ z<#&*J|GP&ge|=_vUhvQr>p?yMCmUnoO^l@63KuW1eXQO&eajw5Sb0pVSvEL{`E?$ zC+O}pw$%R906M=WwNsKr-~bCZKYq@wWnIp)O5;-y=+kJ@nT4OsmJ_d!V>HEiz-m&l_DbC{eE!wh{&w0>ITU$>#nNI5FYN&-}}C=KY!2qN}og$n>t-z2ul$= zZKRY!AyGyVjV!VNtVL>&8g${Fp_GDDQ8b{4T%BpHq>HZ<0wo3F@fw}A0p}m>aqb`i ztzG-1f+WH=6ehJ4Z9&lz=)^f+u@xkJP14!t@V7Vkp=>~V5t$fFYB7mKr=Z&k+fWb* zto4;zjYRiy*w?5-P5+?J7rid`Bg<;L!8@lHQT-nM?g*{GQXmV1DBL?zc)czqD2a(F zqf;;;`c=A$l(|7@7lCF(jK2Gft!}}>_5t%+qKJ?}BW!`pL1YqXl=H!a#YopzR!Nty zy4|3-^Bo84>%892(Q%6?(&c#R*dm$&5lgJkCt&@US_9T-LZw=5obIwd>RxD^ZI>*NY@+f)EKZY7j5Y(;qY#j5KM~V0QLAvr*1y*rA_U78X`W78cpr z+2)m%Wu$B~Gjj^nUZ9`Ftejq;_2eR*&J%>RggkIzF)>Jf*ba;cHch(XGS=zZV9P^P zl2hjvj3FNl+1uS_W9>Ol9zS7YbAzHNxPALO&aRx{@`Y8Jtrm}-J>}t(M_6N+ot@>E z|NJj(Y;3T*yUU&1x5@Gxt#h=|W!|@dC-AUO;Ks`3dJZ_lpHx$zI~y%qRVKoo<(3efoQv-`?Wc^XCW(bZ+?K^Uv8>TVsE351}l*yw4wQ zeaW-#4x2mMWTQU9>WYF0m|r>@nHH9-#Qb7)m4#T4_YU2T8d;@cQU)R!f%4H_3W~`0 zm95ARD_o90j4nB=G7q#YJGR1JQRMQ}+R&{5v&oVqwZSu$ZehX#w(!yC9;~=U&M+h{ zPR)4zR$^Q2w%i(R8D#~Bh2iebK3@+8bX4T=u^SL4pAunmmm^TbY_C1$pZ@OuquFTD z+ucG+%Rl|U|AQz^7#{4C^$z*!v(I?+@IIZ+0sVsx6oreN4@|2cY*k`$-{w%s+P{6F z3toTyHBK!rk?Vq@DA-tA=l;F>TwGm6NtgF{cBakgl~cTS+{ zop(s2;**{>=>tU?*Rg4`usk|UN zz2pNWY^dZ7Vf$$nz34D^B!r{cL!qEJLL>>j!vWjR)``14=et?s>{cLI{4ImOSN;^D z+I}rWZQE*1@zjJW1QnGZjjJsn>~5$&b+_;sKVTNG6Dv^=H9s<3T460ArQc?5YqH@Q z;AGE#Z=5c4&yvi}%`wuN!DxulzCybcm?&bY)goy$us*M*5)z>lqav@6{uDTNQlQj} z2*;Np?Ea&U7d_);+wdadYTD-~aW&yz2q-IMY6|XuS(3I+oYl`6Cpf?7QDTf>r^l2M z!LUz8HIB7wywPs%3t5JZTTuo2yi}8p_*{1EXK8DoN|iU|_3h-W6=6R4?_Y*&e91PR z^xez)ZuK@rDsq5UN{J4jz^~qZABXV!JapP|Qe@0Y$8kC7CO;cz>BsL~|E$qykmvc- zs(|61(++EW#W<#s!NLO}XKEd2teaW_N{{3TmMgm$!tr4$w+TJf`Y9f?s5XSl0@V}Fp`j2jfy1gnIxDAD#5qRgO|2(bEylqb7mR8xWQt5% zC?wfoj*^k9EQoMXNz-(IzySmgYQpNRDU8eYX$3Nh=*Ah{W&<6zfz@R>jHjrLY5-K> zcPV`yz8EE4!tqnaZ3S`ux!+D1l5&^ISCqi7oE^3II9aa*gHr2DJP|vlGir!=Lg9~* z_fvGv*t!1xp)z@phssFSbVXg586ac!A)d>`F^5+Dl;He+om`{rmn zP69kHGK$mMF^#M}PS@72Kk1pr5y0WP$$~}49f6<-Q3Rq~K!C92bXq&=QK*ys3qg|w z=hf*pB?L(#+1`A{{bxJu%ZNhAlKe)vD5=cm49t*R7Q|^vnly-sNKHbbC3cWw2RYJ0 z8mAO_POc3}tBGpH6p1AhxhoVWEE?^i%|(n78;D%d8VFhiND4QPa~IvOBhay-NFh(a zq%MU>Dh18VVu!%PC&UKqNF!p<5srwB1w;ZFNvyKurT}A!MU3oQ7CVZ|jW*|7Gpu(G z*u>D50!9PY??2(;&VZd%Q7D75K99JzpxxpRN@A46N<3lW#zYxOOC_Wzbm6wqV?n3{ z-R&@J#MnGX4hG14ghZn(#9AWqpl@)Imcpl5kU(UKk_5Z9fz__KpDi-nlAeneGYUw- zC}#~#ZF_mp{cgD@=xRwMB(YMoa(Mm1S(eT(GILAQh+=Z(a%b97y}1h4RvY$rw&*>1 zK+{A-al(Qy*zEzk8@s;3tw3N<(t;86cJ~o8Tl9K8PAxC8)tsfbv(249eopJk2gGWQ z!@YgpdGmdi+i!B?k2iR<_5_&-R4PeSOr#Q&jEH4KtC5mRMWfv!NoNoyLg3QF1cT~P z7{R6lOy-y&B2T+i7+^)(F(x0XmDf|_=fvDkVMkw{}r{qbL!cT zlJ&_VlmG=HRurz{BC^QAz|7nX>8TdUewXK&rk4t|s+_wO;& zXcI|+EpooQd50)X5RC|-;mhCsjv`j%T9aitCeMigTkdd>o;(lqj@MWIJ&gY+By4SN z(mm*K?b_???dAJjmFk4k(fytT4kiu{5^z?kJ>{Wu7` zZ{>+wJ847xys6vNTC_rCH7mUL^yXJ=J>H?a-=S%8ls0spJ>d4pGTa{^2OXY&_a(c} z9&@D*(GDTfC=Of!X6o5S7#qrK-yGr$%(#yBE6 z!V)2nk#b|@Q!$v3) zlYaYuNn{3GVX66nA>vt-*kf$cl^su2d%){1A8t?%p^JpB`OP8(IrupHBfdvP#FnIM zDG!wQHUUco8L`$yPscICVvpfyNKxbj+Q|q(9!2D*R#1(mFEAvCqSzO-73J;;5#;s+ zY*_yZ$7AW24YUg1{YT>VmszNPndiPN$u$j2hie;+211A<+{;PVoYdy}@uK#>OuyDt zihtPnuVV7t5_e3t)0Q?n+)`Le9I1-42tPy>q4^^J>PR06#_~zqkfYs{`w;*!@$Pkf za6BD5=^K4tGXA)0j`N$t`K5n6r5#bE1iCP_996bdLem;o-c(F>!i!&|{^Bnp6{o$> zv~%ihsy}1$+;S!71fdl|kYyR#7ehL3l1#m?`-1Y1+9L!4t@V){YeDf&>$PK7IVfeV z+=dgr2oaWnj1JDO5XwbpXlEGQb{T<;9*Hn+GDE!_Pc$IVMS+P#ng1=0BEMDARZK@4 z@}ejWSePS9Nkrswq!mSh)`ibCWlPXjpGHR~Z9Ni8loNCeTU5j93TPOOH7=K3fh9LC z>dEQ8n!Nk5bRYX|{vI*AD#1bC1lr5VNLmW3kt3K%W@sT9;GFR!O;AcQ9F8Uy4nReU zIEfwnHyVlp6)EzerZBErVj-P;85}q(Wwa*GP8Lgwx&%@oA}G9{k&sD24p=E2NpEWX zT=qj1-}$mfeT zl5Ur!1>y5UH5zTMymFc4(<`j6Z}a%^b4J4vQQ{o@*_B0p@cLWCamrNoVbS7#TAMg>vZ<(Nbi9GZ+r zBIj|U$W?@psRMwf0)R+Sl28K5-xHOvFf)rnG8m4?vk@wis3;*TMr6eRB$znEn!;`O zQGQ9&czs~2N|SE53<_uog%Ob1%#}Qn25Sm$V;mV(wGE(w=^*^}GCEf6)3 ztPJ7I(phG;B~}TsipwG{5J0d?BY-i?sfhPapW*BvBPtA{(AdJdZR;{7lL?QrK6i^g z2QsH1BHLW!U;nTFlVXq{dK+ZJEuQ}CFIjxLLz=EI+U~Nuq|l2o{az1|4SD#@ALt$o zV7Jd+qe^`g2s5Kxbi?BIrik+W5cPN= z%6THB!sL0G3DxBUcQ~lf8AYBs+aQ(OU1C9J8QsCKwB_0uM#66hwO-%V91KSm*fR1d zd|%D+GB(NQ>aO%ekdb;6+L%7_&UTrBxPnejJ}CVq*H=!9 zb6b5yuh-+=z59$tL!R8bPugs-y1L5j%q+uhkJwfi)D<~%?EicAr%scJ!zZ|hx9vJK!gJFJ&jsk;>yZ0$(aiX zR*>3YeU5#y5uL3ay4?eEt$nHkO5ITsx}lGK+Z$3Kl_W1bk?HlF!sWSEUjKx=mR=Al zJw%0PWWX(9;0U)X0jVeYTsv$fTDA5~@9xm{5EWe_^?++cZQPN5Hw6Bg@u>T-)qQ-Y zBc);!ehWUr5wyY^TT$c;`iFo;)nv}iW9j=0m+k_H;>hQjn+FXvL!W23PwmZ*1G~oL0{jMc2a4#vWKuN{e zV$aleP5fQs?k0)ImmTlP;E%C(PMlZ&eoWpfWIFD%8$%?OAk&54T56}>uzu6<`-@f? z>Vkig6Is7*@|^F(_8<56%aEA$^LoABiTO8AY?Bwf`jyrzZeu(tVxinJ;jeK(v$}Aa zLMfkXtG)_`BS7AYOm)xET4S|Kj*oFG*Us$wm_sXS0HJb#{6jpeUn5L$kBCDhNDCIt z!pc0aUOdki`#tV&Y$2^BRf@~2t1K=ovcA6N31UmP+eQ201aq^qwA*c-WcP@$oPXsi zD`aTLrA3;}27{r?k2M?&c;(7vK=9zv z6N*AJ^$3o`i^w)1KuD~x$Vg#)-Zrd{DisDTC1HiuhHE^)4#gkpz{Se2n(K=XSQkZU zgrTrG^K&U5{p@3k=eu+tKc*;hu3o*&($X?FZ+^?c!9k5Mv&^(-xP18%vokYfqY>S` zJziTm%gWjTdM(GT2`C3(1+KkgnzJUmWA9ya>V>2C3IVZ~MI$6u3D8bPgs~VAa^g%z z(@EbSl}>q~p{Xf;Hx(q-2OO&zcT;lwet~qw zI|+8;SufgNejH$$_Sxhj)Qb$zNjkD3+ygit2NK~KVc}-!va z+c#-jCfhREZduQZh3)OPZt`9&&WrD z%a>NU`pPSAJMW!4>~5?RMNui^2sbZXPP?Q8HPXLdU~R&DDsv4AM`*@IacC2YLP7xu z7o}q(%TO5(BEz9HWWInAIMUJ9Wwr!?PM@~Y25-FgE5@a-95?$CIzWXS)N&>nY2JCa-EZ;k}H>2Sz216*Bfx> z-aUp{hOw5S$Qfn>27^QLJjW21`hba|8KooYe}=ggX@Rx{_R?iIbDs9aEt2ejhH{H# zPHl@a(mN8C3vtTNu3qJp!5-{wAhQArjgbL5_Z5+BIHG1>eoIlBR*Baw|U z82ln07LDJzKv=f%dLY;rKR6|!Bk=aiom){-B`^$YYJ_FMAcay&U%`Ij=9A`Sg*Cnl^@4=N35APBEjA!%9d*VOeZ7IkV6tKRl$L7i2=Y1x8zP z0A(I>i={9cV>HRIU=>ZHoiOSM4$>yu?Iy!WV6Aj?l(iUHE%+)=z9(tI+t;u2gZJM< zDaCJp^K0(jzKzZcw=i9cVh_$>u!~DGv;6qK{x!>|PxIB6U-89_&v@;%SNPy3AF;c; z%O5`dl#Pvb0-1>)PbmbAIAvjefi!L6)`P<$t1aKJzK9qJh0Y7|!2r9z$3kMc+LTE}@Q-r*OFdTa z=YXB9-+N$!kD@>*zr{V=%iB~a=S7*zKg>((=6Cqs`4s>K=)&oZ+|PF-utnzWkbgu` zQ?%aC(O4QH_#!zWA9&h`dGFQpy!rlnsAk(^V}Y~=Srio8yFB^kCTsT}F&YlNzh=vV zJz&d-)u7*UC0K6GvLX}*hg}z`9PE|#IkWMEXjxngux1RyxCmua71OFC^8njw$h2H6 z3qum_TmCWyPf}q#H{PbaSMc4!_=!-A)z{srdm$!*bPe9PXROQA|6gVak8c@qLKKm&MS> zo%emnH>DJV!GK<`cf@!4w-A?;$59dvN?{Em#WY^a*2deNNWqITYk8boc5 zy7{EMzaDCVRE-O_o__CR{ju=5hXN8oBqfoQUf227hUN$OJn&GG`4;P6UP`4p5SibOWBlw z8iWY~Ownncgs<9j+KWsT>!d8ZHT#h~QP}=Q4QaF_qRP0Mt-Z6md=@PE~mVD=`4>MYka%*oJ7K_ufD>?i>o|+`V1i?X__z^ zjX3D+Q)EMgF1T>^G*J|>v)1IDYj2{O>#+3zYupNFk&h^(M8pYJDi_UaTs3y*goz-+ z4oqq{(o!mvh$yT^7>$$>B8nV9#aCSPf-0n-1#;d103ZNKL_t(7C7_JX6+U04U%_{M z^b*Fo_DhITsz*e^xPrIAc_hsJalJK7U)00p$L?EiM9s0TlYV1*I)K`-&uVR(c+Zmz zhz)u}glgzD$L*M-S^*4ttWL;@F(X{A%#~L2WE)Y}(2*T!@;ycIv{=M1Q=D ztvS)omG4QFZ|a11JZ=GRQg$XmdhJou+cF78td~nmm2*zGNYAiXWP&*m0)cYmiq4BMoDl6@&JY1sT1Lj8V-WcCD->SlD;=t>+i_3=BSe*> z%vQPGj5R0~@zy&(;IIGNf8*-at9<>%w`lt-R!*Jb!;gMKdw!Pn?QL4KZQgzN9nPFN z!&hH@#ozti-#Y@qI%4tCACZk*q+n6x zbUGcr{`zZf-n>OV8k8hK<=j>BT_zgvsc@W-S`JM{bm=TJpM1>wt7lp2?2uXG(+<=e zL<_R8EU1Kwv&+mry~psz7Ohr{6s}mDNuU|QmAK79)?}m-MoMA|kV1iV`ODCrlnYD? z7L~+6j2RW~lSp8dBUp@sFo+N#X9Y5jKuAOcR%#&k-`P?X2?SOu_Y7;D@2cG9S~tHG zVVic?z8s3rSYPD9L;@n|3eb2`#`^r;*81x9Qj|HabWvciRCma4d=v4&X1y#3z${OMo+6*G&AtarMo zufOBU)mQlQ|K)GEd-o3CKDtkL?HQxdh&WD2(-bQpk&^STy}~d4>tE5Hog>c+@*>A8 z(-LdA=^}}f1SKU!p7G%JU2go{|0Kf&G>FL1jNz=+*aAl;dK+8LR}?KPX`37+waZ`J zNDu>{u*k%b`?DtJl8vxNVUT|Ji#HNhqI{^mY^K=65p<%kh@nAhQW)B#O4+$B9O{E+Ce8+kOD0QR=S-!N-9d;EwoAF^0oV-Y}ON+0iat?s+EPS zL>aT``nQa#s~sh&6c+PE8PSjO3!w;K>{Mg1=o(fq)-8m%g(}e6P-so4B5ZJT+-Sc$ ztXxG1#I;*gGahSu_2L2z@V2$kE&xIphy+Mbjf6|5mRMPsqiD@Iy{sfk8chlv+~qx z0veVGMRhAYHneqF6s7Qw%e=>%Q+%7FZ4Kc}!sW!ZkSIkOMd*P`_h`qPF$wFBxo>rM zTh94v!LnS?G`_r@5L3xY5!ye-UA%a_UnJ9Yxjv3>eBn6P_?-GZA|>5wZrGwJZKd)~ za>DA3m~7Z_*GzLvr>$H}yYJ-B`Zd!g^>M^y{qq-1ylHKl=0uO(%Lqy~4QfIJMi&U8 zG8d&N1-4R-_2jY>^h{`o8y!)dUz#Xyz}@z9C#))Ro6AC^TY2-ckAfL6C7I#vnR~9O zk!o1*mCB7*5=usFZ|v~o`3_s_JCJL1ma(_D&;CJ=hmRj~(Cwm>tC(pG=t7f?GIn-% zSX*DCr6P)=;Mv+br#5?>l0KTxYFxn||LUbanOqUG)%%2T3Lq2juzTaL3l|ld7k*2g z)d(Y4m~C?5%rcwj&$GMV>(v>T$Z|>0V z9nwrwE}lD!j8mG;CI`JksvC$D+pFhX_2M=j98i1wQNO zbq2%(P$>lSi;K+6%rY8{==Be2w^|5m>2*6U&y8@~%<#E#L|CD>E;^x<7vmf|fh?nH zkJBqh7b*OB35&7XIk3ifARc5cOkj6iNwx9`H9k za<7wg>@;H$=Dy>G0wEb%P@Pv^Yb)8RK7%91+>G}}T~6z13y$=It+ta->g@XGm4SBW zNGYchnpIaFjrOcRbMl@qy2qH258!rU3@6_)kgoOTPWxG1T>HfalKH4jI&uVE4qhz% z*jQWU|1FDxg#NNFr}6UQ5;HSxq=c1~6`IW!3yX`auC6l5M`-OLq0*#5yVat1&}DyT zm;PYD&dx5~?g8M6OK7cm>&>@lHXEEhdye1z>Ngzh9gy{hES+27%B!#P#n(5PS!wda z4}ZkU=@q`c^&R^M9fVR8TKh#b*TKQ@s@|y?hj8YL7E?huL z!8hODtmzm*tQ40oU*f|*`IxlXpg$Ne91Pjr*~JLM+`>Hbi-NQf)9dxw-P@(rYB4i& z9ib8)J=tK8Wmx=vdq2ibIr?p{))5F*`5E_)3d3k`4?E0gw`Z6a8WF{gp!4tS`rUvw z%%u%f`xK+iO^Q)MHXIN|0uq6hnxYu8x6|S2{(upZTnHB}X(3e5Rb%SU>2M{Lt46K; z`1+iu!dl2(ktu1wRV=r@*pWb}fNR|QNNU@Rg+Qtpq;eJJz0M8=YJ?EJ*pYQa1G0=< z7FvMydBh`^ZyY6^+|}f%jLrm?kJ=O5A{0_06z}kpRtZ!bku+1cyTV#1K-q{?Mj$1H z5{?K}5`1n*8*5~92BanTyAZ4gg2D!)8GE3!bcx{Op?Yf(pI>L;)VGIW)|j2 zTP=S0;~z6KKhIz|pvX0&!4P8&j~_qgx4-)>dwV+=3l~;b`N^OEISXf2IOueF{(PO- zMx=!$Nh4aV263G5&b#mP_Vu>`xber&c>Lf2O2Fx*Wj^@m$5<)2ed`u_q&a);9B~r+ zLN=g{r6>v>K6*^M+2p-9@|?SbXEqp6Df_bN;{H9gDixr6ptg= zT*?YtU1)vIUA!Y491Wk(8E@yL5I#3Q)uOcj7svpG@-{`dyq{sxmvcsx>PiU8iq>wM zbI9l!obj5S6~W%RIVH5ie{Q}@ROBNxO9SY)3j;>G{OZ=XUHbVSod0@$R}mHgd_<}A zIztKNsty|$z_}aGkLr$-%{_>7wt5yaJIV|T>w62f5_FHFK1B#^{0*7_YqrLax!m*WB}>WdF` zuQB*2C5J@7g%u_|BCFFMJ=TV4#QJ3WtaZgf+wC@^(WuOeI_bl_h$AfTM>SFwXGJS}{HrV|mnoYMX9p8Ol3~NRcirFp3n!F_Bbc!mn0BO$>0xUerm;dPBUU z;1$01WKO6GXN+WPeTPr)+~a-@c~Nlx@gtsYZXtx^aL_Lu_h4kUcXs&n55GrAMV{w0 zBgLcdZm}X1&E_Ia)j+l)?{Ld9Ike^I*iRB1;DB)@&*Kv+FACx`aghT!hu#=Nx7TB1 zbDJzLIK8sM{M-xx>l^C~hXVwHwY4>3CAo6xBCD$xn46j5=C`-$^$uw@VmjR}%~?U( zXn3O<+E9&K0AkiU2hrJOTWhA5m(cgt5GmyxBw5Lg2!O7ph^XD7f)|rkxAtr4utTN` z`n@5~Zr$eT^8urNkN#-H!v~LuWW-=Fq|@ne(CJ{bOIEIgrQK>^biu~v2EDxw>l+)) z_Xo74Bvpb`NZx<_H7>vPHov^_M{YfQKpaJ!KX;C^=gx81JLJ&gITW!1}Rv*i-vkA_GkX*8RFB+FcZhPrl; z(kJ)E5$r*Zw6Cu0hRp$-3QJaGq^_VZWA_QGJyqvT z16QRtRGu^a9RhhY_MDUMEd`Uenr!>DzsHHzqd%uzcig)j2P%)d_N32`wP${;JS;f! z{YkQNERi|o-0-mD<)#ArW7pd7t#w-xtt}VRtWR&Cl;qOtMgHP1{){)Szd_PyaP8V_ z+`apNmD9^CEY30lag7&jh6}v&c@MSlj!|%2#L>BI8OvEaCXZNJtrj1A@IHU_SAWgbt5--9 z#e)a;*xB3UogZH3Cm(;z>2qgjx7%F0e3^$29`TR=@K3C7u5sti9S|Uu+m_o*6WYxt zDpovx_JqCNJ(d=i5VE#7BLrEY>2wdsij2YDkgvY^mao40mQj|wh$Ih;4Fc)&K-yY| zOfIfTEKMbm&$rn5?|;u1Yg>F{4;dzrk3_VDXhIjMNmU2gcDG<`7R56;e+OMb);I-*14MavDtt`on0FFU%zHp7klNT!3XKx3r^@Us9 z`zR@qs;5H>fJ&LqE<@WPC2`9hD>@V z42jT&frO32KDUaTO(n=93qFoI%q1m#IYb4s+Ooa7hrF-2a%lyXHnF1PqS%CY4gDN% z&CT-q^*4!=m^g}Qw_9j!d{iXt?(R?&1=rtumne?u_xcMmsh!V?JeH_;C(hXHn@H79#%rEB2pUplseC1PK^(^}Dmg4{QRT2VKKeQ6pt2Hp zUG@7d7chd+F&th@Jt@5dn#!1Nv|CGCp1hm_2 zu3x{-%E}5mJ3HLE^)3DWz>S(QW41Ba!0L8x@Qh#Z7a|mV^?J<9Lsc_E9l<+Nx-rqV z%CTCv>blTnK#cIdrvPT==V`TC2qD<(>@)J|FbF<@tfa4w$-;Zo`$hQl0@j3ls}>PS zBBdB*Ijtxmn65GZdEz(@=S^bBX&7|c!g)A9_$Mb3r<053)9yLh&KF_Z7ZFP@dmP)I z7Z6zA@3~=IYDRt1O{zVAyeUVJQL)iU3t9 zrKWs#NrZ_qdS>#?9Zal@iZ^#?ELjq>EiLQaJq~mZN`)O-1X9F$=cg{Rr6WAnPc;em zMS!dn6iU(=jA%(imT3xY5mCf2FBoK9f0-wng76{8i=3=8BCL1}una9MEuUs*c*xFZ zk9cVgMu!24l$LlgR|T79ZTzLmL29g?v|6GsuE5kV@!=3p&_wcLI9l+A;`r#I4A zE7;h4j*>BJ>+2W~?z@@aFr*8H!vRU7$=b#ih0%1nBmU<<|C09XEZe)g9`MvPLoNEP zE-zC^55M07DFw<(7U$=YLeW3$Gt+L9C`sR4Py&VWZc_|Yy{~NfOgkoPQIgQ_jrh%{ zzvt`SAqOMPNGfjJ_#@wb`z@V=E~C*1?~Nic+_`t3gM%({nzFmMN6{ZL+S=oTc!9SM zbLLOYq1fWg{2Z^p`U>~g*7^4SJ){&gn+?vKKFwfm$lUxq&32n2%h=!FLl*@}9I>>x zNE9pf_I6obUgGTO71r2adf>WnY@q_o?V`FQRFTeZ>Aq2CtGhDiK z1tA2tZhc3m(QhV~mUo<2-S1_iVp};=!}9R1;?6++bAdt6-C$uKw(4?{m_7Oly1XEpedt z#rpMSpA({%rybJ>!g@sHarUkLw-Dn|lqa>f8V^bWViJtL=$Hn2Cx4%`mvtE%C*=Hm zpR|XBe(+LkD+kUP!{MOMez(IY&s@>5!r86aW`kxc!CFCQe-Bd>E{e}OVy@M05hXFL zR*NW#K@4d(TPP*DckdqO&Yk1p#f!9OXHm@tx9{JhZ!^y_PfZ=d}5k(184c4Ww5S6nkJdq{96$J6yAw?i6gD%#Viqi|ECN)yCiUJB= zRlQ%s__SIr&YeF`94YSJyu}aRc?)Ya7cZXYqmMq~+`02Kn+>u&-=uF%Wyby4!IJfXI;kl2)R=sFXyUIxD2prxq`iGMzWR!-*_eYyjE$s^5;u-eHO^U$3d2?|xH~Y! z#Sp^~p|#tpJoK1Az;3g_P`C&~7llyiOjo2N;EdW&DnK1c1;!T&F~NCs`nOz?wUp!I z7EIhWcB%YNAsHiZYugIL5%PjpedYALt0m;OiX)IZ;H6-517NE~h5Po^Q}(&OcmIXr zOA=6MWY{Yy9l>NmG5~GMxf?WrwP3U(kdz-~;iEF~J!gbloKU8ZGLAeo+G4XX?=8|u zBICELDzGZ_p^I3P7L;`nn~@bHBZVA96v|db!u;+WiDgja>?t^iC0dBGKOOlM3UIiN z=m`P6oHNf3SYAAX7HQcoi*%cs{P=(NOtu0ps2}ziBP`qk)R0p-s>W5Ersv1 z5I`Y>lSi!esSkX#u~j8OR4$wg3s#oJ$ECf7>R}#AuZ5M@oldSrATp)P zTN|#mxM)x5Ypr`YeiX}xIT>(C_|H}|LDc7OIlr9#SHk62jbw-_Ea?G95SXfnOEufw ze61JYD($MRN2b;uDm3*Lh3A)rhH$#TdK?xkO2`A6X~8HPk&DQUll0KdSYqplO`0ys zuvBxQM?@{E%HzUJ2}CUDD#_5es4n5hSqW8*i`P#&q_q_Ii<@{{B8UzxjsU-Q6-B0Dc6ekeoif%$2KGXwA&f>mBmw;UmtUKhK~1 zr~ky<>@1&u{yD$=^{;3)o2;y?u)MsCQi_d@4YoJ8TqIcVf5S-Du~tYU;}2~#+SeVB zz7QXNVKLZJTeDjy9Vht!$N2)s;qUKrOygu;7FWOQaU2#6zn?@zo~I$u+Ui>oNms|C(E2E`cnt4x5>Z`# zXUh-0qVYN4%Ra6XgW_0;o+gu%a(x_W4^9{K`~9ghGwoi-9h1)sDSILzeD&U%zMkex z)rILKaRy_TW@y_C>|k{9MFx0q1kA#zqijIhq;A$ zX%1BI}xh;QE(d~6P@LNV5c|a*6y;3Yg)~e`NcEbzxS9Y4<7U5_um2Q zq8(hMxGd$qY%4yOq?D*A;?1|-;^U7$Vs>_pcDqfMjaXP*;FCZ71SutP8gu&WS(X+T zX*8M)hZ(2OoaQh8@)ryTL+;+Y$BjSSpx5hqGgT2LVb?&=fim=QITb6}6$AnguojsB z(o}!@pIF>hbnA*{xCo-7ve4^w`RtFM@$}JS&dx0G=4)3ud*&>kefAY!e(^P%8{1sJ z{xgp<&uUz8Fl`E{Rt@FSCZ~q4ygJ-ztdx?^Y#lo?=(WCBMV-2?4LK*VdPRbmFq9ay$Bn0I?001BWNkl8*kr0Lq zNncp9p~W0%^boXhnAEq*cb8>zYm@Hp4oK6KgM$NJyLOG0l~WW&!JWHzkwUPzut=Ju z+`s>T-~8scJbm`uZ3oqwEX%og;UcT6mnaHNzdvARrbW_hFf4NNG$vYHK&qIfy0)?d4O*EA_O6!Xz z%J7sx()oyALjGh~7Mdy3M3(Z7C+b3<_=sCCFCMX0cDn|AEuhO-`jGpvlG{>XLJ_y> zVgC0ZX|;u>n%pCl*Z)+J*}hL)Gb9#+@d+OSP8)xAXlvDIQMRw9gG(|ZTr11ZT(%0| z!rB^&M3lLz5nea@0khI!1BpN=M<^Gi;uh5$u02!W#$HQ-7K%bCOq3#2j3^3>b@C#W z@{8~R>$^pZ)eGmibomO+R+}_UiIn1%SFdv6+<8?+>Q$%9wI)eZk~pr;_k9&e8v&>&;^Bh_ zY;A4P=^mi7um#nSz^5%m4CUn3Xk?@6?SQ2r0?(f+$Ex&2H6?m*q<) zarAME*pnEuoi6e5A*())*I^EUF0gvVC^?eP6E04#$PX-Z5YC-zGZ#6;39s-J_>R zA^%4mVPPSPR8+#Xx}gxBiTS=nr}FzSvD2K@7dffZCjB&ld(mK=bX518d{*_l z5{hD!6Q?N=f(SuwEjHx-vVQBZU%soURzQ@1$d8KujuHT412l4dGV7?;FH_drhR?<2y5G8UWX z>~?k;q!CHdph#0xnjkWlDatgLqO@S{hbb(E!XrqxO~oRxg~shL3l#;!ds4Qo0#wex$|?a|%^|b6E>-Mx zI-GCMVH3&W{)k)`2$_rlobdkB=EPC?6=fkOHw2PVQSj)|6VkZBT(iaAK_6k=3VYaY z6!NFKyGc-@a&&QkW~6iaCgX4}ViXIy+XY*X9#j1Gcierx&S;dey}iSoJ9jbGa{JC5 z4h|0JcDq<>DDs^C;UVj5YpzoK&L&HX%RDibwWm+%_lGo>=Gh!{8K@B#&R$^c*%l(P zBr2laXtJ_=igRbq(QeQ1!3Q63?)-Uf-@eUgIAn2Q0b>ewcXv5|?i?R}_#vlGt?=D< z-yy7JZf=&-r%%&N8`yrYmaE6haW!;=nHb2#F;XndsapTPn8>W`ikY~^QaKIv@eYnd zh;*v(!MKr`1~{gHsPU7++nKUb?%4a)?lpOg&CBxdlMQ@`u&fi4lZ#^04r}d^YwMt| z-Us2k~|R*4gkx+L6^O~4hQ=^vQfda=g+u*?*ZCqmY0^;Sl@KjrDx|{)JNe+FCpOY@Q}T| zee$eecV`DkWZB3?NSUG{TY`gWt3|8H zc~S3$fNi9=eG->HE^bLksP#{9GDE-B$=;(NdNdsI=;33YKYT{|gX`sct*@+QGDMsw-%B~G1MK}f;OY#VFfk2gMJaM<^GeR0K4eDs&_L|~ycafpcY`MVNd zoFvOLc6N4H-&mtRI&{&drHu?j?<_>!_S*2GxC-zRfs&Fe>!Cz}HaU6j=7{z>uP#r* zx&lcfX&qT-L3zPh&!3qYaJpe@M*r{n#6_Sc7KiBb;AzHt$gRj%}q`aqO8 z%PnHNXB#2Q{Gk>Xh#6ARgq+OQce(|Kk3^XN?j0Q2>YwhtfySk(m*(KSV@kzHhy6k@R3vpsMP9Gb7`Cj_-5f^ZHrwd&&1F zB3L!~Eif<-WUA7zQ7u;lWFnXn3&Pn7%nZVZLgB2b3OY&jvlPE3VgCJI^Fioz3)EJI zkaz=g;GoWdgy=Av7wnl1S(fwVm$$k0*)@(GJ<3y0J<0a=HizctIdS3ydwYAl`_6mZ zU0Pye^FhcB==;C6w#LtX_A}03yuc5B@Fz@8PS9#KIdkC*Pd{~qOJDyQq7!=kjJJOC z4!?c(w=CVe$HwLcMNxR=QfrJ>h|(lU!u2n%)6FtYo;=0W)D-vc-{;iSNhT*J`KMQ3 z%0fewYA5-l_uDM}}tKYxK^#}<8BsqJk}ojS$b+#J`g zUGqg+3SYim;osHoXK1BZU0vn%H{Rgmk3U8!#qs0E`Py^O(QGuhcI_IUfBtz{R7Zg6 z>1i%szRZb}C(&Aao?{G2qA7};RBMhMI>ON-3p{!50!`6~^C9t9u3TYp@i>iU6K5@l z4pYhI#^X`C^SJNg_rDJid62RGaoT$nnfLbvN=H3AprglkK4RZ~pZC?oA%Aa(C;Etg>WkcbEKa2sOw(WLtP-NN-2^g8Pp$wZYI=>y<_(A zLBRE>`$ydqb+h3;{9gljia?^2s`Q+4VDZQ3hQ}fA@$Yd8-|!&oib7o(eN>Qf_`So| z<4nvbkaM`-!|&R+;8aWyq9Ex-t45k)vkd6d$P3awA;d?OT1I4qmHV-O)>~m+2zjy< zGla@>3}0bfTSvn}Qwt5HNOVG?6Q6%Ysq&l%(L=N9{K3FR^=>a*L%%SrZ||_Hd=P9C;t41UmUnkq%?)Xq`ig!bHpC5Bu}81n;JE4nmC!A6 zZmlmPM;6%X?XaD7{SkImzAFgDjpDix2|?w@f=KD9=Yk-jMTJU_fW*99dSE^y=oG`z zB)B)IO3p--A_R1yLZ#KMt!x*va~sAe^colrePn#sSjt9m8j|C=9u^9Gae? zvvH43r|T$eua`{+YH`Sep95<)#diT2@)-46Nb3G7sgmV|7*hjv1i^;o*Q z#QVSf9mWa$ZjVH3iacX;eVzOF?(tx4oh;AMI^<+RXe23lzfZ5zVS9Uvdw1{BZnS8m z4SLoK@YWc=UO~tgD3$RjoYvRtt z%XnTAKOe^%>V=1($`AFhaSUJMaw3gl{8gXp@2Ed3K`HXTOBn+yrI$nrh6a$(dgub1 z`Ad0=c4vUM&xpL(SpM3HO z*RS7X>Fzz8DVUp^;|D+dQ_h|}%l+jQe)iL!vU2}E-}&~7y!gV4JlNXe*DwE?t5>fB zKFqGy+ojj*jhVke06FINs=(@ME<7qQOr>T9pDvb;h>$ohH4=EfE`Z`@>SYuoo(DW>M< zOQ30LdWyNZId*oo$@2`bW$31{mLy5o+1}xeH{ay-H{N7xdkd6cjV!hm#7IYpJUE<(eebXC^E}O!IRQ&BqP8dM z$JCX4vHn@5?0USz=Y37YVS_iMmu>vBwj@eJ`%c3AMH(8~u7X{q%F>t&?(0D zv8wPuZRniGLj$;p&yl&jvck)+yu$3zuj7XQP)`8OOse3+Tp84$<%+8Xb@^DbAfe$HN}OF!$O zm5^wSQG!inA>6s=ud3 z7s5isisLRe8xENbe8bjqX-YT{63%~gj`4uKu5vRIkF%#89+*_MeNLbYFePh4Nq$?{rl@|3Je^$`$$>$7}6&GB%LH2I&=uFHH(Xj{O#ZVExWtB zEG;eZ>Z>m^J<;Z`|MIUmdFm9+R-3fh0D;Nb85WP9@MG2*rxedU_Z-hY`z(`_lW47( znwsMI=bz`9r=OwQ>G0;8Z?d+wMwVyPuZpL~QHa&Mwz)wh9b#0tc34ge+}_h8fy#KR6o{kw5W>$u4n zhX9WQV8>nK(B{EsBZHO@s^PhBQ5dEt4smp1nio!-;!;y#b?8^HkecANAi>gC%kGBN zib4ru9f^Q71%(SD@4P36HU`zusCJWG=h#(3+DOO?S9&f;P)uTEP`Q!Eu44f1 zoDUe$IA)tI7FrXCvEg3Iu+xW02G6Ls5TMU0`x3(j;M0Ya~h7E-X#wiAW5ui3&n#sdnH(VrXN12#3;& zRHtZ?Abto@>xsg&7n{5wE6!J(G}idMP{HHDhheFZ+pFrc*Kah5b2OaeSaXU)jW&r9 zrdksurd#8t!53zT*bD>n!*e4dfu1_L{esnvT@nNLSJ&wl1+LK!^Bxr<5YgeR3?=Zb z0V`S*?TI#xb_=8+Ni?~GtlPyYpCG<4hP_U#_8$LvMGxhZZTDOr_FVMa5K<*HoaNT? zGBb(76a`CbYdGce!LF>V^N)Z3Qyh?+0<9HGckUq0dpUV;F^4r?p}X<<7u>#iEA*+r z8q4+1u7MRgyxR(X9Vo2QnW;D6G%J zK4TMs&QqID7nH_|YnR`7!_kn}ri){V!wmf`3o_w>q{!5r_q2`xiPYLI{bfcl^GD%7 zqvpjM7`zfEQY{1Jyw=1l@qHDoE1Fu;$vC#~hf;`E9z07E&OLdaiRmdsD^5Rg7L{tc zojtIQW9?(i&&{KiVsdJND_1TtJvGJZ$|^Ume?i|`jvYJ7OE0~|i!Z!DYhr>sOZV8= z++ulUg_V^xM1&`wyvX#-3~#^v4)4DATb7oV$csGe={VMMGCXIfG~1y$tTP_ZGrYf$ z_a3rYu(~A!K15-^_^{t<&%`qj1wc4)Vv(1=^AhLJoTb@r&}gPi%+2$!Pd_I^!|_G4(HHKMWG68a98z_ z31Qz#c&qgRAX9DSxXV6`?{nTutzn(g5gc ztpJsCWcaf3$fT5WJ*e-f017F!kVr405un@~B@wA!9CwI|bLm1~%68)D;(H=(3!s)O z8yeiZFf1;l%N|Fb6KaH-wef47`z? zSg~QR2z9A08vA7EU5GD>auJW<-tj=N!K!eu8?#>o>@7(6eix-)jn?*#i zxv|00-Fp~gXf_*IYsqq7k=cr%#d~_$euh|wGnQVz&+gtH_ix_e_~D~me(`HqW9jw! zbh}*^77lUY!bOTAWeDeU^LMV~(`tc~w zjnuqF1HY3nwkQ`2bl=AAhPf%3WsTOW>hIDaBRm`Cv(o=dGf(+m;yH@lDtC31M`Hit zI@p0bs)WvAZk&G)!DD-nD8*!(LMD!0k>m0{%2>2cyj)TgA+?30(P+?267Jkv;qU+9 z=d{umN@;Ye`P%be<Q(w##xu`6!{p>7OG`_<^Ugcmzju#Dnj#YuSZnF^ ze5xibLaW^-3E||qwWMiEv)M#zO;HpfY+DT&Ym+1?^IW&uZRY0YP|D}m?(OYyX7L!O z&zxrd$Pw07AMnv9pR(8KkTz0Z*bRRkV&2-iQB$*Mm*sCBGg zKFT!y?(a*IwA5Dxi^8X(Fa!F){xdReFOI^&Mgj7VyLOKQY=);ul#2OsDrIzlwTw_Y z$Gv+qS?4 ze+XBqN|Zkbnhw&LNT?=QKvUqs@-j267Ao)gaESp1U-lAkj9Rw3ViUmx2};yNGvUgq zlRP`WK$2NDX1BpOjP=$~ryK^0Q{L;r;>vI;ub_C@N(|`S;kGl(Ru2jw(O3;ifo^If z6|8o+L}5j+%3%^HQYe(Aa1Kct74<6zBVZJHr-$t2w08;?iUxW+qnH+^T9fE*7B#0} zJwJH=*3V{rMFdaqn#~6D^K(o#8XTIQVSRgt9p^B<3c_H)7OCc<5&&JvFD9Bzj?c}Z zdVS`S7EeyivsLV|PVhGkN~8m?6GsJCYVAn$XbB>19h6a;9Kq?7A}hS-Qqn}k=S9j* z59dP2vUBu{J-=^NED#bypd79!$a0^TE()2Qe%}ki3a|9yFgC-f0)weSFN3UaUGP|u zK{`?~s)K3XzVjBjZK?c8l_PN6GokHakH%r6P5s#cCK!d~8X#E;hxoY&#B6{$*8|+j zb60JOYLnH*9^W|}YETdT9%bFF@8MA(Zk-2}V^%>IQmQtdq4^6N&C|re^@(>yg~i2n zub~4@D1l8y6hypdTFm+9Z{5#p;_q?n^|gwhjkAaj8_+5sa%c|Y7~_3PLU=Giv5fyB zJr2N?5?ssmque$n$hXJ>DRt?mvJw9%O^b>6wT+1hywSqYQGE0rN_N2A#wNi<2K zIeYdzM~@w+*YB~rx5M`K7IU+6oI7`pw3(2m4K}y7+1%RX;*(F&YBgD1Tf^9bBuOxZ z@!_=tf^_NI=;>huCc?3_!3!1VVH2mIa1|iwYI_z}dHt0zn=Jy(^@A2_iZ_5LQmwH> z1Fh$$UhfaB)QVX|8eA5yh@X{f>FV3K68K}ZNN7nUhEIF?R4=X)Z}Z%wzUEOJiF+t? zkKoN5#pAfgYI}+>?n()$z@_pw72oO8b*OTTHF>uhCd5KO`!1jL-1EiS`j+3v2Bh{g z9RE0%4x&*YLSTOEJ|d(9TU}7NB;-G>(fT-OC$^*^mB%1K_7y5)QgtT9I;5eHh7KNH zK^7?KuYXsg+2quzQ=B_@j&ok3mf#&OmW5+SX|@{_h2_kdGqfisIW#}-^ID2<;lc$@o;pQQ6coAlmcDlN zDp{7Xyt>NY|J~nl_S`uZj~zoPMZ4W*YHEt9>1o#2*ST^1x-S}3lj9~QCrOipRMC!&^%i$--{#KJU9zl?h>wce+T7%`tDj<=<>bi|G+W-Ibd*DqbB=DW$J+WD_wLHCx-;z6cPbWQE8ReeDpfG&qYhw)DQU#)2tId_F$2UlI@dX#^H=5N2@D_w{?m z;UeSidDy!j_j(w>TE9lkfz-aMx9LWFKHS#hUlmW@*MZtKfFVWB>>l2iht7qo8Ni30 zEpI950zrlg#;gz(SP4B|ZXC7d13F**ZBh5CUQUuGXst0`>MNy{L2yPYTKGuV5F_vT zefQh&ypGcY>+f;S`BY5hS5exL!H44m2Px&_1~BTGQ3E~*U>w)R(@HWPM=K1TVFmhcb**5CsO zM14)d441j6irkW|?~&bpK(kZ$#N4UIrj9HraBYQaCuFX`IYH%)BzI`1NObDGQjH?n zEtt!iGxO8ekGgnoV>PER1-tElCp+m0-IW zO_Px1329&GiXD{o001BWNklvN*rX6eBe{YFAx2LaLnrJ^0wONs9Y%3A!p zoQp{Qg6E61q&9etG?~NXz%44ftH%kWEDuG zfl699G}apK-o3-tg9q&H?l3voX5sK5ZhY|tE!E)sg^L_sJVs#+H*VhKy?5Vbb8Cyj zI{xrazt3O%*cX{=-f8v7=KH$NFHTwNN)>&59R(a#iZEoE7lBb@$$RGUR zkGOE*Jl0ySe{r47&F$E=eW3nuLj2*q?G@Vy7T6Nt(4mB_2~<>RUu?tYkPAg`#EXXe zymf)qwKZOT`4!%J>up-?2^wh=oVQP&JavM9{ont0?0KQKdX5P{)j4oBltoQS=RO6iTAWIB3_^l=iLRt+GGV{ z3Q`=-O{D@+DF?N0q;{Vd3k9r2 zf;<&H_$yiyIcrp`qE}c2OqcMYi!lRl(yT$YuqokZqrkOALBt-|Me9=uDIGxh9P3~IDm!?lg$FnS@@rB6SB&5}FbsARV zYOZ2sG5W+snXKI4(TL>A9pk9ZKxqU+DD!E856PwU*C3jOf;3(`7xlKKd?xPaO75@t zuaoFhL20-^l=mnWP|j5;7F;D`#c-wrHPQ%6V z#rgxdU`Ptg%UK_Vhfw%zCEi;xJcqMB=cq(4`dWNmRX+!Zpq!(?VoXlv3^^847<9<{ zhzNxXv299dwI?`wbb(fT0%!aPv#dv!8`jrWXru`XhZi_`>J(X?v%0!Uuh*mB_XRp9 zCMRh$8)&WR=NX+|7iTTqPM0J}xOC|fmo8oMX_@!-h2G&*S`Yb=B=A#Ap2j&ujJh5MVAV%9d}saHvY6wT@5g8x)q9-t z{PnQ_wTga~(((a-<-U0wpaD|q9;15TO8QnAV05B=E_;czFw%{|{v0^};h!D)(7EV% z<9w7T_*}`Z-j}NVhv{eI<|6+7&~x^XYZQ)HUyD)wNuu!M5n)sNs$zPNf|ft(0UExB zJ2=i0rS5$*+~(tetHa+t4&kWxak%dTW2(eUzj*|5CETMxPlhnJ^1fi*Rz94jl_VUR zoT1s8q}%Cpe`}KzVd8Lux#?-PdOgE-e@BeEedNWiUMmLN!mh(ViST_ zmhz%cx7z`McC$(96g$0w?FYN4Jn_{r9Z>pLI0oXyhSubu%ZWd*Il_ai;GHk;a4mBr zx%Hr-^8$eDhALu;(tF8+w!y2#f)StR29bl+D})e5v$DD4w; z8||>ld%_rTSRMK=5`<7}X+gO8SGJLaH|QuM)DR~N=oAH?eR&6G5*AJf3rUk{rEye6 zJX|ne4}zsZ*e&xO$Y9Q-u>Ue+$P35x#3avu*Xj(up$&Sf!{d~l8*zO#u52Zf0v)TK}B=8 z%~8*eqKgL^ioBp!XWpZPoNCR7XbpH;mW(xt1 z&mH03-Mg%8Z?L?+%I`k-kd2KkOi_^MIUj%YDOumo!_AvFSy^4B)7isVi?b5KE-l@x z&zE<<xY)x%CDuo} z2FS*R@k!+&;Z*G$I#)#`gf=305j`|~dJlp!1fXkP!ZMH|!}H;)|3dUCld$i7fsyC| z=f4fgl5PvcY;Nvw{fle7|Jx6^e}5HgT<|J3y!-CEOiWC0 z<;oSFe)?$+9XZU##wPcc?vrH(=fH??d-DMweSVd#qQ}h}H|Te}WXAFC?Q1k%d4*GF zPctz!$;9*|-CmzhUjJuae&uBzY^EIQ_uuF8<;z@o>Pc>Wd5hh>U2@S#ufgnIpEKKioGIuud~Qdr5^z4Rp>m2Tlkl~} z^L+2(6HL=3Q3lmYK^ebmov^c0@XuH8qBlA$H-&CP2Vkv|*Mf#z8jyW@HXHS4qOsT6 z;4SFNjPO1p$=OoY^D<757sE0|uqW=Zm^#Vxk19gMhc^;LJ4Knp*+rQ?@xq2_jDBLP&VYD4db+~lA*$Yqfx@2vnn?-M8GPhZ>Y{nlsmMD2NJ_* zo%sC45`kn0p~q6rmpIDm@ukG<2=b|;H8Dxrp1_IE6D~^7N=PNa^zu;Y+k4y-k9(AZ zf=uC45Lk4km_X4|344V>D@{WuB-ZnIQ5s_`@4ovUAO7xlrA(p(7LP9Q%rnn&{^CWZ zCa1B^asBfzc=z3R`26$FF~%@EJ4>U{V0Ukiz0Mw4meK8Y>2|wJwkK$}+W@SsuF^EEptp6;%u<6p0P8zQ&!}JZbN0B{;kY(OUBl-&$wQ7Af1vGvkQLi}7-%@| z^G5+Y<7Tgi)jdkb$>WSc&$ZmxHRF!Fw0>6Yrs^T}<{KH?0p( z6KhFPEG#VW2Y>v1o_XN~mhZ3ezx=!ZmEFw;oH@P74}bJ09J}}=Z@>2u|L_0(pV;2n z!TCgk9z3?zbSB#k0>|Ve!}r{_}tSDbh-j z7=W&XV+#wMK68q8dxDkaWoFVQmu3!gV*VJNwOw|0R_P%)qIX@@ulhb2r_e{su|xn? zf*_?8JGNji0hJ`-bHTZ!66k{W4g%s5KQlqcuNa<1Qc7i4x%j>^o(!cz?iB*ru(kvo zz0hT7DDU#EwJtqw{oMM{E`v`#WR=4yi%}BtTnbi6St-rc=3pG-C_OZT(uKq^345Im zn-4bWp2)a%>jt|fgJfbKAui8^3)wi_;MzC`Ik2f<6^m-O(Vbm%o_o9`o*P#IfWua; z*iJbnRe|W%IdW&Yy0(nj+T#A*yG+fs(GBG*tBVQM)j{h75uc++X}^Fvq+}4SNz%l7 zTo66D6lt2Gl#qy5PH1ad=MJ+cj@6BA`mGj4w~tX?sSsPKwb24s2bJU8j{`BQu?Hck zPD>b1GY6g>16Tqjs8_$gY){2@MxXWJ>)}QpH!#+N7}=xni{X3gKxJJR)*(No#qPUz zxE&EG0eXlKS3P9Tl}Y={Fx=Yn`-IZ~2#82f-o2n6g@pKdRMuS0Qd}<>G(1;z0Cb@5 zW9D#pKaHBpNDm`uA_2^D0T7Aj5*%2Ev6j{471mbPD07G5FveoLU9x^32W)L_asAG1 zufQsw8@dPpg=oc{J9oLabRQLRXE|pAa1rzGd0OO+I9_?Xd-o1M`N>bv$}1729HQd9 zQ}Wna?T}L0^q*J4atZ4Q6k;&SORJ^l9zKlBSqEj&HX+6fs!*`T)!He6dQm>1x&ySs zD#P0PDnI$jPiVI%xqkf?dDf?}8P*xB)mRD2vva)v-Us|o|KtCRk_JmlcX+Vz0Ot}e zTs*_cEXo+B`i4sj3v4YfarNU5 znVd|40%u_|NjWh!&Ab+lieke!@?fNs0GJsQglF-5Fn3;YwN@a=Le}~aKZnCO4=R?Y zHh>Tk32zY63Hv&RmO8POSDN@@8CGI3IS*s12*?ERC;EY6Yv(P>1jKu){T6OO0zepL zI%`e&o@hXDSf9cs?0s<(@`g$&Z&`b_=+4utkPp_zP&TD%=gEWEx-cv{#n;nWPAfr?;}XF-1J(n<#x}TREguyb_YyztleuN05L{MZbK}95IGNV3g^2LQ=U?#U&3{Ldv~Wg;B76m+ECy4` zGu>{VH{SSXe)F5R0Wj8Lih`mj5IkVGys^Rm{(t;?(niDc=+Ms(uYSr0pM8uZ-iT^! zILFqnR-R{wv%K;4TfF_d-%%7f)>-nbK%7E3?H48i>k6#%m6%`t)vsu`niN^igaxGcFQij~YIu3YIcBCbhZ|jze3!&2RNlulL0WB0uV8+*#aB-r z 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ۅkr>2;km?TLO=W1SVsHdKK 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/S z)Y#KHH0iW|tYmt0C^b88A`@lK?gvlLoJ}#_q^bQf@n#MDf^BGk=VRd?2=RXd2&Yi| z+_FIS1Nljpu2MfR^mRyNU#Wxt2KUy@7YNwa zSu5SD;Yr1*EuLQ9Ko}JS{u$aj{qq$)#`yE_LolZ5?Ri=`iEg23lj0XMcVG{H1Rr_4 zXw*o0wlW{!5+PHkZ}pU-06C1V4}A@0<$K~>0yocA{xpB%rBRv?1$dEIL_d_vA8v7j zBhVWn%ECxT>2@mWs`9KR##SsEa{v*{8FV{_`^7?^wtRCFDHR?66JllyOknD1 z?|}-S{{iZ5Z@)8vM4G{iArlinqWnQS0{i=86wo&g@N$rf9W4R6y$xMiSz$6;TU+CM zTm~d5cYMC$R#sL9L1^tQN_tPqa4*cbX^e76SVeq?rR@5ExPVvbxE|No{8Y+C`g$it zJP`#WvYcq-y4{6^I$|a8d{w&C21aV*F*%)bWjDY(bMSV&`F{C5Mx#)Vg5#M+d1f;c zFP2faqTI|Gne;2qlA(ShI4%DZo82bM5VaVEi4MWB*&Jnjl6Rio`4nA7O{M9V1RF|W zmY$m>c|@Ah%;|pFFWvR9&f4*m=_Y;WGK|2h#%W#Y4#yzim?8MuiJ6v7uda1Ma%q_o z`9oKLZK48rhbI`X_Hu&c8<>Y?%&?}ug@|?^TW>Q1^XESK6pRHNm6$o)7ywAmecz|# zC<>2T?^@ei%wD51lmBeU{Q`$%SS*ZgU zPx0vCz-k9`CU6O#3}`-pVxh5Yq)j+@-+y*7>*Yk2N@#?n9|!}N4^Tqeuab6+Ku-vC zE?GNXWWKD%;jzpBv2NxU;iAY&rYqf?Y#D4C3Lzv95og=IW3?M+EJmqpSed9>dB@R8 zzNx5p4m7Pbv*%1Bi%@4seg~^nc@ARNN3#}*H-X9PwD${NlwQ?L#Hmr#kS!A;fgl#B zM`qASb%tG(16-fDRm1|1kZ`=btOU(+G?JiE{%Kzre@GGc1eS0dT8u~`@Tgnkijo&P znXo^LQ7iFAGxw-(Y@x-d!Ib{R!lYdEOg0yi9oc`!vuF<Ia)8@-hUd5~G8B@+-cpwjzsvKRoU6+2izMEt>T=b9Cqldhw^u z0>u+f&GXfVq4ID1v^+xW8n0*zQ&w9eK{S)-FlVJa($)PhDwmcUHE|BN=s0DPh}-tO z^*Mtg8TKiHY_8Y54Q;7pRp-Z+uFkHZpBt-Khy;96)$AZ(#oZAhsWd7?K*w-eP7Y$* zA_zj^C;KRj17srpwP7V2~I+OK>B!MGAW9gI&;)trb*M;xAjZp}>4mnH59AWnp{G(~g6 zoLq&T#8nk9!)-Fm!n8&Z%n=NE5cF3a=jXFLgAOoky|=I>1#$K}&4~JY$E#Aka7y%8 z@X)3FE_PWqncG-rg$;!Ub`j;(YYEC|{tdH~-`{aNfHE>l`DiJ9!JK8EE8=X+d-7+{ zW~iU5uN5sF+@3#4@J}ct^Vd1CHH9gz-{$R<=*4{KlOG!s8|cz7jQ8iJeAS8hmCR`t zCVZ})dKG5|SC^Jr7hQVMLGh1rMeDeqv%w+bLM8NZ=~#|NR2ic`&N|U0-hX^$CZQ&- zu@hcMY(DOMYPr%5p(kySsUd_Ny=J zVnsx#qRoE;8+$BY`;I-M;-kV=&2M+GvsFd)$u3OtljoGkI!k~5i}a#1mrS~z7dUkn zhF)CJrg?U$#Cx3Fa0`QXHSXR>dgVhr8TCc@1qN*>AKGkK6_k5cM|h*nZo&g|VwRo`8-;7xp5 z0&WfMLG}TeR?#l!2sFBz6?RzImgKIT&tlf3jbxw7ebC+!>OMrEo*NX15A>4%vDd#uE;FF3K2?6 z`E}1P95QJBnUUI3s%4Y>lx_7g&1(iMbyEib0ux$H_2m?m#`YM& zU7sIcIBwVJA#64?U>v;kZ3#x}39bj$MH`Z{u|S)LnGsl!^+<0{X1%MM{>)r(iX|%7 zp1OmiU2oBOlrFPXj$U#3-Crl)*1DEG${`5WOQ?S>!Tr9%OCz}oK-59iKHJ=HDMxGumwP&HM=bO~8NIT{)C|*38AdvcD1u_F({C9!Ts!Iq@^J<+*}d3&_Lp zJJdpf@{O4Nz%hYrXEd1>||<;!edtiqQ}z0U}{ zu9uMH0}R`ZHb|@-KJQ*@j=hhoj@T)~50GQ)ySpF=m4&$D&Nql9+Pp?S=E!LZu&4T- zyG%%Ow`NUi2ra0_9cYs8bOl$67q7_v;*omOCLxc(;=i7)>wB|lBEY6s%a2{_YLneG z_VluUOrE$C*Phzdw;dpovr@_E@SBZ$=f-`-FX5sCo9Qh1 z-aZw;0O?^wz_i^k*-KO0{pZrNu}WB2R5;XuYs2VStmoiDmhbtJkoh>9$>Sxh*jR#g zWLdXUNWf!USxa4S3qqfX%h1T=NAZuoPgqPw>+7DU%UNmE#ne|SSE)n+A7~ds7*;Uo zqaI8u9rm7(7ry}BmWe>4C5r4b!`9jaCh9Z_>Fb)P83U4eD5k{gu2f(ECWJ#cJPCVa zn;Bdi8Ohc|bHDmvk~t!u81`2PB_8_i!t%G|xuJrQ;W#$oS%wA(1xmD#hOEn5p5Y{g z_JP}1sI^o^r+a0lR~@X*9sMJAl^<12>?~E>8OB+NrRMYqE`uk|5~iq0MbFx2$nGW5 zK+HY0ZSXHOZcmej0CAzIbGVAnO=3@p8HJ$7Q0U_MAgYyKg2z&uRR$s~?(yY@1^YOB_mW~ia1d7fQz$`Nv$ zvvb+P*KnG`B1LqPF4Nr$Yj89H$Pl{%U$H2mB|A&nSl^X<-UJ~+Wk4qJIa5Ad(t=)r zhn)=)aPa@G{G=0@vQ5O_2j*fzhsA$7TdY$x)P}`bOn@F+OyDylsL4}UXhgv0-ks+* zI(f-3moD%3-&qz~@EL4v4YS4FIX*f%y4I1w!Lx1ZZB-;=!fUsb4NeY?s~mS9l_^TI zB$_&i7#y+K(a>Irp+(GV*|WTQbJ)Cj^};-LjOhV}fnwUAQPqIGdWm3~ zHDCr75AQBLBMFkcx~?(A;~+{>DOFb0#9JBgq%&n&aKe(4l@-jL1o)p=i?~^YkGP9u z6pi0v@7)yfNesgK4dz7#qQ5)VZabSN&4B3fY0^rlkjLwZ12i{M01G3#~BDCoVr~NpBq+xZ?yzcsE7HrGk!Ni)*O_xx2 z6c382H8Y58sb#EbNwQI<;DWm%g`N6&?_{coY5f5!e@CYP0e>P#Gut~V2L`vOn|m41 zWu!r=agTiOlnRyd;r^dD@VKgZKcWB|{QFAzfp^G;T z8}2i1G(2!n4P!&$UZ1kNTBozO)6)&~Axn_|@wc(Abb7fp`Mh2sFg92Bn_X+m1t#h=jNjL(1(W zUh4Jze>#S#qcnA`pBg#LnJb+ea$IsD(?|>7H0XR6Kt!mkjOVVg$*d)%gNp5$m!pge ziIh7!a-~z^DJTg0|^vmwe_{b8RenPGJAazIxxsB3<(}MQR0)&t`c| zda!xtex0Z&umfsQU>RW{Kp$&6+1q!|4JrON<=4>A5QZ2`g#7?HJ-u!QQmt)lm~*1A z5Z}Ds-(s1eK$37?UOT#BH;zCelnV=W5Nzo zkcnL(f!Z^X`N{WHXLDu=@Yrs5jQ`Y474xXL+~%8|d{A+Z=n7iKz9&1noIxe_d}GZp zbnLTE$uGsub}d!6-OwM}+cxOo!06Wik9%x&@3m2Cb>UgYQdzC0xDL8-1Li=wjy5rhOu1WpGWlV%INWy5IuWVlymwb5<(+UTsPc|k zW70j~j*d#w&eaf_UnDq<1HZ$f0>&5m)%YCsrh}{cT9ABdVcX58;<;W5t3af$4gX}@ z%vtvB^_e2}4+v7$kV0%d?@tuTc5hU6(kaO(MZLsWk9(wC0|}MI6wWkeV)>Zii6F0z zG3z`4`=!J4E%%GF$kl#Qsv^+9-3!4K3hO9RW(3n>_BXR0ZbY~*DN%S_3H>nf$PY`t z;-^#gK2BbukQAvjb0#X};9ir_Kl=);K7Do*ejB3|MM!m@6w6g5m4u9!$b1(@Vg5(f zf2v7D&;4Dbx^aCflb(HQd#lBI1qw*uR0F84I{JO8@cBs@@~rGXt2(u=JFHUd!l?sT z!O&>IOpv}AvX$7_e@ctk@zr}9M0B!b(#haURPG)%SSNV?HFn1A+^Z9B>Y8O`XIT+* zn)7rKP(!;YDfzK=$P(`zX2G|_euOjr#`fUC@c58X5%DAJJB&=<#@9pUU16?KkC(q{ddXAf$n}eBr6S(QHlS)lVg%trl#~lWb&$~~d%la@a z@YTKehp)#fuaFCqp>9HktS}-~;%Mm@s57!UeE2Y?KKqEdG8H&jb`XUH{Gm1Qw#oie z5iEm#)$xJ&bh&Bd=9`No_#U_7JP3SG6f|X-c(L<+;5@b-ROSTzY-CgP56}^5%$lcI z@Yw0;(>sUHW4y^qjhm0>P!0HsAv>nSv%x`xML%weoj4Nqr#(1~2@hd}n{@ zHi3+eJRqpkzmh#1vUvYGY-$(HQ>vUfZN22Qw3~-o)?#ckb}rotUA`>J_6o=|3OXgJmn?kLKP54t@v=5f^U{$)l3dyL557MK1W zhBmOIZYv+cTtg&=9@7Vp%W{5d_IfIEpK{oc=PUW1d>$jGpX!X>rCtNVnsyy1xKxkz zCp=>UD&NV*wrptkd>D-Jr7$)ktd@$+VWRPcxWWqR--m_fwa_3 zt>KsATi*Js4BWKffiBK(Pcqo)JsO6#(n=q1$=ywYV&(4kmz}3AGQqLC0?|)fR;Ot3 zFQRSbQ-ILE4^6E}ZNmPxS;*(+8Xc31Q4`$`k7yZ9I!4BuO}EXx-2t1DIapphU1CF! z%bmpc(d>&iaU@OfV@}Y-%`LS@f7?9|&-$rxWp1b{#|HaYU;R%=%2>>k3Y&0|xH_~?d&BKc*7ZC zZ`7$>zYA-SG7xJ+d|LwWeKk zp(Tt^?}W4!>H2;Pl(l!c=<6K%R;-qm>$1fcutS*{1*a>hw?BA~Pxy{pd8D>9Z5+%@ z!hR?U7Pj+|+rVZ7MNgdVrwVNM&An-N+Gq1G96X%BW%luAf>3Rf??sMleIfGY2XY+W2zP~hU`78dKZgs5T<4TZ+T zJLjc?d-DwS*L#P0cKGN2<4;;^-=oZ^Jk7tF)qls_dO?EwL!WB}o^a5>PgGORm-B7E zk15yZfeFFg>?(L`1>3r}!z8<$uKgK1g8KCJ{8B|O_j~Xv_NOcDqmN5ST|q$ar&7#J z@4^_Sg&V3uj0Zw!BKP~R&hie8p{E7Ug{Mq`CCDF>S!BVjPp8L~h51Ee;3b3p-5gbq z;UoRvwWEa%o2{-kK*#7 zjMplqROx(0aLXT0>mS`ywg=ipI#n^TCy{|#LF&(quAWVs{^WC>Kw@-Z?@5(Z)ha&^1E zr0YZmkts>ds1g&ydI&AaFQ*qZQu3oWOl6K-NB;36Pvyef1|UI3&^0?_8KEhxzuI`2 zmIaqnz{G?Rt-gTyjp9ovAwoZIS3HSk>7iU#xAaFzN#Yo(WmxR>Z9^qudXWp;4&)$i zoz~7@kgUcX0DhJ1=&wNXZ8~SaWuJb8WuM)vC$cS`^or*K=_i~_=AQ+z&=l$(ZP0EK zFu`f^C&azQIpD z)fLh9-V#w=ilBNzyahDMP*rC6_-n|p>~Evd!jU@B7}QI7it7M_${IV6&r(32C%x9Q zc53#fPW}%Tg0^Aru0X}Dsu#;ks-DjXh{4q2{f4;Zb;oY!>c#}VVVeq#L=aa(?fLcw z0;Sgjfm6k@sp=XUJ9}Hc4@%#}TEL&e@N?1}7sNL%I7EtZhmq58P*z#VAQJXH`3J3l2?CF{WjLaF-Zv z?s7k(6e?tE+(tnEYOUp+{G$9HnV&$&?0h&5QN*_IeYzdb|<6;)7(Y4JYdxNyzf zdOvJF@9^1^>m**_#+A9*xUi|Fr%#M7G1g=XhurmX?f%y4kf#{_{a(Z{h>bN(18Ob` zLP~5#2^Oe0{K+Gij zC`?&MwfW+)TTjqGA1|OXQJhv# zktjsjunk?YmMGm2KFeBhHhn6|?~qixAVU8=r*J9`MEFhFEy}&5seg zw{Gw&*%2qZO8nniLotS5@cS{45u8t#VAo{*x9FOflQacho()N7%2wai!2w>6l$!bd z)xwD8LVQJxbjOh?yHi88PBFjF-V_S|#N&tw|9(cS;Aap>6Ti5`VP0$lRY(ka+$yUp z?QeD=+hoU#TX*!oh|VA0n|nz2s?1(%lJkZ(H2^&z5k^dso8~jhn@05GmgLPLAi+A%--fRl+JBmYZB?-Yj&v zLIl3-r!{W^$JA{M?`b0NjMx%a(u%8GI5yh`wt!i69Ze!WFElH+PSsx5Mgl8@4zpKI zXRA;pYZSR6yDq#Y8>*C&b)S`^9&^8~atFSQ);r22oW*rJWr zMeBgKH?J#7-(JD@*BZdXwcGn)VQJJoZTtv zWlQ6C4EWA4x_nOW>6dHj?lr1^k@WUtSLdm^-Hqi3YO5! zo=?9!6s2k8mtjO;>x1!=y(2v{D<`P^pt+;O70PdVRtrKXf~RG!#rnf+UWh;n9o#QnuCltvDq{hI&S? z$L#3mCv4Mww%VW5oXo-@Y`JEi()&$3+c0P4Y(1kl)tkpPySuBe58z%i@Z$%e_dUuW z7U*TwsXMjmYgh5{lel#Omcq8i-2+aqg`xJ;JKPUn^b6CD!XiLs<4>sau!KKG))n33 z^%j}Dl=JB0BL|4_v%kL|BpFN4ih&rvq2*U$$ivCW+3J2n0?K3NOFTraNK zHMF;nfr6hkx}CeA-yr;Y9{R7*3p)AycjU3v1{$Ct$aB{b{j;H_a-+fd%q?*Ef#C4- z=J(K6gWS47(-kgS!K{3Fcj>=ZN0C+(8+~ZbO*~*V{>@#T|2_QGclo!%qFK|{g&mM-3bZN-Y$NIM z?Vr_j8Jm>JuG-!WnB~r0;X8S?C<vh3*ZpkiuK7UrX!%b4f{EOQ%EInL8GCftQM@G4m@#IJ9IYn zKK^QGD2hAyN#`Q8{klN=KH~-);+lJZEJWhwzi`+)1koHAub*#J9-p416F`X=auBh> z4tNJzCqp|s`E9q_2FBDFTRjeFC@CquKm?0>=)i<}YQH=;ui}0cqT;^dU2@rIut%Js zalOdyy@Ii=C#baw61(-PgRo@Nebt5eXy#9@0g~loJ#+((9LMKf_SBkn(cl7dzgaT* zUoaXui3fn;*#==bnUI;(i-<61F+cto-5)Prz*jRP(S`op$nGvA&1XN6h1T@B`0Ct> zH>E#gd^IYVycY{?rwUD0^ZCm6S(tTq!K;O$vPoBnNb@h?v+7#9C|+(GhgMWvVl?;=^CQ3OQw7GIwu{-7i`Dts4^Msy(3`O8 ziBpqFPisI8#{FCS^RIXr^L3_B5_F25R=^Wm@jbW|E{#~~0~?c>vvC8Qjgm9iRbS7f z>ak|XGlffv-Faz3PIT#X>LL}RgAU-2&c^%{4n02@BL^`xnrc8hN%BqN+s!B)g%nD9 z-6@LWJ%Ng)8hbU$^H_?kM)cOkfc2NGS<63*1yj2JnlkaidyLrQPFUkBc)S_r7%cUE zn>}A(z4J!8Q_HK&vYBw_#wc)vh%`BALiVZU~jWID_gdxRpNQQ60 zrMt2i3kfQv`6FQvl3@cNDT|%+<8*Khe+V~h0?!3_fb!VrBi&H&!wX{x z>n4(U5fJN8zO+MN`@-q{&-&aRa#cP*wpzDkSkd>E0k^^ibRjc%Q59+Jtk}}BXy9uh zB8fh?6Sd$V(!O9|ts2I6>JG0u-t5g*5X9{^M{RO;ADQ`agp2b z=7<8i9v12M6}^Wo$`3Q{!QY-g1=OxtkgGgUqXI5XQzr*`xMj^xSXhNT6OBJm`zl4- z=Oj{Um{lw87vLK1!BsjZE8QYC^zmE_`_U4Bzb_rfFC{nTICHbC4)!(5ib6HXHr;hAU2YXF%D>a53>FScYg5i8ELdl6gC$$|Ayk&# z)n)0~ON;t}6d(~kZA+cP3oEB-*&b}Rzuo%QD!ghv&0ti9gm_aFm_$4;$@_{tJ$MIpjS1WQ=me|ChZ+ zjFmmhHMRdKt~7sZ!S>>uIq|K4AIvI_-AFtzLvVVXuf@7n9jlp3U)UZ82KT%Q+kQMf>ZaKWANMm@~KjlP^xMU1hX6tP04hIJZ3~~dlu62e?kr@#4OkFU+ z7-RRifpCyXHgiBo`!Wi#@bZO-+ZH5=2@M=!?SNvOY7W|lzGUbWrfp%GiapH81wMV| zNEsS73{yp>{9zWjEKwh@kK5~)3s=XkDid+pb@f&=2b-{pry*XeZ>k!elK(C+xp^#q=M4 z&GN`iy*eg2H>Xos(V^=sDd-^460v%9xL);mCDQ@cFMlqZO){aM%k_}=^}*UWui zTz1*vDao)~+Ni6#H}q}PpHUTl{QbUmC4O}H$K$1_>phYxUA7H1v>N-}AJUUgGlu<) zrFO#6@d(8}8}Dv17(?&)kgc^P-nn~=cDKuu$8V5KFu7rANzqD1nL)i|)#V zB%RXkwA>a=^L}nL~v|6k# zuOUgBJfDC{$wjz+^Da-Ge$MGZpUZ1k7#vL)^k%GYT<6NxyA1jRUcP!vk}k8pzDoA; zHP>&v%XHQ!NfeVYND5lJEbz!D37p|%CeY^E;PqGT{Jl~eYWgbveNq2bzc=Z%#{Nfw zocO-=2iJryJ||u&aBY#E;`b@mI!LMYar~h-M&pf?ATIdl^sp(Y68FjOM``6#iwhta zZMVR}Sc!8YX|R5c6%dy;W9lbpn)Gw{WUNijCld}HJ!bF8Gg@iOCSM9D5{$SrCfo*< z3gxn~1XN;!o9?uF>+*RlY;}uBG+AtnJ0Q){taGpLG)QK9#3(m@MdAViVsa9Ul?#b% zn)fx02@{CEejFbpNrI>ZXjwc>X8l@a4|Fv~VN?g5_;55KP#n%#aCO+AtM?x~{rV0 z)43G*Akg6S1|qd}o$o4Iw$Sf8!(PnMPXRDv+#3I_k`Zvl0z4Ib)L17O56tb?JkRaK zZkBO!a>A=uub9ndoSvSBfSh=~bUdzFsm&LbqDPD|g%8d=PgUC#eoGu5xVVHexGmk> zbg&To|4^5<``)+P9T&`mL4U}>-af6g#l`K5q-l%&gFSoJK|{aSjfd8nH*el>aB$$Z z>zM$;-rgRot1GlxEe3-DGj|BkhaY}Or_*6H9Fk{Qn82UI)8aZ3Lz+lxm7cMiG8@Va z+axSihB(tHbmd~K6I&IwCP9ut!`!`wPBb;Y#aC^*wunppuXA|fV3esmW88g(5@VNO z;|vIfDI}q5u2WGXAiV%bAn))MLgs9H#&CDE5bU*HE>o%-p;z3f^-#5E{qx8 zQHsXcKd22Ru|HOgiC0b@$awzq?`j|e8}v;Rd$3b0VR?B8jbb>NlI0nBuH6a2(bANN z?xuE2)c+9CY~~^1BEVZR%4Y-v{O8$hX4|+R+sl6s<54oGQFdrU8|(NW-h;2HJ@Mz_ zH>U%{4Xy^Ge2|@zL%pcnrv54Yj~kP!S5(tB+b{01*iVUgGmszGS>fMBv@fzL)n^s~ zgZO*h|8@5l(NbqeYr$j`!1H}!{9oe+(>~yIWjVNBZEJiL?Y7^iVke|JMllgXM$SZM z%p`I0Sw7~uxpUTw@ZPV}$J!7j(5c zN*8a$V81~xRL8{rE!th`JL@F{-nBuTC_pYUcNkt>@YB~@M^n6acb@0=u#dW6xG^Q& z8MlF&-(v)11klA^qE;?_8^cQ_x_o&mT1u=%qi0jJ2#F}By*&=T9tmR>6Rl0y-oDzGr8Y(~ zStox@I_)fbYn>fRdOY02+eAt_aBh4Jb8;|m&QdKc$@*lD4q6vW9dnG04~G4G&q{*yg|cqYHlbWyQ}`^+Da=aseq=8jN|=1-n@85J{`I9)-?UzF)yAyWPPIxXq1_9#c#hOf$jVR zjDI~5q6SFi-$EM}ykY4{!eJ$D&;9txV4o9vfR7tHUya=#)9phT@s%GN?_1ijqu>{( zcL7A(CHG#oOo?_+zSp_)_I6R+*X;H)|JYQ%2P3I zCh9QvJ@z_c9pHa0cqtyk1u{_4NdMLy@#k@q7avPn@*`}AX9)(CZMV&lHoo7%} z)`8k+vY8E3wpuN^OH0hAQ}*`udH(!4o12?#ZEXQC7z{8d@Y_KcjY<+S?T^nbRaW0H z2!5U4!@JySAILN$3R-FE_Qj2mhlH-Am8QJ&-n*==t<&jrc=hrn=huU%z08j<<^>6}47IXULW&FkE_af{DC z{gj=TFGwg(^_xyJv?y|=81%2lh5!H{07*naR2!HmMJ9GOt7PV+n7;B`Lm6Wk*MyC; zts+oj1G$I|Qbr4_2-q$dkOBx2kah~*u5^jQt?VIP7txY5zQp4)Edwn(pE~^ zN?BW3A``=SIt8WJd9%ampwEqKSD8)6v=T#-DAF_`6~k(`&Bod?*=)?GzyFA}^)+r@ zzsk;=*Sy(z#f6L845w4JE^P5f@4wGvYBNL>keQrBDSF2zT)wo;-Fx@=@S~4;{P?Lo z;oYew6GeBM6UVFVJbxfF^m_D8U9hI4*S&)y4s?ne<0um$Rv_`dZ5~8IP-|{E4i22U z?Z=Y@Z8V9bVdHHuVP6kdU=S&dA&F$I)=_o;ma|mq`>8H~5$`!y*UxdERm-i*#;n#? zqO%5<*S*&i&}s^JomHN#x;#T_OaUc-p_A>kEmj9W@*9Xz{y(cO3LP{=$TQ61ja8w%gV=qWnO zFebt7(JYwUF5!3nBO(Sd8D^F-nVq^4H7L+CgU)Q{DVI?o#_yk6*3sW4xK-7uO%oVR z`Zn+krhLeR8^_$shiTSLb=Mq^7KDWQV!GnLv4c=|g&>8q0ooY4D_h+E&IjzgI^y{7 zF<(ABVyM@-wSNiZKGWogfB5JzD_!B>5)3536@1Uejzu#lPl;P1H z)2it-6nKzY=4l)l05qpYm zfcL;4_PBW06hfz&=3{7cWJP@a+pWC&oG0i5uKfZoJN$<2&bL zcx>m5g}3-p)Zstc{`PoXLK%hOa9Tx_vt2VA!YB#=+;oaD1rE>=MhdpDJQCvX`>`-C zAdR08Dvw(+9%pA@F1o7k)?&z*-kAOdofenV^n3hS>h3iWTo5wXm*rlKOQ^AOj<2MK z#!zmBKKr?$T@<3`zf0xKAiG{xCF+0A-`Y>iP&$;^trM7VtUGAk=93izR1SbCZq9~qvInclQBxHfA6=?H%3=0 zZ^Th)ng$=bDkfFGA|wn+fzJckHF!IC0fm993{b;$9)Ue?5VZ1A^DQFLFhwRpld#5g zmS|}xKeQ<3+ZVTK zot~2AIf?Rnp+S~SxpwUu_wL>0^~)E$dH$?8K~`+ayjBSsMP^`zArm2^c*j8{GwUn$|QKB&*8f}E!fX*}~)0jlr zWdsRix#sBbh(WK<)$L0h_j}}dhFyuELEH?56AGhqOrFzixA^||9&qi(4gTr(A2ZWK zR#sZ{hJB{943#FR-1Uo}0acJEITJHwG#g`*9HVl#e`wJJF@kc-C;d!xle2XeK=;qZ z?<~4vg&RgCdPN(;ykngC;UB3FPDR!t=vxOKlnZ>geR{|>lktd&7BW4*kDZ9vZ44qH zS=YXA6X2~2GR1w-6sW6n7OVFb)fdw_7u<{tN2%=#%0IX6JP(Z?JebHRGGG6q;9Q;4 z9|BWV>{3Hl0fybb(V;SaeWKUxb=RVx>RElYXl&yvK7G$;$5jKFb$n4*?jl*P211$w zkX2qjtBqCuYYapY^}Pf9_~2B~_K_YxhrWt~5EvoPGROyS~tv_dI?ZRUyN%dmmD@m5t~H`; zMq}BTF3W<6+7O{|>kLRCQ&pf}<)c#H#@Mz(W6XL&E4cKHnUf3;m+Ch zlNPqE5++F@tJ8h8`fx!#i*%MTarnJHkarf1 z)jGmw*QY|l>iPvf_{D$EKm5Z(4)>2Z>`%DVz0V)^*13M;I-7S4zyH;LVm5e9k{FEY z^03z?>Au6m&r?P_9S%oVxNu{e-Q+EvKh+E-SIN~Ya+%tLLkxD6hsKb@u2i zy;^raw)a(eUetd@-x0DDJ%7g15iqgiS!E34-XY`O$Sx(686r7@lS2k4#~5j$M94=u zC;ekY5>%qd@+pIpJrZdlDq}VobNFURk}h#{s7YJO?%A9hPK_Z=W+W;@=YE2!rcdJj zb^690B;)!)SvfV@O&B;nG+w`$LO*au|5g31vfhe$asCqK%kzQEG)+VKeo128RmC=T zya~w|Mi(*`#jR@lEZj1oB`~KEW14I3%?0?1cj`_gKIG&*2oF2z6r8gc^n&NX&AU>)TAf9o7>nZ~nHxS0!17if-UwiP7 zI2ydIGYGlmO(deT($Rk0ye-CmEl>9QtWz()IN!J^FAmyN?5c0>0&H4$)!~rp{ids0 za?>PDe7_EF)x94-e_oy}UxjRy#-pK}rdH134o_bMflSl$uB{q_n(M9TYQS2yh=#UT zD|YW~ZfqaD%W9lb^>d4Tm#4!T*SPAu@dN;xVZf5RIC(o7T|eBBa>esuQt$ z#29S0WWPUTIG%9v%2oCbPx#`?uP|g>yu1wv7q_?h(FZ@~=H0uD##3Iu*=`W@>TSu!KnQ*@q_ zsFbv2L2IHxY3JBgfZ!t8sZ&~u4-Y9T;*vAq|8??U_ab%U6J@=SI}4nj1rnNQT6CS|V#uHrs!F)=!f&Vf)MKP2~?`0DJN)He)s%BxhfssGu^TD}_V0 zkZ%gOS3D!OU2~W!;A1KEVbs9^92;Z8esRWcsOe8gF~;q?X3>K+IYN;+Wv7a@$E3~) zjY#knhPoE%-hYmrIP0pjxiQZ-(NX6l&6O2w0Yu(;5SVykFn0m|LXKeVZHDQ zz-wQpt6e|%!u;RTKG|MB`LbO`zjnMH>94tXqV}t9jAy;jmr;IeCG2E^KVg)1tyRt{ zxRjzYs|5OE787hnW;}MrL|hz{ZBY*3g7?}(jM~eOSKZhxl!=CCzm-k$@1Mg)G|A~A ztU9lotN45|d4c)PMVsT?2$NKwi)oU`FPN(mg5MzC$DO0S#4x4Lanf3Cxi#hZc8tek zogp=r|Nal@cDslOvsuRO?k>~ml<9Oz93&A_VB=|?VUpA)S@|~Bbm2mOuprLL zs6?xxDda6~lAT~sY^`td{(IkHb90O1qhpSakNEl9zaY;urjv;~Ow7OS@SQKmgkR@r za{ZI%d1Hb=wS8y#XfaP1V@SmX!;A?w%mVTT=coF4k-%03w8_y6prPjZ@J#${jk2$! zt%0aQ-$sv?-V?hFM^r>Z1ZE@=n<^^?WfUR?LqhJBxIKGw%$xuEOR_v?GM^>E5btD#CBsI- zUOe$EZuKgSA5{Zw|Ju;21ViKdI|h}4|6hk@h*s!vW@%9=N!mgK$HO5{Uhi;w=YSV4 zU-R(s6Mpo;54mvR3WNTHPIsC2AAFZir^R3Y>hJjS%dhCP+q`)5hVI~mM=zdmb$f+Y z8`|9#xzhB<1Cm6cP0k=6k}=`>dpEgy@okP@9C#0z_<{XR4?`Jux)w*IJTvuKb9d*yt-9OJ&4GJs*6pMm( zB2o%mmCA}hT2x?U;OEPX4d|18~Ht;5QDS-00Yt}&pj-Ye14KzEfN zV*BF*nTz;hQ6HUGt{3%9wS6UhxJcHim#uO?nE)V7FmRrK{5zhNoC{Jj4za`OPjJk# zj6r`CEh~xMNeo+CTZjk;2M6SS%alq#Rr_(4&FnT20RY+wFuQkgg3SOd+@tFI)xKy7 zu=uf`3#3RH&lAtcD3(I&-%V;uyai`EOM$%7$BROr&Y@8#um9z zq~whHn#`n(BxN{B*}VCH3zxRpfBhAs@s!Q=b=rr9ckf)JZ43`T+vMVv8?0?zU^Z^C zwU*N9cA1S*w;#40ORaOPuTk!kYCl!!K98NQUQK;}<;oQf4-XlQMveA%F?J%twx;0U zpeVinSTMB+7<53^e$BQo#cjr8rNtPGXuCqixJf1#6HQ(?-y*me;GAJ8_^}~i+H}q7jv6`xjP%$flF|6EwJgi*L1O{G&X->vyI4oX zd=zhfP=k{qzb48fcYP+zcR{tzIfPa7TU_P&CqZL96l8>mXC2%4l^2HCW``yc>_4%- zEVn1BG%V8&6&h39s?Zi!$DCd=MMXuR6BnRV&X)L+12`$j3dY9rWIWEkpH1LnUw2$g zA;-R)IlbZRk`h1W1@AZEi+SF2wbl4KP1vJJ2gcVT9a!&Um)~7PS51i#W{K;Wr@g73 zvoANH5VM!Z|8Bfv{*Iq>yDbD-C6Z0$*{4oitCv^NrjYf5x`HMe`pg)6fX;;r7kKyG zcjDaDR17qwg&)3edm+nP(o%j+9!`i$pfKo=CMr)k5_2+$VTOV?EutmtJe=$3L4#aX^z z+=qTTvuE+is| zxV&QH=g(|lDc2b}b}s7R_!I*qX;N6K(3Ud&xz+Z0hs$%Z-dWceF z(J6`Eku%uh>3f*QZ>cCN3?c>P6#CLpY9)N^}1OZ{$K2jP|BbYp6lIQFl9h3gy zE2gU}>>eEP_{}b#J$%OL=`o$|5?6QkQAvl-KL3)>zW9Q~ITeRThs>rk);Bl!v;X7& zWcAWIYqwT8+8FY1_==-kGfOo(HFn9ByKcNa_#Ol z+EoK4I>I-HEnRT{Zmshzpc5dn%n6vo&+{Q!-1!onILEIXmz#Ap-Pz=(X z(-kp-h+Ph{)NZk|vVuy5Y&KysnFJ;34TY)Hao!+V+z)46=XvfteR)=JYN26iF!XWR zb53mK`Lo`wE2{v9?UR^}>Y+7tZLHd@ z>e$9(^=*u86RlM`o8-GGn6XG7oOzEZ8`C?*Uek);_c^oy!{V@rvutykrgSAnW_fv;rKKeXgTY*%%$IFX%N8FX_fC>)i*>a8q+ro+b2=-kM=3k; zqP1o=o0Yb|5Rpe8j)0sOg&G${5f#YSPlI0mNv5Cqnmd}O#x&sO?bf(uU zRbGtcPHRmfArCA9wG!S9fV!r?oA_-J&(G^DiT(#4+?kg-|9d<>u5Tn-9!Y^U7CCHn zfy}dL{xT`{iNo~XIWN?G&S*5?`LpMwbV*tXlkq8E z{nN*6U3M>Q%3! zs)0{CF3Nq)@q;7bzHxRlK8OV{PPeE6N15QyNWk*J`b#W}w*JuXE)hZ-O+r#&>!4Q( z+3*2_Ffn2XPN$}9@BcYh93+T;i}jiR?gN?CiQw3VhBDpd?N6EK%6i34>_7+sNL+^3 z;e_|9L9Oz$ROtA2SYZxJo`E9?6vba9EURbkJ;!e~`pX!~b}y9f--~@d&!;td>|5ZN zx|*u=+U4bCRO`PL<4XB{iLRPrqrg}63ogc6Zycj~tL>;_-?{hB>5~Loy`bLn#%7v) znMHxjDpom*-t*k6#S`{J2)xx!l9;Ws)RZxpIXY zH*RqA=1s=qDLXqmwA*czQe3`#1+6vxexH7?AF!MV;gGA`n6TZOCnRW<3g)YevxTid zB(nLg+x(T6C5FQx4<9~cZ*PyI<0E%)uc-ty zy?HA}u+E2q@|hKN{U4EB+s|=X{<8*Bo$11viHQ)2j9UG57IXY_9oRtTd5q&f!?AQt z^99s`VRy%-3npL^gIZN=Uf*Et@)|42Dlfl!!Qgd|#H3*CS>BoCNGD}wYn9E*7inKw zrgwPC@$*BHR>IQd6}Hyb7`*JU`^_$`tVMzi0x7!@qjP2~!-x_zNf3BHCMGyJv9s>E zGg3fex1r08%Op*NBu$W6F?M!&TjV4G{o&9uTm-N~V6?)VM!aE{;@cOU}+G>H-PWCjQmavmNW@bUlqSLjZgW`L zCZjp$(}3Ho-T{*U=)4{vC_Mk$oC0&hf)Ua*Wp#CpL=?Hsna;-Kvn=9UN(VZHmjfzc zUYk*TpJS`Y*n030zKaL>fxt-aVZk#NtWW_GEY?u z?RgyY*b#}}tGlYS#et`4;L1C&)l8^5N1+ZNRLlIfL56BO&Ijn{?}&IsKg8!I0^6%F4c#$)N1i5uL$msq>2-07)BZ@KEF zkShg*sSt72DzawXOkls5by(m-*wT?HGHxN zG*|nwD!Xwx_Crj`{CzLKg$g$A^Sq4W9NXA&tlGo*fN8V5R@>AR5UG|s*WQYatkY-J z&vg!Im0#-gMVWcefFHr(=U-s=$i?%;c>B-Ug#?o_&RQ9Q(sDj zFBd)A)KAstPESuGJ}dg)e{U++I^CwwgRY+o-uC*D;GCEAh5t~PaZJF<#UTgLTP0jo zva_YTCfIx1xBmTtE^6QN-%Ocj?FS#iEc(dxfrL|~{e+R#gYjfT9FQnvsL*k9d>i40 zIABl;4o4GBTI&$VJP%KVw#Lu;u5cU56?V=<Oa>% zQ?9O>fobO6Yw}U+aPFcj#F=~pYp`pq55q7s|6Jv%i2CTcYTcDGl*nB1)%HrwGjW-- z*^Hy3qp&jP^z@V~SFUhyaKN)?&)hQenLQf7wOM0|y(}G}5_p+nBB`N21C0@>$Hc8rfJ&3?o}9NOVtolCvZ)jdmiOh1;@Tg zF+0|k5(|+rrmB+~uX*8ZKEW0!(q+Y95`$iY3lA=`es2Yn=DdCVHpjDlj`j~|Wi5N` zw?LNWv^P>N|L6w8OEB9=NQR1R^Mt#%@AKm51@GLu!u8wR931SD?afeW9JpW2P$Gu*G`=NFa<<1?m;{28ZA9DF`$`OUPWgW)~8Mx zeA}xKPJ%LbxDCVvzsLqp6|?D#c597pcbQy`*gxr!r3ofU?Xq*Fz-aPJ(>v)g=nYUx z(Q37jRvRA(G8ng{-q6bi986Cbrx_>hF@xD2%Qu#Iu>2je!I;-?ULwFu46UUuTkQ)N zWjIGMTE8ILisEg8uk>tpnsFOoFp5VL6yV z+@VE=5}Q?Mbi+byVnu{hDb`k3Sz20ReB5K&O~JSmz{9es*ujorV+^K*@{|6z0$%!A zbPqrZ!ix8+5Wv$MK$CyQZ!`|Y5>6rfPQ-8agQhf>sB@;1DPY_d2STUaM&~)%EF;r7 zX{$x6l{!+TkU68twe2760zG+_q4S(p%VxJBHlteS@sigf03E4){LFP3%$E7Vl5h^7 zQGKt(Ph^t;?*ITG07*naR6bOq=4?i_j98Ob9gsy%y8EHF?D+XOaL^<-#+bQN?EU9O zI;yVxMF8%+fzH}sL=D~5>kOzKo{F8vCR#0IDmzzRmT``Q$5avkR#{Kg1%BdXfW}0b zp1uW+B&8I2mXl=}>+9?Gz3B`|RnbNRJ^~PIf>^mRXy=4!I}@!y`^*gwvW%NRaeY@E z!#vMz=D2s1y|q;epH1f9^<_cCk^&~|rfzxFxWK0Gli+w8yR)4!hBQh21FjF1%W#sy z!KL88(%2b4Ce3^s0?kD4m_ql34mQosPxX5pZ&m}ET&>tfD`q$p8jH~Yp0&}cO7Kmm2HbI#tG;fIXK%$;ZNQ`OWgKiJy6 z&~?r}d*>xGGvbTL$cV_!%Qa(+iAIimT#wskEvJ0Gl*N7Tmf)I*BDf!_XQ(ZufiM4l zRD7v9{+Wc8J|H|41s^5&W{Ryxw_!~U2Eo7q?E&_m?}a%HU~f|Avz2hpog$g-}j-@EsK=*`AhIYsiPG0q&s&@ zKRuvN?uBFiTE5e1FYlGOheJXz`RY~XWe4yi%F9#P z81rSQ;l7Xep*w*9=;q|{44Pcb$(}Z|Ztm0Iv-9g&*ZC3c(51ubtdOvXQkg^#)^(uc z!N}G_+4hc}jD>i$>_=XAey=Q}ms&+%k2Y?~OkKzeyE3$o*AC&`QU^WO#c8f_?sa<% z9NcX;`iXSxW+aB1k8i)dqo20`!8>zfV*`&KJ;G<7eU8y+jOFEJ+`fGqAAR&uc62+P zp7oAlDjU{ai2HZ!Ii#1)<-~qCa~fh80!GGwok!C;c*Ag>;Q)9_P6!VkJiyJHH?gy` zgA*rCV0n2N+dDgW`s4{*)4)4TYNg2udt$g|GOVwhQ29F zugKUW5k=`Jz3_&B;uj%wU!ecKi9*F^&hPhu%c88sI}Y>SLW9pRgDr_M6Y$Fp_N)b; z1h)6LFkY)MI!Tz~0JHrW)<-dDvi{ga&k}slF81nL@>1l%?OjN9b7qm3YSkT;c>eKUl}+}jwG|)5W&{A z;~D4$=&J#j4B!pp*=Nu2;dlQY>#q|~c@_ZN7_813Gck%9@|1Heb z3RiDjz|)%#Fnch?!HWhzo4k(;ub#sCxs%v>xC66CuvEbqh~?gZT-Ts})Eem%BIVK9 zQb{a@QlzMidCbh2Ga@lKD3jn|+Asey90TvY_W^$V;~(Sl#h38T+i&B-#Y@=T+ry9l;m3IJ;9<<_+`1O0POalt ze(hIq@#1CNx_Jw?Z{Nn1E0=No+I2j9_ym9bSAUIXPoJW74ogc*u%?2w2A}}&ttHf> zN<2RyG}I4WFrGRyFAx&Q;i$+00BLq*tk+yXGVk4V9=q;9B?nZ-+G=qj-oYG&mu6|0m}>8*+2Kwo6KD?nxJIzZj8Vl z1cG|K5|AC6%-dGdd#*jqMSE0H2!Flz7>~!ec<~}0K6r?xZ8}MC@}QQ?qr$Q8SM*E}}@^{s=x&-wS`7+U*`n|(X>rIMs|t+$6iWOUSa={)f) zCp3$25V0u;EroX7|MVq2LxNLEEPM~vdQN6wPo8w^EK6#$4%e(|G z$|&nDdFfHt5T*yvN#wZbQXEybilr% z37+N5qG5(pxK^Bqaz;TWVezB-!Uf&J&8#c{R=A3159bAsdJlD}dj9-5e)`j&g2d3c zv$uyATU#I`2ZfSmLv1RbDe1McvVyg>H9ULvtQ%85Th85BsC}L5mR$v*qJ}Ts`3nMY zu>u*zW2YneL@kXgM-#BQa^KG0Ry@Xpo`5e!mJV%QLZBt>^9OC5JWsF8On(02KhZ8v0;QabItc+G+-3a83 z2+h*iAZ?92F$uQNhklSA`$-fR3c&E*Vdh&LOb&6fK7p1w7*vdf3)-2~N1bt71Saz_ z?xXFIjHid*dGe8b%%YJc19VITM+~<#!nxPJgtg}$)rjy?y@wOq4wLC&ROa%dy%dFJ z0%2OHfdqSYEzYpR#q9}xYwbGf^QZ7PAAX8=Ke~lSQ;&A1!HCB&jfZ`-i(h@?4Seb3 zdEC1F8J=#oXg58Ucn`0=bQ0%h9=EopcrvkQV4?@ILpa`;#{%jzx(v}@!FS(owJOO8 z52=D7bX1-}LAVDdLUH#!2KmkaXRg?lV{C}9G+x5#jSY;6u(kaHwQu2xL6rrp1t@tA zP*iRxHIk7jSHj4-paIGFrLW>inS>!_fO}}&$M4KS8~qoI*}*G8j$ksnLLw>yiH6n? ze)a2b)aOcxc(KIb? zy#6|V{}28E@4WLioOf_dgC~z4VeRA^uD)^=hx;?U{-xLPhyVIt;lfKVfjwh3Yp{Q? z2iG>(IDHy-?%cr-e)t2t*m|D8S*OLxlOk__4tUcK3HbB@&irq{@;RT>7jwf^`W|IW z&Nryr+O*awOolAj{Bglmxkhri19W+QuJGr4CmuCMH@Q;&FRZb&uDnl4Rm%1r_fi?G zW#(-zpX+@;`z>L7lKrU!7D37TLhJKD$hymV{zcxQ;5Dd5&4q36S#PYHW6~>V?}*^o z!!yHH6{@O2UDtT|<;!^b>>2j=_u)Atu4C<;u01U)9y|A4qo&d^Ym+7);cR~DSZlu= zcX_e)pS_1?A3diP77|e;mATyCUvd(Id8tS z8DKhV4Otm^Cig{G{(Fg^x^k?m9F=&Y4_hpfgGu4-ZbJ0QBmJ$!5XV`QeX?Q*x6Jj0 zhR1NI{JhP-2+t_X&HH&AW1fjd$;|tV6IUjQq6@u~&!MP%G4dcm3?O;9%^Qhxe3{FG zKAQH?rEe_rvF;oD607#rRr2mq>F#TT&T}G4UR}j=d7WjweK@(ODBY*DDPc@9p3d=I z0;1fFypR$fMZkEZV_jhW$QMoWs_)YYID#OlG-e0S+ts&rFb`9*Vvb{!=+G5iPr^AK zj{bG5)pR!CG$C%(wFE57xzMmiG8qZUH-Q$Q8sx(|BJF2fJ8J4oMTR;ue_VZ$bWJKB z?>|LY>f6KS;eOhIf?sJ`-9sLk9BrH>AI_gSvc5Rz{p%_JoMW@z_GIf%v20!{7hdw< zOb4WlcgfW!%QSASzQ5>NgpH26lq!k=h74sx_4du}lHaC#WjLJ9TR7I@E9zO)KE3ib zmi#L4mHwKKWADl54G&8*Q)|c_<(P_N@{`+DSWN{?(u*wev(k9DITBl{7kD3pFmJ@ikVM(mR49^IO zh8SQAFo*C}XV0F+jW2x()7cbc3=A1K=g>^2*x1;>cs$0Vq&y`8YGtfP__^vUeJ z{RamJm`ox`dlJ;K4Hfm>aJSsKGn4uI@fWRQ8>Ssa~8gs#NEiQ3}J zmzMCa{^d8ZynKN1#tK&L4IJEij!$mBz?xl$Z9Q(hat`16*6Ub0yM@yy8{GKn4UC^3 z0&WFg`|9gBxjx16QjH7aQ+RaiV?5bwg8_jEh8RrZeZ}ThKEIiqxPj&jEebd!)b~0H84sduT~e zZ4GKaqRt>7t}B%H^?NSd=CG_PfRx|KPA2B}v#&iMu5aDM^-!#<{M}qpbF4>%MSw;YzTA?fGPDmn+pKirh#(~D@)6$PgEF<$9VDL1)gti1`J|U-BQ%Y3Br&X z>%_~!=L{{p6XhuD-bb2`UJW@Nm9Agj1%qEn+b($1b(w$eBozX{a=eE*cs1K1+(8MZ zhxl4-jI5vVj_EFUQSyv2u+|3E+j+Fk1-VZYg1L9b>v+e-x=K?2+A3+-SH>a?R?f4W z&U*F5$~{g}mmwukue^*!xF>xcRx%b{$HlaBJfY|PA%570v-HVm{L=-FY~E$~OJ*6A z|3TJuSm2_7Fp%Qn_P67xcIo%h0NJO&4!v8B*HJn^-+a)c*IjD$)tT`_X2A*lSKmAu zqKVd;mt{!2WhH)Pjv0ILx3s%+2))2lW!rM6g6;!Iye1-}VR%R9RFtK8AG;;^)3~l77X?}3;Ox(qU$(%bqC|-pL?cBpP^!r{4ZMbh1)tL19c8}+Mbgh z{nO_ts*#M*JdW)%^Bz$avi4~|vhq6mM^E{Ea#Yu4DX+EdLwqFNQ=Rze-W+T0`~vc> zzZ?MN)Ia9cccQVp7WGAAMlLTeqitIp9v=1{e@<=f*E0ZMI-TO)y?YY>oiLdk0sx*o zS;hYTeh6cy`lWcRTas-H_8tDmjHG{L%^#$>^3S=OL7%FO0X&D0Bt^z_D>~A2GC|{7 zK=dXsqSJx%E+t>%2fN#9{CN5lh8O9jAe(@|m{n6j!*=L^v5|c-!hMGVI zm57qIIWx@}Y~PW!qsFP%uo(?Y@|y@S55SSZtTMQ7rnrw?OxzS_Xc<(AM;0eD;S0H3nX^4LpzGhyc=^7ogzffl9Khf z2UGNXO_QACd>x(_gz~sFM}D6eznB@bc&f;LQ({o6lZ9Qfwn1J@IWgv4+@s+GN;Mc_ zRP3>`WUxFQ;pvkt)Jrp*-8h5E{vI};ZlZM#2ZvKQ2e5N^`SNA_#y|UYG_x5VJ$#7u zjnmjTbqb@pLS0$7#$olu3A}XS0{-F0KgM_e{JS{0b`ovV$e8%dGq=V5{yx|T1C-J; zM%HAWw;yKY&uP>JvC;Zqz6Qoh}3ZA29>V2Y>^0MM7Q$8ORCgpU< zr?otjzKqO6~aHH zTte`1Y?=nopKZc`uzK<&mX?-q{``4ZGI+MR3D+@>D1Zv(SYx2ZSEWOKJcSSgxQNVD zqqNFb^|JQSa9Sn^NmU14Bz~%qqKQ`Be|fSB(s5+*+?3Cb88l!#7&6x6_mV+9&GfjI z72wG&F0_`4pNy^*LCbq{!E`-&By6D>d7J_)gnVwX>{ppOKA-2!zyLW z%@Wh}DU|vB(%ZYNcc`yJ?JX76^jGG0q1b#8Wwx)4Kw`b+SOjj7k7dFTan{NDiT4V8 zcoYrh+A@I4kIQ>Xx)SvtTR-aR8d9|Wy#L)k=oOESX4rF)!9RUwk*hrki84CH(dSK0_v>SAa~>whq5(HQlPKSGdTr7oc88&Iu&as{uQkJ1w?e0k{RYc&PrLd zCXZUL<=iSiFEVV#?@$~{oq5!^*7A#azK5yoh{7UoLr+_dedU-j=L@Ogo;LLKwMXxp zud4sId6_U=_HnxUMKEBu-o^JwY2Amt{*l)r8-({B+uPe{+je1{L%%8S@ZMuOo#DZQ zM|k#Z697fyr9M+t6_oxe&kYj{Lt^%tImk3GalXJM?9+=5&Voe^QOBJHL^wP=#I0Mm zk`>4#XP75XpMn6)rn4_Bn{&G5$wCL4+*8(3YZ?Hmx(=}bgW*KXSw}Q%2(a~m@kJnI z2=K+c1m_uJ z%K#bFV4PoD$6If_jLVmnaPsUjZa;pEpN~GpcxeeQzkCIkUOtZx|Nb6+@HZcUoduDN z=ATUBA$k@@5Y?p}fzpv+Xd7Y!e= z^i0|6qk9>HZV~WY1mMcDhnRQou?PJ};gJ5!dHF0vZ$N(xu-cVIQ#SH5mR+g8ry3%5MXAeuu zz{ct+JllMRlWS`@d*%YRcXr@r4IVvsfdBZHe~BwsuHaXH{nv5jm8;m<-bQDXV)a88LXnkcAa>=ND+ zNTqWs_uLQf#K+Yr@thfOPOGW7jf#l z@@6ongmgJ*&)b6JFB5inQ+CEsuF$HJ&NnYDQeMmO$3PapDb(Y+W;_FbFK3 zhypxWfOCi{A?pc7l3Y-?FVcKNMv~xW1HwG#9ia7l@6%e!lo2(=$2hTcbXH|48Q$wr zT8eb&Gd}3w-00@L7vq#}3 zWWjlvhk4P*QF{7tUtQO|8LNlN&F}T~{ir&Jp6&90^2sh9FY7lM9xd_k5O$-u95TFx z$hss=Yxv!<2mHKJ81ag{6Xg*NjEA;8Z$~kg@;aRNsKzHyjmB86YgCqCO)ws^{O;R; zM+iZAXC^pFPU>76yl5&5fH0d*;ho2LJOYtH>m8Uw8BN=wvL=R7(hV8!J=#_hOi@s} zopWH{qG?+&`yjI=H(>}9BG|>6NLMfOHyJSl?YwtjHXy$YkO$KQVMA^NL*tOJRY>Cf z3@{;n8*b2`1=WOl$zo|#BLow0$1*N< z=73IsIHUS`cCQZ365fTvyt7G(?>{S*U1WL|d#$EW7&E9zaO zU-h+PPG6S!SjM2$Q?I4YHN=yC(d#G~n)9iWcO9qRAz8R^4x|Tr+tT|^&u@KmI>$fx zdwF}>w#D}L_TXGR>Iz54370Ni#_5eS!NCN9&oxa0=USXSdlpU8;NHD^m`0IbfGQ&A49}MpVYpN4l77j-H5o8F#FoA`yM_kpJ&szRG z?$2qb?XlJl%I7c2AM#7vRv8e8EMjDP!!-?XTz z8e>bidTtfJ^ILBLqb-ox$E7Rhv3lY$zWn;@*xY=9S6;Tbdiv}5{PsgUxOaf$OGk+D zQ51zhjZ8|0$XWwmCOs;r>MRg5%xUMm0~29tDomK*Mm5}Mj20F(NP=Gr4H&^tGBCz{ zN=e^1^nw3H47877cfRLfOyv>#0Pvay)HGNDW6c;GNO=Z`F({%A~Z+<&r-X=`xd!WrECd<%y&#?L-|f$cx}32wZ#fp5HX9Z&AG z`1q3*JbCmS_n*9nU;f%vT)K1ypM3fpFZOD{S}-h#Eh-0uT~tAw(m(RSGt+|+JtU#P z2;H^|o%@d2&CxF#J4IGR!I;A2;*lzAEDVc5k3&EMWk?DptqsYfPM$o8GaDOtzV!mT z?H=GADg-Z6vKFJd0&2p_@(9@7N5vx;5^r5VO)mmGIr5qik|>#Sb9NiofrBXF9F0*P zfJR`?V}uIpYa4j+ zVh43qVfplFJpTL+PMkS~m#{FD+AQHDECY zDo+tFZ9~!E84^G-JgNUC2f+?q7Nk0Kc!Lp>3gxCz!-yW%9P1OF${a!-NP9?kLV*%^ zK!N}OAOJ~3K~$8bl|{IFk4pOQ0aVI2IPuo#X)5w50)BAplTIb2YM4hN(-4ks1W#p> z6<`7%W(^^d$_R?F0F{_!R)B<98j>e&*wm^FB*Sfbp0hMuDk~`dZs4u*?2uThG6v*~ z97_Q-!|PE7hVBtxH5_sL&6s2`j4}e@h|a?Y5AfQxYeDuJgOevuVtZ!?v!;o346@Gy zhT*ISQ3duu>pi@8P(Mv=k*Cb7NBOPE_=_kPWDHmWv9s4q!URhO4Lj)CFcK&}@3YN8 zzm6FvzT{o?r#xT=z;@8w=exr~?Tos4BYie!xO*<q z!2nS7sZSZ}1H(h*&-JyyDMu-sK#@2IiEj*DT}2-qy5bn`YShWn(|@R~rKnH+(sj(D z2Ziz1c&FtxWW4CRuYHI{G!g?*rW^$bE^}Nx{jzyE)c?NEc^k|7YN#p`QKd4H5>?K} z5=Z7rvKQH-8&n2KNz}NZR_cmdfpPAI@7@o4FGWLyyr6QloX0xvV$)<(5`t++c#xOy z4_2`T2__+fab06+X^ew|LrkaBPM<+x)5;^o0M;Q^uk~^y%4As-IY^)#fncn`?*1X( zc<0yf$N$}bi_0q`EK@MusrKUJd95&bZ_0Q{-<3aV-&KBg@Lk61;uyq%kc4dMri`!3 z9>K*3MndOOyklczW72c*nxf=^#GU~kyyXKn4C~5sD>{S$lhuVFeNnl2wN1VI!cbDsj%~CgL2Di9_E9c~59O(kS-x z>{2fGU5n&&ZeUAW(u=P}M$EFlMe=^pZ~1b_LvfUHvg~)+NAErAdW2v4rLW@T$u;cn z?+e~y?CDO)q@*oq1V#099q- z8WlI`uh!8wxffYW7lNkZ-J#cAg^&%3p0QwK0Yh*C&cd@^ z1W0#$Vyzi{<^XuH(DTKAoOzJ{HuPTSIReO7jI6~HGhSIciI-Q`faPU8-r52?7ZT41 zc*|qPMPo0A9Y`-4N;-mAVSDQF-n}XAPj+zX>=K@Cw%B^{49nyDIN0CEVaxDUg^wRj zaC`STessIWa&;D)+b?0_YTUfXc(S*JQyUfsdyKtETf$sqOt*)25-h}jTsY; z9p>|b8BTy!$yA4q@#kx!&ud7%i!7{UlxoRURfV`~V$@`2Z0+n|Yik=# z+rmnQGzWqMp=E|~9(1^mHME$Z1;fG<$82f}$3_rm*pk}~Mf&J&(*xq;e>5u*auJKq~ zJ&6~O?&Iv*I;PwExOe+A>~HVl%DL0{=GVW9v!_nt_QxOLJAd{aJbm;4zw^(33uyN7 z(a+w)cmMpmU~cio^&0>Iw{PFZ+UX6DD6Y+Pic_nrIK8@ztw#@m=>e#n1{g<><4Y6` z$3zTrYABmm0>hCo5}rcBS{UTSh)ouTjR-N9jDn$%KwBi4p$Y{{QUF7Q82X7lj3+Rh z44HyjhG8|X%CSXxCqd0P#9bh4NI-x9P2q5{%0AEo@%z|5?Tzv0O17OC=rz;{bRAi1$q69UAZ9oe#GqDN`S)9tOExUa!h_BuzAwgy#R=KCW*1l#+Y?(D#uQK^v2T*2LpJAlD zbPh8@d`#u(`4RD4HW*_9zETd;Br-u?z^^g7HP2)biPy$y?kk@DQ%fWNmjy?=A zs0c<_(8V@=_$Jm9pH3f!aA-$CER>^?Ceb*Wzn?%jqU#<5^{B?i#s-)f&!0b!n~{A+ z;*w@V7)#&Jy@kv5*{2)>$sr*(LX~)N5uVG-)_K}z2n^3DuI=`-U++kUWI`GTkL2m9 zm_jiknZ9=Fdd!o*gexY0V+k8U1QrjMT!~W}sZ2(cyD`TzVY@V(nuu^trxzVWc58a2Wu zxf3r-W{0Y(5H>}{NbWcoYw8*v91Pf!%+U~FtP%d~pui$v8;KPPz}gDN8W7nSOVs^aNvyR&E^yG9bc{@;UPBBf6E>?W3r5%^BMKYzMsO7hK3`Qf$Rr=+j;(BG z!!Gc%O0bYY1p-=!%34fk9?pBrm~m(b^@R&Kx3rAW$2Y-Gp2Bk*HfxJywljpr(3JUrIc*YGd?i+_Qyf8!h2 z+1bIL{^_4$RM+@7fB1*EcI_ITJb8?dKmHh(FJH#_^B2&zEk5|*1HAX%dpJBij69>H zZ~1xXdmKliXb1~{-lxQ6J#zA>>nPqnr~hkR$0(!k{&8eUUT+_d>wGVs&+$lkuhg6J zx{M+Gzf+QReBc4LJHl%r6q=rMFKdugmJMfWm4TIdl^8+U6mC?Hk{~((-Bi?f2iqvnS8cwhp`d6FhpdiSd~lX5}1a{sf*pB|N*}FdDyv zdUPI#(+YbBjkptMfHkymuzy2FDk4$l^$=N@flv-ijCizxGBn8QW}L~nKINdi-$&m* z;JIi_`tBch!Yjx)JeJ=z(kZM?fz6%20v~L`@C+VuSoM5dR2BeqyP8RaL<=V|RBKvt|~KK1#WseR!`n7}pive*GFQHw16SXjzUd zN+Z1YAWku@oOdxE9ZCB%DNAxwS0NGH^sU_UUW~p`|skzdt2DQeGgYBBaBBD^z&N)dA#Zz=;>zgfD~nn z_V)1c<0qK4Exs~d1}!Z^jfZtTf(K#RHZhu61nZorq(dp zUcw!acuWuozI8BaFBi}oG^hYBoOhl0kZd4^*uXoqTkt;4EA}j2J0WPcF`1_cg|HGJ z#H`pX?KA4P%8%jM&!E<(yPZRX7y-tFS_vh4En z5T;m!Nfu#=4pt3!78-Z*-(tPlyFi+Yy>-)kUwUy=X>m%ONy-c5o|FBK1M{yB8i9vEB`cRwt#`(B?l5YUI@5XkM@G~%SgRqBBftlrP4-Oll-Qu7Mp)ra- z(puMv>~&5Q^vaKpg6A={V@~mG0U(Q&P82*MLk|=GZe}xVZ*Rj`3tL%m%Q43K9G&$4 zBEv{ND?ViI&fCtZ7E1L3!N|B&TPz!c!>t{B{l&^casn8sS?VvG%GGTK1foX=QW!P+YIg96`I8LdA=ToMgdMkJkX zRB@u8j6-k$kU1kX3cMuTG*KT@^sGKr!$j&(7`a2#w?tC%yCmco%=9Q zpMiMMbsYPk{-ncQ5ru(BLBSSJv!g)tqHE|qZE&8@AzyTNmcD!>8x)cyk>~tDT&SKfRRPoM4MZ{J(Nhqv$GSAO{&Y+QI9KYjNuUTiPJ#pAv?FBt~#eFnw^ ztde(*Ufu_AVOj@F4q(SCuq^}A8H`Wpa>MTX}-LuXtN z2;M~dhlhCd=poMgG3rwlrn6nl4)$S3VN6x*Q9M?dj%XxQ ztrk1Y3>VIw$IALy>~B4W!3<UD5+2D|rnaQe(8j92brd}>i|N-b!x)3vY=+5X5{yTH#4TmCp7GqZcxV_u`Q#2(RyR)M+SH@%T zq`~x)$MA=Ym#2i+cmiS1>Rga3v z7}U}r8mA-QP(>z?5*68GG!en;bq#tJVH^09K>)?p6O1RoGknv6+dymU0Y7WuTNjQ{ zITwVqYXhK_WOG$rgP6myU@%|_r~zLQcx>Sfz?MSHMoYn4S(Q$PC^)PJ+v&ERm^y%p&gQ>F+ri zGLKRO%6{u_Nlu9LEif9z;DGtOLGPR@v4no_qv3I`p)BWphRg^;RTCE36pu@C7i*OY zp&HUll}?RFeOI56SQTlGAd%5DO)zE^ao2_viJ@ze;@9`y;O1yn?+^xAWSCx5XW#tl zmL=FBdw$M1LkPSvXzl<+UWk*7DsHM`&|G_?HUi3Un^SrSPcYf|fx2tm$ehV_thnw9 z&y|kiNAsIN%$%~yYIqXTxt6J!5(_xemO5uy!lt=P8?_`#bfyMm344Nf;|dW@5X15e z2h9v*4aO%=V7zhy%r31160f7o02~a2LT8EXoLOE3|AwQGt#e@EM`6u}ay49z*DxyZ zx*WqXcabm+fEKzbAI8h7XD4$!;vSltB`6Y}6)?DLrt6%MO>oEAmaiOLx5fq>#tbiG zuW_qYxApANvJV@k&Id0|Yfy_PDjSc69olv(b1Rv!HEd8mZ*qCny{PjiO&mgTv;@bw z`V138S8N+3>8I4<2{3HJ2Ci0}FRfU9`q}ifkIK(FcZJ(B5RK{_r&i+CvAQ_NMZ1u; zL}Pzq`#m@%=_D*gFn3m7hi*!IF-$*3Wh|CQBjM9Y-_3(Ub>8aH>GS$KZ4{j@Cg8O;Y%;;5 z4FJ+rla)`@nGca;#PyOkd;T$WEw7_6!hBs6r&Sepqr`~fy)4RMm(b&Y$VG;%*jV|O z3;&$A%^A+X-S-PaT3+{X$LF7)&1RU*X1#q1Op3tO5~ij?vwQ{HbOYc1n?u~Vv4nfu z=W*-(zeBtK1Gvc!2nKCE!l7LSZQQ`kt+ROV$BfnSRqW9>arW#f{P3eqJlHwJ{FY%QrStJ2LuE-oE)3cP;{8&3}Nk|6_-leElmf9&!GE z=lWtw$MH)Ii^?L*r7r{cmk}f_Xv%`M#{kF>Z1q2d`N9G8{o6O%m<>#yv(^6!9XD$X z{`f|S(_*bnxS0t4S6Zld<-*E`8dQAz_Fue)|K)#wyLa&`&>b=cD+$0JUW_l+SQr;V zP8e$>*t`Jfp0cOYDcZK}9$iGRBei7ER5kwM&Rx8J&!B2m`BWuZg&sG(2F!+ z_7b;_Am@i+yZ{Zw6)en{Wexj~o!J60?wuh4oDA`X6W4y{VuB(9!I}zCAwdLVY~)cv zsSG?)V_1dwi&Cc(${G)R z16L%mL>acKl0uK*`Jf6F)dV(#mH_7%#!7rY6U}M}H35~iAX5PxGVYlwc!oIV0T0x5 zh1yi8jD@uUe6rHj0Tv;1XKm|XOtuEKAyi~wDgw6-o?BQBhMcOZgYdMBnVW>R8;R>> z4A?W;*26m+(64v!#DI}75GDXCPn>x|*sxIp27Fk5mMkh5)HWyyX zjc(34_?d@o7(8iUT7W&FG8XJww4!|0qZ;+fD8!(gws`pXId&%=ErQY55doyxV&`{Q z=Zqq`+X9l(C2!Bk_^uHq^tWl7z{jTP*p?>L2_D5MtFJ0fR+SlD!?{9QtDQbZX$ctsOXvF zj5Cb*FWuL+ihp#GlOT?S&PCCj=QqnKDy+fFE&RS>EyiHL&CoP6oH=(6Z~pFYVYhMM zrU58~7y`nPaW#yXNOMu{IUM(vrbOKBnPZrv=sD_BUH}sOt|ynsgc#l=$Hl{MDVC+4 z#0tmEE4a2nFp?tbM7oJj0oaAjP9KiH%kiWzBQ&`X1h4+ zjL=##jvo_O8DXVaYAmQW5Q~&Kt~3sPrxHHY%HP-kiSg9vrRJr#5}wOEmv1p@#dRv;`Hg$IN0CE`Lk#7^z<40(mP+n ziRBf%bp9p$=5PEauD$jenl_w5`S8OJdwHJfOZk{Zjx;w;IgUshB-EJ?Ss9s3qxmV- zX~)7#Fsov;{9a!>k1`ha-9NGo**nK=vktnpGsHvB=0JvHm4-1Cnyhp0%V)Lxoc}UF z9Gr-lFqup;Od;b#T}-BZ$^U9SC7#VqAOz8mB)lEK@r0X^&vCsIBBs!|(s@0xO&Nb! zgb_RM7S~bsyz<#{PUr7b3}9(#3CkB=!uHl9OeecR1o$-RxhU|sSIGc#L&cymvZvvl z#f&Zi%jb>)T|@26uc%C@iewBNDqwU9OQ&DM;|F`#=N0hs8Ej8SSn|uTRX9%3lE)z| zs`Hmnop}pSKi|dr#%0j*1m~~5iBEq130@qo;k9cQ@#!Z|vHA24@IS}ZrCHrev*SF28h(?g{W`sbk-lI-5&sw zki~!-z_ko-7^aq;Y6PaLD?~jAnx>H~ZNZqC)(iw>7)(d2c*rekL+}*n6bz(ExW)E? z0jmI(0TXtPy!SD_rveieArN;JnS%!cLA=+3;0Zt_#kmYdO9N|zcSSJv1mH^unZN^> z4n6kF@NNb$BsNn3LJDdj89PH9#sIF2wTM};31B#Qv?3H7m>A5#2+EGZV?;VfuRi#& zvoEwa91L3kWV#7B(O8P!2Y@({W5pSZR?k-Twhc$gIT-6TxH*EcnDcp}Ks_dXGBE?5 z@gByDXCkL@2*x}YXeC+Y**mlxvYZkC=h_Hty~Nq{8uy3@@C2*$0HVO9nDyKvgF~H| zh2(>`F1%vm)DQKnP|t*Da0B8ozHt14;yh>g=nbhLhm@uq6qp#}lXGau9l)E^rO;R1 z5miMo(2ivugnA4E#sifn5SuLfWhgi$a|%FYW*99w7zqvG*@MLE*)UMGjJhRQPaz%{ z)MFwjZnIenUwO3T;mF{S2v2K+W?aK->@jkopK;lB4Rg8jtylDN=olJgZWc$XSv7E= z!yh!pv5^>414RoaSX{`}&m7qpqYx3Xbw!95kfp4jEdtEklOGjzPOe zLC$~IV_O4RZ*S{t3ikxptA#-@Tz2mbFv)Wa3{ljKucE4 z${;vY1dKEUm4y2t(z)a#y^M@0OIbt#Cxko(5<1O<)R3A~&^wRH*l@fwAOm>MXuS&? z-XhBx9Ksk$oPJVA51%Jt%=oEvNRtnBC=}P2uJADTVuS}$`X+H#Lv#QDAOJ~3K~!6X z((~Zp0N?xG_weBJdjNnBKKKCp`}_F*fBGJ7-~2h6W`^zU9lZbUPf=GD9z1-6r;i>3 zF3IQ%VMse&BO#rc)ZGnta542W~Zb2qUQ z@;R$xhdDkpe?sQghpmzR_GsWkX@%L&csBm(5bPjv&O#3*sH0ut&ht%KNye9h$d zk+so#7!Icf!-f)KeR+$AY*<6GSGk1|{cbcGW7f36F-GJ^KKM!AP~XsZo8l9L|E5ktcu*)=r(ndbNg~&CjuauoDtyOdSrlCs;w62%;?D0KD2+3vL-S z3Sxouh;=jt1qRqQP4M6Y5>v^JU#2$zf`K;xp^CzhBU53hK`AQL0NCj65M(1E6GT>z z>_{CX#pd1;6~*!i!PCt{jcv)pia@ZV8phP&M74mIwhh*>kYPP2)vk5PNJa#Z1qIw2 zfU6ZllL0^hcn3%z*$vr&xY7rLNscpvtb`FTLbFIe4*({{jzUJ?7-K3BN`C1PETjNZ zLM+HGbjau$K%c(BCR4URuw-GX0RD-@s2D>baNLnpaLzksTZe$LOs&%rK|DaKQkh^1^fhY!KwX_bV(cxpv-d8f-(a&!a86M z=5;17ilC?;TtTx87&fx~C5CZ5UhZOZW!+JV377XLjztEDm2$Z;t2Z-nM8?URMaI*$ z4kuSnVrglFgWVnYNh_f-L}`cgjg2^Q2l}^do9fnM=sH<^GGqb_$1Wth(xB)W@5~Ji zrGl3Otsw(uNrUpcu2FqH_tYwZbjN^F#I-DwyyV7_ScdOWJ&iXacyTTE};M+9*}o;Yk%=%0e5Zt6U3x#!f)woSu+l2FL&s z2FHkJ8G8=NRit(5nWrLguHPJ`W^5P9$%lMBBtvS^dJ1x8sj5(cVZ8?#16SK{!fo>z z%+se>k<$?jz>~&(4>VM{=c6ZFrjW|NF#vXq6;{Ki#+mM}Km8oXAL*o@XAh@q<cE z8V-`#N#6n%iV_FRKqLH1kB7HN4kme4?5L;3bsox7IZQgHS^3Hvbv-i}6Yt<&{}MbJ z%=`ofHLSI0S|2fA+&q-+?NV=!Ye0{X$2pnCJxRFKE&$2nvAA-cR`L;@Prd7sgI8k( zV4Z=NmzHsQRHK4{)o=qDy@0U6=w!2Abzp)x22e0YPrXCyTiB`s$O5Bf*b}SpL~y&i zsM-eB5GHL49*@D}G2FC4ySERtZJ1jl6OudCA#oIDQ<%0zO~OlkIDG>~^bjT(xXc*G z(E=5)uH#_Lo)GW?4*Q){da$g_q@Y+G!C?o z@Jt%EpwfDQj31Lz5gC2(?iH!FfN=wjceDhqir01{_~G9oz3vd)vGvv z_AGw*gCC;xt;F>UJGx0C2vC3t$EG>ak5qaC$b;_d?i|2iBRl9!0K`Zk;6TR+SvK|@ z#1J6yHUnK)`5A(vFbi$;;V23-MTRLT9*}Z^A;e}i`S^d0!^e=L8ES;{2`CXHh9n~- zSj&KRD1+dkZO)JWAWH4teAbebjk|*(M)WX@*Y=9vY1{ zfz0Hsqh#>V!&)3cJV6W@abB5345UG(SZzAtkr22pe9kr|G7!XKsFiXR7&$Oj)>iS- z`3sos?_u+2AAy{BJj>B#U0XmGAL!?hfGHU?JHQzVjxcwrAx90Fx#4CJ?=64Jy(vpY zv9HejnWK2SUZv49Hz>yT=vs{J6nbYg$=~lYnyDUK8o)XeRPL1qR;5LLq*ooZYi-mg>n3?Wjm9BfXD_akSq6)bMB+BnDLw#6S}^ivt|Xu_bl>ABVD@O)1&nOu zB`=N>9QtP%Xq}!)WU#TY>+7gC*5M8gvAcVK^*7(bsp~he|9lJEKlvH>`E#_sfvXHQ z-ug05UcZj%i)|d-`5ZVr#B_HX&EX;JXoQVdUjehj!Dn}X=@jG_DcZS|FQqMzsG!}fAmkAutZzm<3lk=jqHn>Y8yx_NC^m>BWyIkDL@!~@X$n&r)OC&7 z!2}_}SvujCYgu#cEU<>&3r*<93OV|llRpK9?c*!Qy*j$1_o&Fq@hmIJKoCG%FgU83 z(GLjv?)8D7^0_|7$vuOf8!U$ug}%>6fxI$PY2T&02I@ft5d8EIoA+*G`{`3KDzr@t zV}Sd2J^-3Y+=(@Cn7r7;{k@;y;m>b@TLU;?iYc}qe1Kb39R%ZS0&82SKwhY;FhZ)| zf4ctvc~!&nRFyxV%P?|%Wd_G{TGLFL3#XSMi;H{~ffA$Jf60 zRs8-R{2s1cc?Eax-i7}*;l+zB{MK)O3)fzI6(4^5b3EJJ3`bsL{o4No4m{6r&2Vc=iH-%lBFPviZx}7Di> zXZYS9-!MEH7G-WkC8AEY;qrL=kWsBPWa?FVXfDEoIgL@2qt6>-@RhH81;6)uzl+Vy zP5jN@d>?=GM}LI7ckklc-~KkXwzp8(3Tta?_|~_+h4uAyeD}NG#oKRx1#4^TVD>n3 z<_!Mbzx&@}a-Q5Y%-M&d!26J)Z^&@lnFqyuEz2nX=AOp6cWG{T*Q@W|QCDAkhKwXb z`a?c9N;>2l1;;h`M@6n@O=Y!qmHP76xW48cD{{U!q=zU@n(Jfa@(OgiMrZ)CfgAXB zI6CTM%yA1zP$)4|>$=8lHjA5hnhb~Kbd5EXachpo(2mp-eULok^zsU7&uC^dv~Z|x zg;i7-JI2g0$Y|n44&J5Uh~;2N48AJX#Ly`C#YqC7AV(OcF{xvdm^>Ug_Ecfxv(u-=5&aZ|omDh$DBQQ3q8YlM~yts1*kGFQQ zUsY%b3G8GigyP5<6G zxVnA@%P(KR)^r;aGsF2aCsChT!Kup^V4D`7{OHHnynP4fPOQR@Ykd5{$Jl=VBa93} z$N?GSAQFSuv$a-^2^+ym$r*q^sEx(AB3yjwJevImOIur5vIbMD4h$p?48orn7=f^2 z2+LFf5@X(roHRt3G&8UX`dq~9KnKI65P+1zx$0Qz@{uvA1Y-h-`caVmCWSQ5M0tS0 zX;p+V5M3p1;v$9CL1Y(E?}&3tPF|6rcq&L8SvbaD&#*ED+)4y^AI-tRxXT!!FW9St#ip z`5N^G_F(UVJCQo;zvxLC{qXJ{Ou2EGs)Yah#qL<)k zM<~5ISgN=$gh+`0h%$$8N_q8dewLW0(uW+gZYD=jnk#%|VM73WkpZ$Vwo}owImVj{ z@gaj|34;3+4{I$(qfyAd%-lIv)7KwBM2|2f5{;SN6g+7G@!9e%VKVcuh5_FQQwAzH zxMl+H0u5U<027QeAjj~&m6#nq8U%7W&RsJD++M}3H8^aRv9z>=Q9S|(Xqy%X2M4Gs zi}7d#TL+_xYaN{C+EQ?=#%_-kJ-As=`EoBS?bt zAa#opr54$4g+dOyhj736#h=2@e(|$yIqYbMWjh=WyDhsVQ=-(O8Waam1VIcW#wyf2 zSKaXpduOH}GFR?A=bj6o!x7#H)V=qdy>sWVa;^Vbxd!(44^rN`IS;7$IbQJF>?Mo; zlEdGXUwu$(^Qz`im-4Ni2(`7i;2FEL*P=!1&}BIRKjpmspJ>_hfU zGm^k}5;Ce-kl4ZTnc%VvYdS3#^CImD`rW!+HKP)x~eOs}&z00+0H&|Uc z#@g}ohUugQ=c#L3#2Cw*VyyawGLVcqF;7pNR}%tG)CQf_A_!GT`5;3q z!XgM$QjVeohO`Ki5WN(%(zwc+j6+m;(lu0pl~u0_cW;*vJO|S;rdo^OU&tN}UKD^Y06&Qb%@|Qa zH@g`>Bsx>$j($ELN?cy0fwHQqg}EL}gQa?2>h~o;Ty!9{$e5aOPoB#;P|Z>EaJWCt zDG2Yzs^coJd3-gCxpYqaLW5U{0ZYuK^IC?(A7#Y+uxlQt&VOEFt@8VCed}BN^MC%& zIC=5}ue|b0rqhOReB-OU`Q{cs{NWF&s)|4S!#||1D;|IR9OuuU=gO7$`NlWC!Ra%n z+1}n^Y1rq_{_GeJ9z00t7YZ853Yqe)!{hoe&|cEbB7<#5zlU-A{JhVn9w=#1J!9wF zDCuHR-p_ImEK+#puSJIIMPon8$XT{?l#%~~KAZ3V2P4zjdk>E<9B*YUU;4tA_{ZPcdXd-w12r7wMnuYdjPoIH7gS6_XN7himl zjg1Yy_O-8Z?AS40e)(m-|NZZ?y}g}sL<2IXoM#k5IJHFl(n=B-3l6R8k zpw2lLJ>morpJboOtsN$%*}Yb-8H9`@f+6L^jB_L@U4X$-1+c+mLM&nwG?K|Hc0Piy z&F%G%#y5|?XOSMgu-#CA$hcTopjX$35hl)Y7tbYWxnCQm0*cd9uDh5+-g`WbN~^*H z!3({rVLE!m>aa)uGtaRy7}8XhySHyMJ+;P(fBYLf`{l22=f*W|{`7||ojb{$-A7!% zd6V-OFLL7aN!s1>*b~cK_|lh{j1GADXaA1-W{mY7T(p10D$nt7))QO5dbpHhTJ}41#@#lh zflyEE;56O$G@ASp{yi6EUYStF2DFMQz(^ao44{p+{*pZ~W%;mLES z`SKS(%d?++9#i$XdgUsw{Qb}Py>EXT*R%0*<_G`oCI0xo{U6vH?=hVo zME@`Gc|#TxnJdYDFvjZ?a5WwqW4odiS&{7KyfYdoc+)i~z@H+P?iIN&hCmm>;qs?o3L3#iOggF?vzLRwdkm0A8~r2|O|paX^Z5#6t7Uo!njQi|d|KJDX> z-YG;s-?(Mt`!bjbDQYjhBJwZocfrDp7RVtfSEMygC-+&YOngCJ zc^HdNp=a7m_i4ECQ689!o?U3@D3nGG!=A@0lYuUcZ5=& z&d_$^*{R>wm}r2QYqQMTsRzuN&Y9i!>H!sHq^FrqSy~#hxw%E%i-+_OJ*YM|);W9j zG=sq)d*Plqah!`6FR;A4%+AhEF^F#X<$eMEKl*pJsnCi9s7=KBinpS!lpfXNQERYO zMV-9p#TOpJ5Pl&o$T~`HtrbAsR?#l&TN}B6h>F$(x z2pSX@B-oT9FT9t6FE92`K5EZpspn&VY$Y+jGY(tXw7*H9gd|5J;ysE>EaKCP-hbFA z5Q_OZtlQ7?*bf?4UKA0;roCuEDrlE1JI->quGlwK#6*%ckB*LwjpQe4L=>d*R06NR z_a5u7zQV_z{Wzn&9j;%!!TBei)|#59D_|A- zWh;xODU60fk4uN;aUB(etB$X9k`76-uAk=J#+mhmjvh_);>>&=JwQk?HVHPX*Q^>C zefmzWAM`nOBj~>Jsv!I zl%luL1<7Qwa95Z6ztoYAurv!HC!YNny{dufCu}sHfT-(wg3=&K0I7jI}DylT0p2bD~B?gCV9y`I~7oKE1X=uX%%d4w= z_Vb@-cYB+!ef=ApJNFp(?%(JBgNLlFta9?xpXTdd|0>5%5V-Rq001BWNklOSc)E+~Ct+_&mnL{abe#4Et0|h6mSg z5he`>@7=&ucL@hmTIX;!8Z+@wO{Tbej@`@K^sn9J)Yx#)2rlK342qUT={Zh7K7I7* zBu=WxpY>jmWHdHBslma=zkwO)?%Mf#6d?6q^*(024eD znc{rIQm9z*y?j3;btkR=erRb841;0dBRFUYOe9upA7H4`?arwwb9TRI1;&u(M2oUn zBiRrpCMqy^gE2vC*+BL>FkUb=_TLB1E7a7)#mzqwrV=hx_wEeHT^C3OtpuUK=DC{{u&Y3ZX!}nWjX_}V%_a9=6@YGXJ(zY$z+uJ<; z_&NT`Klu;%(T{%2&wlnZPM|CT5jFCg)xS&e(kIL%@2OS_rCYNnYk?cH8bW=?7>CflVqh7bg%m0 z5#iU1Xk-5O(!jik=8tOQ-$z*~*L?oxqJGU^B9e`FTI}mX-%8^W+Z-Gm=xE823gM`A2r<$`1W)t^6AZSp zIPaJ=O_z8ggyeddBBUzIC!YH_k3D{loxNRNeeE?y`+J-?cASeBF0!<=!qscndHdbB z@NG1bN{AudL1e~rRI)y%KRwSp{R~e$agl?A5%0ctnR|Ed(JRHQiR>1@bLMo zI1o{Zo#w?ziq=pW`R_&RQFsR;p7ADT5Az6>F?jC?LUa-c-Jxhk9bU1<(R}adcbEO_ zc*Y!LK1An78T04A%eXDii$f~d8}HLtLnA_CNLW!ZLXr~+)MzMlX%IPa@)W=G``_i{ zsWZIw&U;+Few|~-PHo!07yT9Y=l`A~`?z#my=LL()AI`)LvzlcJS8!5h37yazM&$fUy^!AA^O zVRE6)nv^@%!ur-4#spe{)_Z(_+EzRok9h0dON^S9P0j~qNY)2 zih9@l_$A{}<;58@CR)!El5}9nGB;#a=m>eA1Vz>^(D)H;zt4340O|Mh+GO2Vb-Msa zXHQbyA$sF>@9PpJRS4QC!ePp3<*-Pw7NlmxWZ3<_!FLe?pe+~d6DJy4aU=k?cE(j1m}IKz-V$m zX6CqymsE4d%$(+`*a>8-!NH4xd3KI1iu9~h)(QU$d5 zxT^wgV3{s~t4IJ>JCD*&Ab$Q<-bpY*1?sCef(r zAT*BP0{wvmAyL)nhC_5jS#@X`tpGK<}UrS=Wr_ak;(_2X>6 zcZHLWpJ90BH1(AoM(^*kbfV_zFFngjZ^-?(F4Nq&&DJNLrGMfC+q(^u8xMH;{e8H% zP33$RWEhJWn*`|&f^eak@OZt9RLe}8aZCqcO#-KMnw2v43}SptJZ=CJ2v!JI0fz25 zQhulq?=cD}Ix(WpNzW3hE~i~|fQvJiUJ*TEeKHsq`A~xiJX{J~TAniQ2OevNoW0S~ z-)?eV7l)^5TSm6!&Y3reJ~&)RVVvdqxD-~WeA9U>k`5A-?G-1l5?ab&9&K`K!PBYu>;7elBF8 z2F12*vDR|w(j^{kKgyMRV`yz*&_oiy3f-hirv%Q>Y#QrKxGfz|FO6~^HfM_rmO0O9 zGAPYFGY_>F^o_jAv&x|MP4CG%koFnH${`!~%J=ykkr7y0TB53|7&7fXVDDtQPJE#x z>tcRI$b0dS(fE))GE2pI6=v>>-#h1Gs;eS(MTcp-rXr7GW*^MYvA$?H7;ygL1(sJ< zK;YA#{xsfuzWwdr;nb;9T)%#Us;YV7i6{82-}(nU_Sj?Gx_u{l_4st~to*JRUp|N{ z)=flcDoaE1>Ixtd-6048RU?cGEcN>=EiZH8_!bXu-Q{<``I~&-hG#wH?Q;1 zw>LtX$pg*A3>k~p?Z*QORc$DWePe+Icn5Ryh;9vgB z|Ac3rewtgi?(*k<{ujLRi`N)WCWr`C@{Er2Y-rqwhdU37x$Gu?-gsNii>^&vH;?zT z>$2#O>abD1i^B`)K-XJC>jJ)M;~{gEs+qgu-BGJNL=@ac|grKORMN2i8T1XZ_wiYe3?A@cH&saNH#l1ye}Qeq5s8k(cS^skaHrnA0u z2X4xHm47L%B_pklBj|gi*wdlQn_A$V862WKQm0h!2|9=Zn+PeJg52twTdC!@; z&+*MoZauiiq*rqcG5Fa;QiRe4htNbv;PlQWr#^)6<}(p=!|eaB)nPpw_fw1DTmsb zYeS0fGss<_G#u53`IF<~jomwDK+|k^K>WT-Zd?9Lcg!fAN?M`#P2Zs>vI>^Eu9Q!! zO}rD9x+#w)mv}knIhZ=`Kiuc?J6EXseI~8LS3S0yz%D~xfA0p?R7}PVOBM)!96e zSsO#Feu$u%;Usj%VP!at69y}*F{Ik|aBZ7`t)6BIGLt+!1*9-(J#7#?hSnRr)I8W7 zaqpGa2?%vPpmiRsp=nw!UA+;*zla7!F9C6mUthjP5U@TPi6*W^t5f^5`9es>Xpxkf zAt@x9hfg#qvaWJ8&WLhaqoL3#2Z)i#@V(McfKwq4k;y&+HW7>$DhvZ>=}UzbLnRhpIS@nN*7TN^!E0DW`p--D$c%z2zDp0= zBSz9-bTHmv`Zcz)^p0=RtS{rI4SVA;*Y4e=e)x#qv}L*1r|J)IO<;F-o2S40an_%G zns%w?z1QDl`|T^N4f=$(VSjg*TW`M$Z{Ohjv*(d#ro^*1Pj-ES7+#f>LD#lX(3_4Y zse*-954!@eq#t5@RM6COEE{6z{wBl;kdRNBgKm`QHcO~%XXKPXFj4t1V$(kNNGd&U zuxM4ikU~d{v6zh>?%54`)g78+OMJ53VDqK>RPXLnHPPtpT#Fr8mY+Yvs~Z!pZZ7iz z>&Rce#&Bss^V}9!mZl6gm)W{`fc?dNs$Cxq`+9mT0c=8pdfKf(D1pfsPE$oAnF!)M zG%gagkDk84hvemus#CgH304i8Mxtkltt?U{!*UH_U~zT8g*Ne@oGh^+Q;%QbGv&rQIGu8<1MC>qF%DGP2eu z3=Mv%hLr){3U=af4|WNU#eX8)w7HL^N2+>71!C#|O@|>WKUGPLGP4BF^9Hn;D(@6ZSL(sxHI$qdVdlHg z<-@vp^9HwX-{#`Q3tV~s3U9yjF5mjrKP2G!@sFcd<`-W0BCD$_?CtNdwYA0O#uf($ z2mJPL{}!iCpW@fQ`ZbRpJz{TfkNv&Eu5rEy@U484!{OFB&f5sP+jS(bz5ITiDbnSZe zYRXg<@`RN-1tEIIcXC^efQK*dlozA|9tjtQgl9szC-IGOx?rb@xH}-7x`fz9g4*H8(PbM`Y-+kU;O%4m^M@X%m4hppx-+B zJ;T=K1|R>}vs@qc8B`SpM^zgJgFa6@agk4c>Qj9Fh0oLP54iIF`}~`K^Jl#I=3ALq zMLJqLdGaLx^gsHK`Q#@*MO{^#J6H2ZfAj}@=R3a_QzWGt0aX>#CQL>Xe*M;){EvU~ zCydAA?8&QoTS)1{suV)owryM!-+82I_-Bw%%*zo*fw?Q|j45iy7=yJH&b2t#U`$He z<2t=C;(M8CMAp@%kV~D8LgvHFe$(mo z98G)5BXfTXdYzHzDH2V`p)Z#-2{ZYO3ptWSj}(2#20eXo*Tb1UN_YQZc_%q|Rr{)Q z??a#pEz_#PS-}}$+^g6R!ni;(+Fn#+R0^f>`xK=}>Gj#Yy~0u(XzL0eplMywQoE?D zuwcT=k4&OpO-+#H<`D;Nd87$T&l;n3-a9$E=)kUY3Yc+3n8Vj4rmjR{HS@$tG&+B_ zQ%4vR7oD)q53szad~tbp8j9!3_Z)o+)U|b#K~)WsGn*zG8fW$+Dq?iHeGziEsNGpC zto>Zvk4{*0&mU#%jW-c ztk-?^ZrtVJyVv>IyKnOH<#!o*XabnJf>b5h3Ym#Y8e2YmR99w6HkAahV@NkD#i9^P zc_6LC>U7@3!XYHV0J?!f_B_(Oc~v}KiYuZgFR6%y)pwyrssPU6ZG{Y%VYot=PH|O@ zuPU%TjI{(YwAdKZ5j^y&IL9fWa1>twObo9Upp}4kF_(!qc9x-ufm8(zL#oyWgj}Z? zNhfWYgiPqbk1=t5tjS>})(WO|xHi=zPKFy()YX_HlLUlpOx7=S6|FO|g;{2zj4;uw zgjhIZ8^@b<%eAeBdfdd}^(@B3+(_XdMDPkoQm)z>WyV0D=#%JiQuJGcY{{`nxoaBj}8{B=k%`boc8hdx{@WlDEJooS_ZDo1m z#h);F_d2$j62cTD8lo!`M^>JIcfvB#J1J*mNI#?3T5)=)Mh`%Z_L8Slg`_3;F3o{f z689$LgNbOoOp|X+%tI>Cpo&Q}AtCO95Ji?THtyljDP$rs4Mgw!3eP_OTm0$$Uoslk z?4I}x+rRoD;l=CJQ$tnN7!lUbALC=c`Fs2@>~iheC04gS!JBWq#EJ8ptUdo6FYa99 z^y=db@7?F_U;c!pYX=yy5Td6w#Bs#99O-ybO3+V;-XB#k7zdS9@leizZPgP#VXq*> zoI>$`YfMr`$e>89;0%Ncy!A|~Ksz*e6R7Gsdayf(cMfR+^+XsB;B3H|Kx;jIV3-;oy`4n}ROeTX5DBOiae_gQK!N zR{E<{2Oc{$3~ZlzU}>GlnSg62xO?|;4;)ejLIsUU1~2cau`Js`oI5Mnehrvt+;<=& zgS3|Eb^}Y$&{EYEb=Av8-+HBwp~j5^#(9GCG~+P`?_cGhf}7)gZjZ){4-T^IQohu& zkNCSD0?qKsMC2v`ylAjARJNK4frieoxyTc+yk-wLXNw7yV68NgmVMJ)i~5{~rD`5( z^`?<9i?Q_njI(^qZ7dR!my9H=7k@^Y5wMbz?k5JDctD7`v6K;L;VwN?5S0U2pUMbE z*`1~jDCa~`m&uqj9}ddMH=K=S$!y5bzAm!pOSN=S5`D&&^ zr}Ns(fFsX$c?sR2zuqCxt%YJE6bk6vg{%ju<3Kj7roM}ZfXj!u-9d$?u4{U|9+T;m z*WY-ZS6+DqL>LYSh!`%ve>wI+Vv!AFEU&%(I$Etadcdbbj`++|gov@Sqk)e&U*}Ck z2ri{IN@3ubXqa!){8h%Vv9ZR+<~q}+p*Duv))0h~$4~Ol|LH&D*3E0&xbi-gwFK{Z z>ZvFB>}Nkqe=uM)8gb{&9p1n4J`W!~h{Y8{#|x~r96NT5-~IOQ@|CZB74IF>HX2r} zHF)1LnNI2TdaSLju(q}e!1e1lxPJXAP16+oTlK;~Op#=m=h@x&g(nmpHbK|<2;FzS z;H+MnYqWJP?!&60s;fvNNvA6JmdsH1(brMVL^C?HCXkWTuf#KR?&KrdnZF_rPxs|~ zQt{8$#i%)N$UH51c7R$MR37Er4vir?6 zrJw1%EwCl(-;412eEU@hp1N0SuOKru^6VdFFkIB|yn%DR6Zx|bVUjZ^?;nAo9)4eB z)HB`~xt+34lJ1-B(jsf{yaF??l$1X|XwGJo95cHyKFUbF1e$(@Uov=WSXp0V?f7ZT z${M!yalbl;X<7y@Ff@kM)pg9VO|~YF@yzmoi4mIgLUojJA%^-X`wYp84(IXHhAJ8G za!$CQ2DLhc!A4-Cz())mcn@MRAz%b*NuCM>tO&KlO4A`F6z^DQpe3YyIWEmvsxU`f zE8{)3Z5j9}gOxR!6KCk}9p~iQDwBR6scNtmV=BDw<~b@%CMy+f@X?nk=hul`-nka% z9X`Zx$rvahsj!jFo73b?8e({Bk{OPG530C@Oq0qGAp%Bg4YoGWHtaok$idDogcb=Q zCk>vzM7kv8-uszcQ6-(Lz)e^zk$(K#7Zf+Dgz0*XU8xATfAt^n{+YQ|_ z39u#Bp&&RiQWb?Nr0JBm;{AFhahREZ1Xe&rp0e(6n?R#%uzr(C{&mlxl71Ca`;DtsS)e)}?i|9@Sg?)9m} zAjc|1EZ)}%6wrmEsO?^PLSgXAc>@y<3f||+g~dN{K2^{M@>uiwhNK9E4giSGMTbc{ zuOO0At!V)AGRzvibsl|cFBZP4nAU*@qaC)lR-mf+d%w%Qb;sss&a$<3oaw=YgL~VA z6~~+Vx7b@-X1u=6-%a-U`LN}jo$xqQZmuqImkAHuHqFTa$E1&S0xl}oQOM`6&1Ap` zg_pJoohMB;7UZz;S#J|C*aY}Ynk@}gS1>Qc@J=VtnyylyNCj1euYexNQlJV@`RG6z zIu(@=gceUO?v`+lh(v=CQHj)W{%|mj{YiP2r~_D!2@`~Yu6 zlMK*8VCtLrjE|n9Y5?>;g`+DkNHpjL7D_kn001BWNkl)24I^dD7CJNd{^kJ*}g6 zcEDV|jNMvge^_(tV4qRzvLUiGB+g@qd81;atJ&=pe4z17-V-R_DV{6apT{`aXjcHc zTx=lk%Z|U7)z8b%%I`{?VT^L{Sw(p!=O(10U1`B}xC(+(AAJa^sw^o5NKEgLxj@EB z#j_z5F;qU5L)Vq(hv?ZWv-`I*I3^NwbO&tdEfNfpaBm3fy|>?Hd;1Z4dk5UTaf7?JZnC|-!;R}VagAfrj2Z9k^6>tB zUj4-ztgWttK-*4v_~-$9JKL(O#-B=y6f~F&d^XLSl0%ktn3TmlVxE9O4Y|>nqJ_0c z3T`JGGVc$1=nPtxRTU!%(nZ{;Goi$`a;`CxXFD!I@oUZc}iz?_oOmku8l;p&Xl(!e8hvtaOJu0>bL|#=I zcG4{~Kl7Ys)GWoA$m=dTnS{9KB!$U`?r>M;UXnDEBL5oE6Efu+6cHxl3DahZh*0(F zXnYHi@7Gn8$1MU?Z5i~cnED{iq0cEyq}W=q12)|g(jl$&1}lM{fVd(zsiZJzAE=C_ z3b8mye;5l61@B{_w*CNuVKg3bR(7dXvSH(k4(kjcMmDE{HLw9k3-%#6LXL6rta{eFh> zRgkJ&KQz8VM=$148!bn#Qq@5`cyZ)3RnABIODnM!h1b4nyV6NrNa9OE!u~=rnlx+C z;iC>&s?xR&YDEd!?$LIwMb}Z7deJqHcjs+pijB*Nqc~^tI6*R|(6MRvjTl^TRAOQ! z#}H;uP-mX_fI%K*O`ZS#&{cql`8L%^<5I=Hk8&-3PdSO*XCGAn9Nlib=JV-%Si5Cg zK2;IR6xHpyLkl^7c^`1b(Hg<^4V%xL=aXOhDhDnK57QHD8>prY>*K%@uv}I2dp({x zdx|FmNUx#^QE3mx(1gIqJNDa{e@s#`>YlaqTp%2b8P+wl4%bG*Cd6&r?+-BjnyCaD z5u}Rw(wxAJ@g7ZOu^xtESTdIBctS0(R`=<9p*HbkcG@)b>t0-YVsd0Bv0#W(P!~u^ zvSSXDFrCs&Mr;lH$Y6=}@r36m6Pz_z8$sW|_OQ0fG|&d$<)Aad)H~YXuu1W71lqRc z0LMri-WrBpI4(845Krff2$c%$1m?X2+7R8ioj~Jbb+?80{c6ln)kmSzT> z@<~PT8po}QmM5ku@F|yKxz6!kpWDo8WC-X&NnIPI=XS=-(OxqckMZUQ_!W`E&ViRksWQfX~xvM%JfH}NfF1^>iL)?cN z1|0E*&j&c=dX)8>+adyVcfOM&b5REjB^qFx{$L*t+U|P3i_>9{=wlN(~&_g9sil5;9Y)`$9=~oN{ zuNAnXH)cE}jG??Z-QOk~6!Z`=;;YE%tr7Zl#Ph+Wxwa;GUz?nQYPh_F?e`$*ahs=( zGu&JQF^orJZr;4XojbP}4*M8e(KHQjz4aD<{nvlPwW~J}v3%ka&+*vVv)s9Rhj-t7 zhtYVH<|VMTvB{GcpI|zj^2V=!#gAY7JDxasmM?$(s~ms)ET4Pfb3F0XlWcBnBEV~} zz0Oa6`cqzg^;M?RX`TnQofU+ec&IS*=|Vj>VJ%he;}^_57%8cEkILDY)+2z4X+Q*n zH8!STO0u-PBC9%Vc+h?=GNjHvKP$g<{})3)bl*|eA%mB6yaFOFtBpfkJe25TrsV{u-i~PoK{07!qe(-}I@b)|JaO~J7y?0c>ek4xp(hAqtTetr_Zpmvch06 z0N|;oo?>lvm96znUitaYxpV0^Su^Q?z=-JsF7=4e((29vs$ss8}W%s$>>{IQ75IP+oxf(1!d?|iE^gS}WE_2Twb#TAvuxI`tD6fBp z7X_wK{+Z;KibWD{sUQj=??O&ZGqWk?K=y}O?aONsV?DenjuI3f%v6tRyS!$fF92}< zTkq@cKgu|HND$1u=Yu>k7oBFsFSUV=F~n{6A;LtGLMQ=ezzqAWuCMd=-~VxpORfVw zYv{Sama_~cP=%P^k)oRI{$#|+wN$+xtq(L+#UvzXm`*??c$TpA2@EQmiZR68UaE|U z5u7pXyOs$7Q`eD+S<5yJyKPI)7={>XYw-jI#!LTrV|dv z6ZQr@#=Qs>*S+C@KEverii>%!s#MG;%5h;Lo{91J$}nvk+NonAfpKjZR(%Mb16rt( zA>0^DWf=7PJagt8CtTph<@cG~zmIk8Y=zw!5=a6hKwZ~SSxvyUOD>0xwC)ERo?x&== zl%oF`+;l!{!9))UpA-j`fhEUMfRI;1zf9fB_Yt!4p<0QqmO&!8)LcMH1=%$tb9~V7m3oW2OTyeg;fSS_|m>WMW&L*K6Ug zDdhaEiGZ?q5YmB)_-2>-Hw(gz(jE}9;WN!@v3DbM@dXAnQ-WWFS{XJ@1W>=OF^9iwB7gL0%t4|TtTk$=H=RWs$%P-h zL=Vf{(`5AYdEklf#=IDDs?+}+m`tA3bMzg_ImEt8KnyrnCpCRub)j8*%ISMO9!=jn-%3P$-E&5Drom`A1Zcv}8{#g#fkQ(nuoE<&5fX%T3rINy?{GxNu8-}xxL(zr}8 zSR1fGz{P`0Evi84w7W@;=$@5WWi`Zc=`kF{0|}FagU(qz@J~}$u1dz5f(_q^p(Zq{gepff|HcPcqYK$qpGuEzW&ImAAVenuHj zcp!9gC=fIx1_`Oao4${@$KrIw1CPOA$Ye4F?=ljJ``^qn&Adkx6A=Q|JBDJYZAE9O zjOy$lz|g@U9b)?6Q=vw>Hm$+oaRe$mSJTl0SN@}SzTV{`OkfhV<%65 z7^+^6Z++)?IeFnML11-tg-?9yQw%Q+xp(JI@`4oB*Ee|j>8Ch<{&9{UKfxEj@B&TS z@ZyUva_Q1Lh=iCPrMBF=caN7|dWrS*b?!fSz}D6l!@+>*WQtFoc*l+%g8;W~-{#@N zhqP_W#>NI!RWX@PnM@{(MkA)v>CBqWAAlWo0czG6{LtqkFuBA9>U&6AO$dAG9u+j?#My zUZ4jYGrTCbDau4TS&FFEi=HN<}l{)KD~<{?@|-mG#|`Q$dU6%gVU) zl2r90#W#ocK-YK0IN9kW^YxH%XE_(p83glZQ1)p?PKC556gekj zzy1}VOr6&0V%k*5eVjK6GqZN_G8yN@YWAdpyroe+&skxo%I(h2Ez+%)kZoEYWB~mr z{IsLr86eQnurD!hF(XR4e||;1Qbj89IUhn!z8&~+j-BIwdH=_}^{cyV-ydVFp(ln5>&yK9?>x`BkM$TXFLV7=j|acJ#1otA{Ps6L z!Pe=P{>FgSNuNKvbcJauxaCtpjL3f!&nRtmi>foC`#&3v(|AH&wHW5+(^#{^T`b|= zc^xOgEWRY1r1m8*A}=a9)kCu|2GhwjiWX*m$#Sk(yP(F@WY|voGa3>@ic&Y-ns-Z` z0F{D8mEkehmMf?r?qv-j?|dR+2z}?Bsi)K-Sr9aYC3{pSLWy3J29d%rreibd2qTdB zNZ0EQ1gRNTKg_5@bGi}@qa71c!++k?X)ZmO8f>@6bGT}OAACOG>41Mo#NH{PA+JFQ zAK)#isYG=1z(T|1A{?ZBnm>`A?`Mf8)BsmTg3Lc#0{i&`Qw<#s5W|}?_UZ^p566}e z&M&RAVgjw#P|S{BYW_Wel;#1nhTf+?G0bDOm`Wp=9VPiMXpT`K@odG&k%bt#9H$<| zE|Je0W0iPgU%e+-lMNHaCXO;fdqv9pRsFTJB&Kg2W<{bhl&v$&6Hjby%LlI#y1 zPgey_OyWb@=k!d(yn;7fUPFD(*kt@p_V$>$r53+{E@ikXS6NzG=ILji=FFKhEDwh?P0M&ZW@l#?=N!FWkM;F6zWL2>@Z59Ha_`=Kc6PSe z+dtsety}!`r$6QP?c0f#XZiG^EAv+6J@a%^T#MQel9UPwj|HV}#Q=$(ollV)`hRAy zGq0J~i$97nGusWi2aDb<9?OTdpXoaBa!E>*PnB?@^G$q5e%qWZD|(Roln)Mr@<~e| zu(Y(q+S(efZFzA2K9kXix~>`Y`mC+4#;CP^k0m#ZPBJ#myfMOLJm$>lGko$>pXS){ z<4l_-8@k8i2~E?ow6v7F;hbYSopR&G4JMNbC$^3;7z|=xUBEXEtH)I#K=3I=Lx*{M zo}AFFsmc-MC+UTO&`pG>?$Pn(5CW2-Gv;L$YL1BJO79V#F6&`I>YsUI0nD9M%d z5j(CfaWirvinNgN+YF(Z63Z3qohKlTO%J^iPRQdGnIXwg{uA=cbN|sfEa+Sk*yu*n z^-n#}^WjwJ_EsR|Xr=gEI+#)eTgMU(&tZWrXHxB?mSXIfrchC|M7HsCAgQC308l}% zzB~q*8Qx60<*y=wt-91p9a`x4%SX9#>YDt)VLO;!AH_yDWAiFxEi&7qP9%429>Uc| zsB$i$0@8m_r)Gdo2GQl0v(JTMr|PMLE>bA&Jp?QZfPd5^%udAoC38P#$EK$Q@lnH3 zWKq7S?CVip6GuIB)EIP;B5`)7DM$7uF`YoHS=)*S8;vG?)1x&)zwTx4GI0(Q>P|sO zZKyHQIT!QhKvr&aojg7%ycL9A3|E2f)O&_eNX)rrEXLX#E}_i0)Jvm{B!y#Hk~@qi zq|^4i-AreI@N~ssbmlB-01L&tOlQ(kZ#jt|!KA_>orgfe*e)aolrBlX3guYjz{XH7 z2}C1nR4h!MgjgI$t98?WZ|1$>q+nR_+1_BlQVnL}7&HwMVxb0~LWKfHr&?H9S>fc# zQ&hHMZGDx^wKZ&2vA@61yYIcn($W&=&YopuZ4F!1Y;SLK>Am-O@Ze!oB=aolDQKE! z6{eZq(nRIO?u-RdHb=mIDBoOqOcK39$6+K1E(sDi8fcf3R?p!2@*h2Ts= zL!=x*-cOwov|9^P2*Pa8465uEs7~2fhs3f-C{IOgWwFLkRTU>soMe4tozeb2H*Vgb z-|uny)M<8h_qcicHodCi^yyO!21D-Ly^D9Ajg1W^(+T}SpWWSkZr->-+xo7Wu#hUk zO9m}I@v;tl=w9gficzD7kQE{%8Wczc6f}vuD(u~Q2LzAxEvMHFU-{f=wwt%9X<1)8 z$?9^zio;4DoM%uAPn}rj^PjoEuDeE0&Of`t&h#p)>r0$FyT$)Y z+IxRVavXV{AM=RJEYYH>wT9LJLGF;;5gfI1tM2aIA9{E1%};mxW_M}!v7v^SP6K*zecbK7cPC_9hQ~V6$8;w#F z1c`D>BpjPf;#nj`11mAtptVo}1YFQUt?dGcK&07td^ZHk*aYYOA%-T*I8cC#f^zaF zwkhHdhXnffU`MemDp$muGX^%x{o(t-NQ=}F(dcS?ChsFDv1bpgNj&mhNL6~#NG3NN zXiQgNd^Z_O;xo2BW*961qrD;XgA6S-eJc=i0vZLUR072OLNOw=>)IekfsthdB!*M-Z#h%{oMbF;j{ZIwn&+!64a1qqiw%i4odZ1tiXgAs3^|vmG;CW+5s^fs7M(~f#ttHl3%>Iy zkEWur-w`c|M1yn=2*|okXuNB?aO#L)6fYeyiO;E`orVHswxgur(P^w{i{)r&G*qa! zb+>V8aFD2y`mCy9&Ap~nMIonhFkY`Lt*}(rTsyfQbE2D!iKB~==+2C)8ibV-ht`5? zJ;!%iULPE1GHtjs*<(Dcf@14ih9aC@KSk9vJojUKU7;1|QZ`I`-+&0!aKzMgG|n?~ zLPbbN1m=uWh$%*<;{zT&++caMMBBD(@9Z!cj~P$K?CtGSRTXc(@g`@_o#kMB!0p?2 zc>LrkXHK7CWpyR&H{bF2@nb&uN~F{e*|7W!?P{@Z_N#_(1hyz4r)x3&U8P-SUp ziLUE7e*9RRu7Hu7rlDzOsjfiCDK4R+`Nl&A!y%{E*Lm>Z0lT|9yz#~xY;SLI{rWeY zK6Q#gUDGxp5BG`V#~BTVjQ7XvZ12!EEzT@PM9X%Zz~vlr$Gnfy&iR#gqo(W=nW z1*2W(|hX|^D0R^hU}6sHpUp~EHY0M zREh4yJf30G*^~r>ip^5koFR`>pTH#^aa1R`PH|q6a&2=ergYeQ;sQ7yb6`d##P)#Y zBlKQVk&Xm7QD~bn)2T2VtkC*4=FxERJzcDxTqIgA zu9IsgSzTRWG9GjG)G4lg_#qcBUuNUc2LJp2@qen3 z9zM9wKmF5x=i=G3{P+)k%$=WT}eDPugPJr zXCC<6y8DiS0CO&gj}H zjT~pH4ks(9j%Zt#sv?JV>F6cYj_)>SfJr5bHbW{D=$a-JUUD^3$2OBJ%W^HrVp~tO zZ^|m3ao?xH7NyCJ#IsEHmfl>;MVFxhiRGnt+TRlRgSyNclk`p4Dw+fxxer=IP-!79 z5z+Q%cqxF>So-0_L#API%ilLRZ^+} zpN>6ehC#?{FG##kh({GrHO0;p$j30c*VfkPVk6SiBSauT2O7$=@7$Ps`##@ zt}9&S5@r{*{SG2EofEvbXj6NSqTL|UV&2k5R|wIesY#Jbcv6XmeX?v9NqHMw9h}JK zXcl$B=o3#hm@!kt_4H1u;(H_Ao8vRAorfw!cUgLdL?0HF8f+QBC$WBF{#b1Q!p{)-zbYd+Q_UR}M0F|p!s7%QR z>ZK7nbacLB@MOY?nd4E@Qnd}97WIxwp{XjK@9wd=@sw{SJ8TcDXhZ=dl+o)V#W?5a zhC{ATC+t@hgXqWrp_You_KtwI4by`?#s>${n6FfWn$_wU%gZY)EiZBT>SfNHIn8J^ zM8vUw5O}#cZcI12aN#1)pFd}JXNR`yc=YHAzxvg$c=+%k$BrGNs%rq6reW4hIeFp) zXV0Bwb!8RjDmJ$^dHU=b`{OZ7%S+)T7Q*=8fUmy(nrF|RrTGhd;4q&JG}6Z;X>E-+ zr<*e!rjO!1A(L=Ob<#6X#`F!zsiPquyDxO!2!akpcg^dfedEAUn7-Jk2?#F|#=Nkt z9FQZKhsS%JNGP^wr;N0ttR$Uvx&B)Xsb%+r77g1C%GTBvzxwsBdH>yaxqSH&&z?PH zcXyXpUwtK{9%~v_R#({D+vD+*C!tT3IRiaUo;+r0X^Gcfdz~jw9&`KFO)g!?c4 zL)UdoCS$59IH@Po3Ck-hy!-xpbl&sXXTRs`Z@ytVo8eTES%YgmOPz9-ij@k~ffD$I zBcwy}-gA0sh4rOnwC(5wybJkeYbUIZ1`KdC<7tXyH5r~A=aH6NL92%8#m=UXRA*P! zw7x5F)M2_wI-K{EetVU^Q@KaeO2pXV*_1{m{Tr9Ad?wOL#=ej+uNLnuu}|C&iYBL) zSpSVKon)h;WXee!vD8HiJe#$N)HxC%o4#2zRT}LLqgxX;?s5;Lp%?UPQWI6Y2(If` zbrrsCshpj_D)#-MMh;)rUcNo^42^@f02fzPXijitNUOL1zn_s(d+YcjjJgQdzR2N{ zWb%SBzlaajRu}^27U+n{GoVv`yO<3geH~@=TVy0GuSJ3;e>XpwS#dplU-nMr*Da2= zd~tscFVdnhENVBrQS8I{UM?JCZX=kzd}MB(Q2BeRngl1_Go4N8x{kUUgj`u70a*G7 z2+;sGd?<(`S`<$(1`y?eMh4mm2ZP`ra9^)0sjTNtFCH9gt~IP^72)F z@Pi+4{KUz4oB|#^c))Ky{+QLJWlpWH^XS=A9zA|c+crhN7AyRHTIkxhF zTzi_UqZUeg$p{U=migWe?e(OfLTTEze4U45H zUDx8AaOLtO zyLWH%@ZkfRwxNib2ue1Ena{AY5}hy-N2_U{do|y>fw2r6G*Lx4Wd)Cqd&Q#0%46P& zeMgAMj65TWUo{#FuJ)9Fsn`d(Pm*|cf<#593=9%^r#Vj4lZ!$^r+?HwhbOnlQE zHL<1QeMvUNe+-Mfn!>r+jsf2N7~L zN7RWKPY)R~MGo=FvcL1=I11M+0(LLD(sOfjZBD5)zPWzHm(2BB@_LBv2c;4P?|J%s zhhN>i&xXJ(9`8=Ugbu_@!Vs3KG#P86?3MV1ITA&Qk)9ShQj7<|j_oE6ra~o9Nkvt; z6g`+#f_XX;3;s#tN<)n;8y-orzFZs0&-9wrl5gg<(zq`T;Mm_TDP3l)jfK6;2nHz! zl(#kwL#fFzN%ASMbfxV=x}Y$>F5Me}R9xfOUm@+!oB|=bVdo4NrnA*mm9T+}>!@*# zG8Xfs%IfC44ccb@X{#hTH(1(t3BQGYRmHxrh|ZLpCMkqDIw{k+&qcQkH%5$^jFVM% zf0^?E36Ad0hhw~2M3LGa2`6JD=IgX5+=w+5EV;ErdJ!^D?N!GX$@4;}@a>#S6}eHO zv4fc2glGf_vOr@l$zU{%d26Fgg0L^-w#xa_r#O4&H1%La z>pPle#$Yhy#EBCuEsq!tM~D+noj%R+6DOEVChYC*GMhCaMbco%`Sa&lUtj0p!;KKZ z)+tY)Kjr8D@b|1NFY~iM`57Pn@Q0i@emq3C*23D_I>(P6Lp4Nk{^*B4q-k3ap{gsU z(<#6H^{@H)fBQMlH#ZrN$CM6{?_j}kSqpwqUaEZu-dtmSByTcU@)6glBni+;sE1&E zGXB+n-S@-p^YS3+z;O(Ru<-)fyvXR#d&IudkhaEB({$#@B8!`T>Y2<><0FaZ6ac4> z$t6Tq{^mCyBPi$3pQo;C>Osx!-afzl^{;80hBw}PGhhVYu|FPj;rw}|a_k=*aOLVX ze*EJfbhny81m^SpYqvnf5(IS z_o&+$+BvFLIk$2GsYYBG4VVlfx)h~rd@h3MJ;#=o&{@m9-6!ljMI|^Ls=DI%XpObO z5FzU7Ri>qq{|Uh;71q_~dt6AHr2th-rzPZw{GtslKFhL3Ax1@O=BV*R^92r3A5J7nG>C?iXEBY4kx7hNu3R`Z6ADO*iHS_NA`vcY z=JyFZn|#Q!Mx#sdC^Z>r^lK*@F((;3=y zxVp|fVve!LX1{YECcJI3(k4(e1;d0iQgRsN{QtO)vYnOq{U~SrLIWkX>=Dx}h|aXj z@KYUs#=A1ZH0HACB}ct@;Z+RcC~NHZ0s8ZUFhf#o0o!czp87+5P3;~YMbZ(Ec;`{d ziUFXb`g!DK45Z}kHk|Lck^%sj17`qc-ovb!(Kb!k_|a278ZA)|;*q7uCGzp|xVe277;cQvXqs6#I<2NW z#XgCI%H1MR7#j2RDEQz}@G)mrGn)Y#L2;@VoEf@ms7&fA8gh;XxnR5xLc39q7wBS93+I)lYnjodh>R+Fx!EhoxReO84X9E z>SRn^yb-naJl=T7#>4x(_wL)FZ=I)WTb?|6Ofx>t1@%{n6>msu) z<~4x^12~dsBRMVvq|6&4gC#TQVEPiICtU%S1z3A;nSx~CkKFMduxm3(TG=H zd6k2`JzP~&RTaPd<*zuow#K=0=Md*uURmMf$&=i?b%&2X{*=d$pCO{`?(Ff|XJ2yb z+q-z*(RM!UL$BPtd5h_6%I@A?Z%$2F&1(?nKK`5+9`nZKG(MX@g3+LJ@OZc7A3nOz zYvZ@MaBYo^dovzB88SV%$Ad?EJbC<_?Mch?*^K+YxW_gxaplTczP>->{`PSm?kf9L z!`pA3g|6oM^`|tmEzo_?5Pj)tM^Ugc` z^d~=|u7}*XeuMYkev?1><3Hrw#VZU)%S^?e7HAqM{q@0-ln%BR31bCxbx=c8Z8j16w^K zdqzZN97;JJqhcIRe+8aP;q1x8k!i$?g~_2|`!(IGiI+nm9}O%UAS-6@Mc2F>HKWXA zGGRKM(lkvTgGeVJ~#g43=fH9{y17bkIiwsS%{jo_?wNrvQo+Jk`hX`x6+1jDw~)CWFjtyn)3RoK{! zE+UV&is%_4=cwnj-{ZT2#16Q^S$-|G!bK9~d>VJ$l0oB3GST{+iePAv?Utv@st^lA z+|XA}f^9P>d=-YPB2JsbnW6G}Kd-bzMb>v6t?v>R3=YQ-&DwQ^)7JQtaZg)Eo4@$J zyx&|XC{|U*$^z{b3u}`#)x_TL`|+nS)j!n7_AA2k2IlLEI}-!6?38{nvV1kXfji}h?$yW|pd&m`gZ{n9x&Lx~tjyciWTpIL65l+j~I zqY(L4CA~TFy{;BAm3-o%_jZk~M*VGTbDLj$^ea|YR+vmCv~9!l%^jxGDI)OY*WYmZ z^l8@D*Vx}bU~g}q>o;!j#php84~E>kdym_?LBoRw4LYic$b~=0j-PC{U%eR>@!SX>PmeFWn8n$jt;0P0M#}Ym+&45v&w#N@gHUUGwqGKmuP%W%<}64N}PRKxFxJ zgN-`{Ik)CAvV}12nkX+QylXD@yByEA<_;C_)5t~CPohpd3OVj42M0)bU#UCg z`w#W$JNVHe-LvE=Y0TMRv3TsN)Xvo*A5y9Ukrl8b-kycJHT1&nE*`+5->p-NI(m2! z&F~O-`I?+HEOL%JWey^7E ziIm&KyNbruh!ck1(c(FfHXSLkcZ!A}@1}O_?d|d9$Dg2WL+6zVp1N|$>*c#{uJlK~ zR#rm7^tysGr_WN?!~FTRYdnAagew=$^QV9E$GrB|o7{b{VcVMoH#8_+aT4iqGqLZq zU@FDe5*e>*A=Io1I|2YJE2|t^J;vVNUI35tK+!r9HIgg_NlJ^7`AzD+1c+S<+AxBC zRA%l=7UbpT&+oS}$8mZS4i?o>06k0@4&PM>LxCj;IZ(Q`VP|I>7b7uhSAna7S4@Hv zV0C4ct5>e_`s;77e(Dr;HQ?Fv=j?3naOLtv{`Aj&#`s{ufBn0^ z4ztOW3SmhE+J<^%h2fy4t_CTWS04~Z)hq#_5-{7}@n=ClhExn>xPpv=vfZaCUqo_- zd*UJ#^km{%Rf}DyW!H*cmHi_bu)9qfs!4hOA&IC4Cdw%$B!Rz{=#`qt)HT*DyFj; z8;>6G{OJ}uTRUuTZzm&)8BeQfz@2aJvc0**rE?c~<&{@hSzbZC=fS-P{PLH-irB%5m{kbkt@u}! z@)k-V!;KaaQu!Z{L;Zfo)K_WDMetIOe$xw!kL7Ut+TSB+m=nu4Y{4^52q*oFhRZI+ z-?Weo7IkI)Nro{Abh@{*!`{vgbyeY28Sfti9#A=Uw|BUG>n2O1B}T(xIGu=wNL_?a zyk|C>(YE0j_TFsD-tG>d?Cq&ZbdoG&%O?~8Wg-ZT7}?w4$6 zHBP4(cE|xHE=@{ts!Gj{736Q${|rZ!^AnzRNE$?vVS%BBoW^k;bq?G2=DeU&J1LqX zlgXzYln@lPtnlXgY1WO-CAXOng7u#lV8lEHsb6{Sb6(}jRfKcAkgb#Su}gt-%by}r z=mq_L&G%;>o0QJ}uIrBEPd45lPRam*qOS|yq25zhuBL4jtL`XiRu;UdRCIgDvp)gf z70pooy=ZY3wUY-lZxN1wl>VgWPG;hrZ~Le*F8aNErnDw6x~EjGj`E)MjH=&9aF*{V ze#X6q18P^Nf;U|aV`+J~^_-0_?r`vQGae6%@jeqK zozS#na+r99s|O)WzLM-|6iZ4THc&0G9ia5&h;|`|r21gYbOW>+(5a^X)my}pVtJIAqK;tb1Dt$1a}d#M(Mugm%_YcP%X;dHf(67((*f;@DDgHM1EN z%CVId#^VV$uHVGfHTwr+R#%TPs%mIjp4`1hJD##}`wlv7s7u;3S}GZ+%L-i29AZMn zG)Mu9h3!=%we=ODt}D*0on&Qsm2Nu0Hxsh4CNmI;!cCBrygwAWF%VQlTNOqjkd%+h zI*^6R9Qc~w_q{32;xaUCM**_H%zG^htsozLFc_-hx)#@YDukWQE&R0Q#F;hj-@D6f zGGRCvf;yV%j6eU`pE4W{SQ-qdUB%|pXZ-3HAF+Pw6o2$bf5=IibMfUEV?Fv zcJ{#DXQ_YFh6&h3F#nxXH#$SLhmj3{VGt%D$DC*;k#rKyze4$rdv+-zAALfm&^AWC#+OfZT^A{ILDenvGzX*Vq z@3%c*{J|8%PIECk(HJ!WiB|_M5_VAtP>eu33y?x)M7d?gkvx#3K!gpHT~w+XqTy@} z9GOSq@JOg&ogAg*8un(o_)So?l3^io%p%OPD~i-_eP!?IMUbD=stK?iZ-a)e71MoBiC z@=wVB=>^n9oS0-+RNlx~w|sF@C{=rrlevfaZ=Yx(%S_uBMuFY{I6)S4U7PlFmMF#8 zOL;_&oJwRB>Bx?Hhq0IG6|(-5Ftv>#8bC6Q33g;P7|>%eE9Yoa-+UrD8(=mYXfoh; zalZ_zicbIU0RR9X07*naR8dti5kjCs%#ACAQ(4olt#?JN*_wL@~ zZ~yM^`0DGgQ12O3BVKyxB`#mS%&}v~Ie-2vSFc>*;>Ak8_|AWX0$$|w)wgwBOrwr%A? zo=u?VG>h{PS=S-^f$1=o&lgOOrQbkWZYcp@NOzQYN7;7Y*=WxhkM7H5GbAR~nB;6< zTxUKghu&ucBm^3zLcVhZDg8I6z8t!(>_Pb1vvnE%FLb>Uqe^PqMz+8fMnS*5!5A)d zDeX#lrG}ss7pS0XI~dlz{#svNjFkH~q$h=5-s`q)!+w>F?|Kuid6gIKZ~M8(K6jKM z(>^!6Yfhy3XL8>Uf7ZKyfi2C_Ox11csoa175Q+|kI}goOS_rYhN7CpiE8jWw3-9S0 zBJDHsKqx05Zkjm0@Y9~RynAI^hbQ51o8ek?RBc{)TEx)5?>KXh;_PWj`=ux4#g`I~ zaTAIr3KvUD zt~lky$}zgGrD-Od@SYV{G4jf&Y0=3n7>1hh!nMD5^ESWy`TxVLnQ`~}ZI+H53sDrl zW9#`d4)*rBbK`*jy!qFxuB`HOcaPC@#vmpgS7QBH^$A#r6YwSk!@7s#^gf2l=MeS8 zcDz+ak82VZ1aXc5O1rzw_VzY9oib?Km@~~7TG+o;1ScYr@?3d9<5B)_tW67ElLe|^ z#2Z#W?VU)}R{6LTK8b}Fyu6U-sjVcU3&E6yVxR{RVW5tI!cr&HolrH({>Bq-ef)dY z&!6S`w>LR;`ZRC7`ySGI9$df0t=l)L1(v2Q?|k?nZ@&5}!)nOcGiSMU=_23!?sLBU z{Bu^8M_fF4lI@2NIkCKii+Rn)5{_P8y>OAutz~ZLXUv)zRb6FZ5c}&S3X??0#2hqX z)DAP4gjo&>Ql1pmE&?ur>6DFcZ&RGDDoQvb4mta~C;#`Ya$U zEiLovtFQ9#{ynZ-zQXd#3L=iBr4|12FaLts^KCAkJI84KB*S5iwhexAfNMJHwnN3E z9%`?gYHO}FHCMZe<7>xpYs>g0p&N8iIi#-fO9OmF9pVU%8X=|LkfuYX9j;Z}%u|h9 z{J5nZ&(Ka|o?C}@O6@9SX$Z%Luu{Xw(TyBk4O*K*6&7zKvIVUdVG zgOrs2t}yN$1$yRZm~s&u6xP!)?YY8uR^sCQy~WM#hltWr@eFeq zs;7Ij3e_okN|y?qCl(pglnXUD6YcmjNk4ERcnQU6EJh_2t0>-^Wowp5X2fyMAyt*I zo~CxoxeXH^_8swfwiI&~|BuBcOPxf`_)scgX}EG|ohVk_^cgz_oAJ0N;bzP^(MFB>2O|8NCAh9q| z-ujw&&QaHk^iwfhmpVN+=G4d$pBL|-7@j#A^5%#IWrc7G&nq--a4x$F7mIggS3{9| zFmZ}VTafsX?fYza3h#3X<3pKt!O}&E9f^*-*dk)fHMiV++F4U^qj}qZT1;Iy5?uQZ zCmtD9bd@lzVO$9lA*3@wQ!es&_#7(1SC*c`^i7ZuDvc0wU3!ht@I|iX#IJ16r{5#p ziNw=|4wqJ@|7`JOUh19*FC3}8+xzm`m~GM>P3N}Hd;GPSyF>3XhiRd^&0Y0y1VW6c zEmDW1WfH6ypsi~P7?twgLQZpdPI}lbHlDUJnbz~9x$se@-E`v8`s}-W zY9wKlQnN1SE${1|LH5u+nb$jU__hl+9x>;1AxjQ7u@D;{WzS0dCen4!&u#Yd@^hv9_3dnpp^jPaOd^G(oz3=S$mnb|(UEgJ){=P=tKJYsXqz*~fekU;ioEV7+gL=TMIMbs} zf`U6(g1Sbu4N+QALV6R>A(0CJ6-D$Gww-;*;i7p=E^?=^rDK5#ZwAI#(J{oVCD4?_ zlPaN-pT~@p-p|6iMIIrMA($~V^C2^8l4mWIvNXnBf{joi6d##f<)PD%gDV=&Tw?x8 zI8fv8c$`M1GmblBir9Y*Le?S_9VU2#*Sb=Rl8Ow@Su{k&Xdt*+f;XmZxiDDft<{qZ zy9w>uDfHlgLD%5hbHkOqWLBr!?zN*?cr*)5GqK8?=uf zPzgM_{+QA4?l3riiZxN1N4wnqAALe|d!Ny%b@umm`C#g)A8*5d{{{8=OSC7} zsjlDV<>?gPIR?X;;ZntDd71N-WB(KHR$zq!tXJNI#3xp3kb zU;X}fJo} zkh(?&!?3QKo|++*LN!t9gBVrWHn^#R8}RYgUA{fx*{RLQ6pWfqBpH0>jhRA70rH1G z{V}h;`U-pdV}AM3$2@)VlviJRiTB=rkJS?=_~oyD&COdk@ttRFZH<5PSO12!wUd1N z?OlHV+3z_RAM_Njqpm(i=x;{D3?h;Yn)6P|z5%fRT-i?VUG|JsRqnHZ-ZS>Q8E;eN zZfiz0;gLn}FL6W9i7FY_n6g-o*ZOOF6xBmgqJ-NezcXWnN=Tz38f1dGxm+Pb5mJ=p zyg`bh{jA`JjvoTT#j_O8Iyq}4bEGg2kRXC6V9Po&nfqK6qEiW?1@NsyD`5=r)&&0Uf-hVZ!^ z=L`_ZcOlQ4(NI?C!vkg1L|WehYLz1jv~7x-<}XS#t8{-kES+2kB8EeCdV=x@zD+#Ha zd`GLX;ALErs&Z^?Z}RiM|9d|D^i!5bBM$Ztc)q!rdE>(k{^oD~3l}b&W(;@p#Pht>;P8n9Ul#`06W`SC=_;>J$h2V{To)#n$FF z+q>J0CkHfbg9v=`+fV3x%gOb1c6avp=K4+U-o3;2_D+IWm}p0YF?A)QCsPz*FFxLY??i{A&vMstE+(Bi&bURcmrf}aWqH;Ea^9do!QmLXC%FdZoU@jV`ya-k}&h%a(6tXoV;Jk`rZ!=vrtWW7`E2AI# zM#M0ciZ$A!f9=zk%#Dlqna`i}WLIB~6|yBSXJKzBz9Znz!DvX=b#zVBFIH{$$A1i` zXz{tCX)W{T(%<7oN4-5RC9x-2vgk6$bsj*0kdo}l?YXG8T8ou9X46@ekAf>s%$0Lu z*E|kfbOlE>gcC5HN@Bo(&5=1gRkL$f3XC_$M`V7^7TlxdVk$Y`W<15(wxQtdQI|RO zNcb(!ofPk~H73z{px@e#(Q3}DO{)I7fH^K=gXL#OMN|BbU5We-fy{Z&%36F`Xzn3&Rw1x+Bta+5;ab-g?UR#ni9kew(kT|C;|;j|?) zo$M73vpomuA|p>mEhXRA8(R=2p`ckHNGCpR^urzmjUd({JblqlR3Oy|nZlUapQFPx zQ26$2{$rn{P%6XmU<1`*QnPUOktY>Z#Z!O#z%fe9Fdv-zltIi3{<$d zJmL>dU*goXLb@QVG)|_*;}q&ZhZQ;+pc0R+S{^YcDP&b`K~U5bO-G1&n^z(X%*VM` zNI5$+f-?yTqYw|y3r+*Da2gG39xP=+l+b8WW?i(qhB>D-W2O{q6o7Nv2BV)>s0TI9 zRiOw{5mBYSI8YYpK@vTYlrRIohU%PDS;*CbZ-6iEa%^dpKUg`3Q^n6b435)2o#CF` zz~gwOs(ESU0@8KJfl__5fxq(%={#NIX{H_LMR=)NV^Bj?b#Qvv!*l92&QpyJmwE5% zt5Anj2^t5GeBVM?$C&KLJMd1?Dy&~1?JUf5u?KP$S3(kOT+E#}n&4NTQyqtHcoJGQP5D*WSpD!)Yc_%Y+ho6t4J34TmKik$5zo{&Hi-E_TCORzq!t*zxtT)!U5{>>{4Ks&tm3FFh+d?-~+JVx!P@o|gDJ0Oj z_xMR0jF~fG(A8A9m?j_`mF>jirycFY(>(Xg>NXT`XU2kdd7v`_|=Sk`ASg;K6};FqS=zbkV9=* zRHuk==roAtpYxTpZAf8Thp4)u>vYxwU7Aau`sWlB9~0x3MTTr%`%1`s4ln_mQ)bpf6T(mO;rXnN8dqrFKj=F@mx|FaU$nM~=A>G1eV zygF}~%*sP)yt4m;A>I^Kn}A|XouoGB>9L%zMR2pcr|Y`pm?)JZRZYgsqZB3k%#Jc= zrFQsm#m9A-nTynw4(nTe-@g18xTkP-eF{lQCS9dvE0D$VW=2mAXtSH(UAxnLOW)~#DSe)5Qw<<$`RKW)MZBEkuxU^H#{`ucV5-G9K+ z(hB3rl)b$@X0usvz;hJM_17-XJ3wDg47!*^{GTUE%YPGJ37@GCSX!eDs|SNHr`jRT zg_9*3PQs;d*B-GM&pcX3o&T(--=@Vp`ri3#F|RKjR3?*5mlQgI$#}+aWr^3Xy~eAf z5lt*4r(G8!4h2H>>|o4PtV%bjufgC*3`)pFUxLi+vffs?pnGN$SI}c(q>Fk5SAhg0 zYumKJIjb?K$7h>|sHF62QLT+~H1KFwRiVA{gi})E_NI)RhCT5#E@+Qk0VYze;@pKaIpSQ(`$#+e9m?x`@={}ozDNsRN z)Bf1PyaalWz$7+SsG)iK=>3te=>1OLD!IMlvnqDQNjL(In4N&!uE|X1=Ie+n*f}CC znC(Rg1)w#gHSuvvl}60(dmV-Q=Iw7iyISy;Cya67!UgW%zki5-_1n$3V*YhWM@T$2 z{9cE#*m0*k-x-$u%dUT^5i?ItHcqBK6ar-NyUP1|->_)u7lQAC(Z7h1AN8I6Jlbek z-d}caZoBEfWbVT11o;^9UUpFtQDQDOGy-T1$9nC(Wh93&gLGKP+Ac3o&y%OVWS?bs zk|7YwM;F9XF~$}-e&u-shjAJNp!U}+mved~agaVX^A4MZ8RQcvewrlO&U zlarADsIoBh$n?=<^h(QRdo`|yB`pt9lPoEsuy8RMvH?Gd$7q7xU9rQ_!GmOTmDgN~ z1x@WJp|}*;Q0>+HgYmeWdTO8*N4-zGjLMNJSg^Ot#>NNzmJvh|#EyiUIg| zEX$0KfT+WDF#@g<0_0H;7mjIlincxq9OzJ_QmT=lt^;CPhS+nD0qn4@U9bv=?|@ zj;-NS&Yix*-p-8uFCH@76Ljj3u0rrk22*rZSby~zs~;}$RM=px^s-CP_-WC72nOM zrXCr4szw=(JOez)jPaHT9390jSFcxOn8d0w7kM#G@x}2-E@kp zW3DBQ3SBHh20}foP;F?Nmf>(nJ*?@pV>)T@>f>@n&=Du!xgmp5O(!kgtc`n0u>+^x z0JZ|&;ip2=dIp10LVymKO=c+y7nGs4thO`GbN~rK_P!l%BwReZj0}Sp*l@R#2*ORl zFPWzjj*?4I+`sz#b0+)yRIZ|~2ee*!y0O8-+qZe+?YCH2S!Os|;zvLFAwT`s|C&KP zK%C>`$rG$BFY`Pp_I(i4XV?~**K=RZ0aZ;Ep40RFZWn)Ngqikz`Y5iN_T_omt`u#i zMDFpvP@n}UjZvzR*JPk1mli*J#-4})=8beEma{?(=DbYO+3?y}uepa@Vup|Ao>KWYUKJyw*hPZ*lxKDN4&4R z${es_SU{p;71Q1<#^&>X=MVCDeA-Y}gFb4Ew`U{jB-B2EmvH`23&zzJ1h+BS4;7t%^uM0(f7 zC^piGoQBqjzc-JQ$%OG_9NuGNAaCd3n2!!6DNpo-@Avn|Ovcl6`o|R9bTmKeIw9io zAmo8*DEDg%3P*)tY&9d$JvW>xDKwS-(wbQJKDkX0juZ9yP^4+XDUm(#eEIMZ2U0VtE2KUMK^@-5 z{Op7RDKXsu_F0_Qq{0&Q0Pmx0%Q{Xu9&`4%Xt=bTpJGN+1gR=;N?TRt!?9YTA14-jGP)G01ryvWIuC%Jd; z9vhDzbK(4X&YwSzAUt{UnBj29sne%vnuc$`{g#7+gF`em|NBw)jdC6P_oW?|e;Vy3 z9Wm0SYUC4=?$#dd^f_J@$#XKA$WLVcz6||JtkB#GpA z8fXf*N$1{Hj-?z~XuI>81>L~0xuo2ZmUC6j_1TQw?QNRLKKM4-pUdmnc*6aIJuaL& z!R6b4$nm1NTCJ8bHTfBPhotHdnj}Qe)*e4k?+c5Q%s z;+4%)eB&OneEvM^s|^p&J!5sS&huvvxgul6(|b$~?osc0d;=?^Wd@@Gr>>l$zBXd# z{6kKxtZ?(#UDm20L;sAE%d70mCSUK~<5V}`%<&a2o;$<2y23jb4F7S=ohrU*+1z^0 z?TveA6C4gs!x5*tBc{9%L^`TyGg_*Y%T9TBw8q7gr`dn*c(w#PL(3QKBOs3JE)t`7 zm8!0}c<~~C@YA1iVr`8tzx;yFe)l^L4i2d6ipo_ijh3i~1E!N1SFc^+?RVed&Ye5l zyL*@SKKOtO7cX-2<_*63>MO*1X44t2s;CB`fP(kR#mkp?>#eug+S=rcFTP}Jdz;Z< zfEU=^-J>21Id<$gUDxn@^BK+!IdSR)Kl#HyqN-|q+W?;b^f!M)*Lo_aaZHYb0Z8T8 zTXE>B3&+RBDh(~$UqrRnUX5sr- zc+ZRuX^wh^mrNt`RkkexR^lWiuBrB|7E5k&Voq_MGbpCD)QmeyS#R&kcqi_GB1h@` zwGcP<2;pc%*1v5I?=1i>MsxOTlC24=X>ZQGE>Px5qsrVkfJ6(2jI48*wWRGdkh!5Q zG~Q~C+B<4K7ygdrrwfXW!l5Y9`7!0(k%!yM9iu3E(Z}ZBWe8k!^&(3a%yZdxxx`0~ zI~C5fq!s3D)kDwB>lO1x&7;;FU^<=h;KBW#0eODhq!Lb%M5B~fMLUlY zqWQ9nJ4_o#{T=4XzUw_Yw~tAN5BsL;G>od(rXVip-XS%KP{%YusHVBhuBQd7GiN;b z{pjBnp!RO6{JDrDdhjSIqOFoHWrm;wm0kcb#cd>wL z!TaWA^SnH_ebe_@BaP2XKn2yfBn~g&C{pPc=@H5_z(Z#KeDy8$v^)0f8@;BGyLSI zKV@TMgU!v&V9b00zgl#?*k&RN?>UNoa&{BLh=*xI%XMU4j3~W)oO5Hz;=16^vEjp} zYujIj+Iw3qK2zMCAcs;Ga4kSC6ziSIyZB7$_hSC<^g08aCCsTF^9*-xgB0r{(Riet zA`%ME*pU@`w~f_|nnwwjk{&b}rwVhgI695qJD{OspN=ixu~LoL9n|a%26XKZJrt7} zE??Kw$4{`dvcmTBr)=);hT$U%`Zbxx7wt*wpFJBRF0+!CByGWZOGrkTfIt9OR)QnarNo5g zCiE=Y6c_eYjN0)^EP7x^L*u)AuGnh^0PCj{qO#|bN8E}d*}MnZ4pE+-4t20QIbg8m znW<+xl-OkR-nVRDP-Z(%89v*mUGglg3ETCE(Wwh;KiX#VYtI|6Uu3wn!tI+6s8<}_ zPtURZ{wc0c?s9zjfCp!l$=WiL$&~lcUt@nJ{BCv32eQnS^%I<28F2sZeY~r2gE|TF zdQfxW%o%0}``p$kelQ~Be6*ufk$y!Gs$eiD9H*X)8ZIs=mq!Dfx>)rRIu|K7uC>HS z;^pJVSYKOb{nQ$7z5Nz{_UC`W@e?OGerlcZWXiK=&uG>2^vN@R^5Y-#{`((r@6H{r zzIu)K-+!N*H*RtB#!Wu_(T})t^$Hii`jR)_`+(8Xh$l~UzM=&K6G|KjreJ z%Y69551CFTy!rkOZr-}Z`STY5BRf0}JHn0_|6)60?EhfG ze}qHU*x{cjTQcPsX+|SblthXmn{1N3ZvYxV0}Y^1s4Ae=x80lRA98)KUI7Kkz8#IK zdhg!6nI})4<(ntZIeYdjU-|WKQ0Rh{`}gVfy4<+C#77@|Ot-s@b|w>jpr^p(d54A# z&moZ+b&~(xfv$o`!DqK_QzP_y9gL}{#j@7vaQn_8-A)I=@ZNjxv3U0`r%#{e!m}5+ zdGi(zAFMjrYwKveN%k%XhMyhK4}$U_jm1Uz2R)r|Ip66dW5T)~ko}pSHNmNJpNBC< zQUtkQ*Kth^^wa+zWxh$ymxu@J@-_oN6_3n$r#;FH8y%`BzpP0zNJyX*L;x~4Um?;x ztb?d?{QW8rOcLFL1E4~+O=~e#A8RVCZF-rRioaQJ3n-N{i=#;@h?G9pB0hS`%eUa5 zIIq$Fl^lw}RxPR6Kz4RO3Q4G5PdJPb;79SP%@$(%1 zmNeRdLP|Le26Se|`!>9XH0C%iWBsv4|Aq*hC*7EAuLNR-s7-JnXI4WZCrNz9sHePe`63> zwF95+_O8&^FrM&CzG36Vj|FQqMu$dgGkx#IVi*{uIJ?Mr8$PbFDChH^WIgt4D%ervyqZ~W!4UMeT4r%( zYbEK}Iz+8X)+j<*G;nIj9q%n0Wz6Yl>rg0WaQ+77Y8T&g_!{KdK-_#44&E2uG5l=Q zZ9z1~zPWadH(QM;R+_Me0uyR>$B+WTsiuLs7LpAI;42vfdL>;Oi4gk`jxR@@<`^l> zThN6;-rKP087dOEQ+g3>4nI9OmDaOAf#K6+)kGv!4@83_i+jXbqGI{d24UKRO_@P6;@Agdo?5^LD>%h_1 zUP1NU?#pFSx;fb&gMGVvy7>LmS@c+6$N-dBg7PW6-l9 z)y;7G5`3=_v>wo%er?CK?0*&sT(eO~<%nk(RGtL?Q6Ogi*`V@H-=!li$%$}=APlIE zUEVV+aCcSK?uLQS^LF9_&)N$HRSsh9a=6BZN0i(`m3!vO7Gu8gJAPZ3(JR?K-8 zm`Rkc_7?gAK4E_Te5QtF`17Gx<$vZpLQYSzcKosPz^DxKkqNpcZT}3>W7Hh%Cd{Mb zD1+ZQfk6}sVRX4tc1L7Pq+M7C!$#up!rlS$#$R&!*)#_h=NyV#Z%X;fzd&W6XP^>A ztrV`j-p)%3jUH#qG_0Wn=BB4XgbJYoLM2bIpcLYd34vsx=Lr-_ zNtOMmf-#8o#FNMe)>pg?hC?v&!te>X1U-GC)8MT>$EFe^R>S&f6MM+jjBS4NW zs=y-t{lN-yPC)2GG~jXr8UgDgK$U+^DSyn~M*4=wLO+#xM zO1mWT1%6Eh%37@W)CVFlS8y#tr+}&uY!SG?<)o}VZ-6_REkL6vc;(gC`1&`$3F74F z{Qd>T#>RQ(`~@0CgVtCB3eAsy@IwwAKEl)IpP_7&96oZGx%mYSwR^O?eP-w9n3|qu z|H1*rTN6yrPP1>{3dc?yr%@Cv-M`Q5+!Bpui?&Y&e)z}{5ElhmmJP04{)Cr)=?gq_ z?ir*kc0ZT#{zm zH-45jyxdhA!%yCLgR56Rp*hxK^}!mwZkxM{9scy+{V6~Ao4=*5YqlRgq|@)QxxU8j z+n@3CU;Lc9s@YiIaBUfvKrDg)5Th z3{n;4LDEq{28WH31Pm#{3I@n!FGd*k@HXB&jOf1cp&aHf{3G2udZ<$GAceFwhqGj0Jnh} zX2KW+S-wuUroDZV*)9R3h>~YWmbqs`Uu_ie$)?XtCYwJ)Ha_3Sq$rZ@6uyto=Uf`> zu^r@&Kdh&9IQ!t6$6A+r)J4U+wNq#fN?hucobscNXJvD?1CSoU<2(FIo92GEXX0=& zuxMzA3=qHuu_V+8MnbfH^7Wm3^pe68IjtMq3d}E&HEgv1fspV=`S*%)K`Fr~kPx*wESo*Gl%h2@Mx)U{6`JXpX^OI7 za%u`)6cmk;@yQ8VV=Z)1&}=p-%kq&}$gcC7-8&3O4a=x#XFUdwcoar4O70DQH@fZJ zz|~#Hlzo)s=}=L#pmzd~4E1F(No|rFW)rF5MC>0*PaerW=w0>ByDK=~Zd?)y{(WyHI~Vi-26?7)v?jO24qiu_R4MM)SW87tx<^=e~cl-Q`z z*9v-tpjr)Pre^6k8}y7pNr|X}h6oJ>-3lrzKA2Zx7<&SpefYVBI4hCY?T&ZntQ>@L za$>_hlIT8Qkf)~y)N6|&)z4=Fp<1@e~c*bYLc_TdCO+ai@o49x)V!5 z7KZymM3GK_iG*GJ%tVq*iQ=l@23jhg7jclI^K~$&SO$4B>q~UXQbPP(@rA#GGa7d{ zt#`Ejx;G^JHUQ9}AIWzWlvNbgSFzTDtu39qn@o>Yym0X_>up$lYnjc?GQIgN?#*md z-&$d^-DRQfu-1FX(}%_=7Eba0)vJ_O+MH7Jly;e0*Y0vdEz#NDLdKUE-MFSC6kH?6sFSXh0veE7!Xb%$m$lw$%?7;wMHV4o$9;fYlz{Q;f_LP@o zL|en;Sd-7c@ElK{JIk$`cUiu_NOf>Ob__N**V$TL;ne4zXaDg-l)zv9PhkjdSYcQ%ufGQP(wh?<}&kbf4*&86K=YV0~kQ zqG-_T_x#!pa~k-=>nHCpshGMUx}1JJBz+kp6tF0PjkSktu03=Ptre6pm|n%ocAE!_ zi-;(UDkB)t>vdUw_%Pc1%J|%@9{93=HXbz7kt+!y4r#3=dqo+)f>geijE_#ta!30` z9}}YoT4ZXBV9>{PosMsZwUJDzB=1~=v_Cp)J*egl(g0Y+XdjWdndG($EXpcE5s56W zI+KsSO*KMSi;Kw2G7@^ZK%!A5MRsfehTE_%zpAy)R#1gEKpnl@{@!3Aju=;y>FH$n zXYB{sZlJ(#z6^9DNx~E@;Arli&n16q+|9A)SCv5R_+_M15YPg~Ra+Lnz5|?`YwW=G z631ebX&iVan}@_-ZeC;V=%^z+C4zD9gtLnX599HC8dI-$y2S{n@VPf8a^D$<^AZ$+ z1Ucu|xZA8#e)GZ-GGd44g2 zIQgwX6t$Q*4#{a@Q|@a_qD$0wxkD#G-#IXK_#}Hrc%Fa^)cWlK?Lv=%2_^N&n=MF?&&DsNp`@IO6O{oAU36xtEM) zvDm1jO|;-h$GrW7S9d^lE56|^O=wp$z*;uui2Qb z=lcPA4EXboCyTP6Y?Lf7->1`Q)9rMrjbUtTjJu1AY;0^GN>LOA%lGfIy1GhLRReA1 zL1K>r=JIPecq+Fq@cqwOCikaz0~?30N8L9HaR2|}8r9a|-*)8Z!$9S3ZU001A_L5r ze&)Jca3?UNHmE{lse?8kT4)mF+hD(T#gXtjTL7O^ShS*_o!Z^GuAgYAyIx_ad!=2p z5wo$F;m&096pLwjT@@ouExrIyrsN;h1@nYVn07IDwj7M$$Bp?>DDiHKCVR_{_uA|e z8C<=Y0W?g^oox@?A?dRWFMXnGuL8pvE6(XNo?(^p3`@=wNeM`@C9ty>1*OQ4AxP+ALhb|bLMH6;{^oO&(`Yv#7J*7X86nyrdMa7F z10*B2)MWbyv&};IIr=DY_ zx52%i-R0V+E2xz%w#y3Dt2lLRfiIss&AkVoU=AE(a{K`MC$~8J>y8Te~FL^Yil@Jbaj0KElUDKq)#x9>!5- zaFZYuuo5gGk8l7(v1UO2geOC6TwHJ=eY6dcnT{sP>p3SYU9L7Ie#5TzPP3$EgA*iT z-A0LcfXDa2CZY&d8W3-FLa)r+P=>&;0l#clg7*e^;CfY#Z z&q)73x2OYhd(UmgD6bbnak#udVLk#CXlrfS=<I}3Cc3q(EfQ^uB#zFf% z=z;)(hL5U<(a9u%l90myZ#HpWG%H42P!r-`h$`IBLtC?5DxMBFJNuhXM` z!LNHs(Qy5$igedX#ifwgMhHM}ct=cyFM>hE?*h*U`JalV4iI^Ox6=GbqMct`!XQ24 z1_w@erQp`V4S9gB1sV-Rt5{)O#04_+>z*&%YE$u%p&ktYkE1}8$6lGn%f_GNL)PaZ zU@l*Gquyx;{tc56g7ee-?|ZqNe6@qT>x+RZt+6}d!n8Jb zq062E@KRIo2p-DY44c3C8r-?*J}7I38u7Z%Qq-YBn50LCJ35SFKZAs9@8P}w|xS@yi$ zoo9xD%`m@dXVHcRND*j3fe#y9{{CPL?_eHAU!Kq64&A8BGh|S*oc>DL zL3f~NDCk!xYv^^hSzlS9X?rXzOff#IdG!m&IPvspHaFIoo;%2y{rkE4_8qFKfZ8%W zGsUm{`q!DCoZ|<7^;g`u^(m7R6P!E$4CgLf;L}?-`T5U&#^%NbhmRiNOTYRuCr+Q? zkN@v)b8l%WjnO7UR-(t^My|DTXRQO85Q_mApX({)K2?a;h|(0gVBh>ab$1)BL6l{D zY@F%&IU1AWAVR0x<C;Mj>1jJL)(ux|lf6l`s6VXS3qYmAx?TrnrJxFD0Dt`=f zYI2gbwKYsF?BBm1i{)G2`XluDpA1DLr3VpO@8Z8|uJ+ zR=^sjdX`_BK1`$EVS_H6hOdI|qh+QVEy|+g@7{Ws)lL_DQG*a&Ww0@aV_nx&#<*e* zKChk@p+HfHLW_1$WkJDPH&+#g+J=}M-?~?uF8{7}I?1{S_u7VWxU?ot=KAP(8;(YL zKCmula^-W zCbYLU$}OkQf@V$gD7a|p+Ut@8oQ0^+AwffK8~HvL3XJ7I*RsDZDY};0C@Rs^mMqs> z=CS1td$);RDaoQ01YeaX%WMs{7BxN&Qu z{g!AKqVqz(jIG_#X@4{j@p(0EIyUbj7l<-rz$VNbVo^S7)P{Zz>MLVx)MM5fM2DG@ z$k!qgb50v;G2W}G{c-JRH;HdAj>`j5O5z%dHj#(}46R88b;RG}#a<0c+Pwn+03ZNK zL_t)s;>XxH3YfsBx`h)25eOK zyNZ2xed$*K-W|M@+Ezw(g`+~fO}Q3mV%(d@OZ zzc9Fl9&-)LiuLt%wzjrh9z%~AtgNiCwzdYOBenPL-HRt%a=H%Fde2zNh*I+dwi;V5gP{tOaGlKZl! z_r~|fwdeIWAcX@nlW-$4!EQ3-Nv;80ExSgw>BV^<#=zQ)7@faSg1zI7!T}wMJMmE{ zt+R*yhVEl{Hcv$TxJ$fYwsS1OVBLM;?3t)R-uTIRq!RD~ZB!1DM;RE=5G5nK&4|8> zVKa(B9t9l6?;k*cPZP+CU*enP$h%Mzp_MAEQ#r7gY9dx|B1H z{Qn}sz{=b9`5FDOl&aUmTFcn@IP`j0rJS)$TETHARbHA0! zA1NUct7;|(Mm+!2-eHi`59_}dMj8MFCvgN#W8eJ^T7`GI%FLDZQD@rcI}*2pR}i9; zc3uY#9%O9t63ff?C?;CG_?53PH9N<4yTi=PEN2=Gdi^$Kt8^8Y%LdQA@H`9q_w%hk z{J&UQz8~4d9d}R3_jlvZ;VVUKI8!6>I$juuXl{3}D*|8%QIJ7^4w&3FJ1!IcW{6#f zWy3B)I0{Np*pQzKi`Z=QX25`P{Lm1~Z+zn)@xqHQv$3^}Si|RE{sO(qQdJ#}9@)>U zufE3A24t?%CgCc zW3&9`KY4|+)#p2Z_!r!{xlJJrOy5wH1qxxlIljK|9ju#(;J`itKax_ zUV7yvj-EJyD9wNT-gmjXbZ@`_&-HX}cPmlQ2XR^3@dO+&bR7*9+wC6Luis?fp~I{$ zKV<#EL(I_yj4^C(ZFBS964Rf3MkSVy-us9nhmUgV^l2V$tg+qcaQg5mu3WpuzyA;a zfw9&E!q&zn*REWpP@3mI_c{7y$;!$K##9tq zbNuKL&YgdTr%s(j7n+Ij3C739=ytnkrRn#2G$&ntrR{cyx88b}Znr{2V+e(I@V1qEYO!I3TD_~IsO4{o#C-DFkZK|`p<3dYL@ z=ML`U(sSoIsxH#s>|?tI+pUqlAqYXMG}0)s1=xn7Dgj%&O$4+%Lf*4Tw}!s;QKS|r z1R9#UFvwU5Md2%hTck3mo<;S+_Iwnw@&HX?p;=%`XAlV>wSj)^k`(uA@8l{(3${^U zn;O%oeg001R3Lpp`U1u$F;@z!tOMw@Af=Bm?AJ&QQW=LP)%eIwkcP%;u-fYrQ=@9) zUZEA``l~GAsHgY{INLKGTQ$ku773|-onF&|@rmUX#EQQ8W?NKa;=nLfTXe6_Y*{eT z8mCeveO!?`6Ln_3vVxefkV)sHvfU?rDJrj@dmyt*-qGq4}hn|B3gTuZDY>x2==)ub*Sc6hw)4+qJeM4PaL}_&CfOVw{Y;7=gOx#0WQH6#=+?rgoZ1gNYxp{-XTG^me_#BI* zAq6{A#DV`V|5CNUVz*!KbXfzstkJdMi_q8}ZkeI_bfTTow(*0+QkEZuBr!wwnXR@^epZN6_*L2F89#S7fx`t;xFRFATVn9Ml zI0<2d76nC7@^I-PwqIciMb*?nyoa#SEod>L|;6+-v*y{7ch6! zHEh#|e8AyvYc2hLKaRl|Gf=Q>)TT<_-frLR25wtxqI^hGG%QDU0$jUo)a-WUviV8C zz}%M^#z*%0?ooZopW6*%8pV6Nftybc~)@sL|Kn09+R_Ai>tK(C_u=^{aT?C!qUcKwJFrQh#&OEwP7>Rb(-#az4(C6-7lJ zQsRyoig&D{(?EDcoUwz#R22&c_H*#yL8`iDW%U7f78hwYn{lx_V-+9CV7;N`E+yUo z@lJ&epdf-MVZ3ZGJ3WEcns&ct_2EOR+PH|TOp==+8rX2IBi`c%FL4m@QPLu`?55i! zDDs>pL|8W(KN5~>AvDV-^t!H!?ubh(C<>utDn7n>9rJp@qh|zhY~FV_kC~ZDJdSMLTJjTvQm(&q6PlMVo#UAc&+z^S zA0x(MdNsXHmvc`)&HlMrSAeE0>9*S}F5YExeT|Pl_>gM0N(iN8{ECS zNWWh(H!}+&)K%Xb;4WgQt}2W*v|3|y+HDt==c9R?^P9$=^W~s1HRu2jE2jV&ho51C zAPhgPhj6}DFqPrGcRuEW53c&^nt~|H)XY2!hfmNo+f2_K=C^+1InF$Dfse1;;QQZy zo$lru`xlNdd*CE%D;;{>E|nhVoe%C)w2redf0*@cVd-9%x;KHTCeXzkC!czn>8V*( zT5A|BSX;C2(0&e|IEohK6jxCqMZv7V~O_U+qrI&B(_21kz`;n2PX%A&!mU-}}`vol=#lpDxd?hWl;|;W6g@Wi7EPX6Fg=5++17d?X?vat3H*oUfvtVr^e6|6WC6T zHVWz1NbP3E5S#*wX$W;wu-YA|4msSk0bL1H7Agm58sm?k778g9OcWUHqfV`J5_JXA z6`%W7kh-R}eQW_zme_E-IUN5qpeh{rSUY8H6&Nj;!eYeuv?)Gz#NZ53arr`B#oXH6 zQzSl$&aMHem22BrCx5Wirb0DKbXg);OjRcv3Ri*G)DDnRTDw#azQCI`bzIAMkj?l= zlA=&l{l1H6RSvwux(H5JbjjHx;X4JyO0IRy;`&2wuCB6WYA_{Ir{kEpk)IpRUfd@+ zD-LRFp_%O~X2vJD^7@5fP$D zcRIe}qdMJYS&DW9ap15uF-prKC?BzEvEXxR~PT%5kRpK5XBc!%5B-)-p@B0bD(lJpCo_pa3AUrkvqi4 z46UJI;4!rye~b4IIIx-ho&!1YK9cPj$QC}!`|quwXbtF%uRtSm-iaj0?8)qzQG7b$>&N?)!~IBe z9C&e50j?a((c00^<{JvhU2#8x?=i&FL5@qfCk}q}mGhwuKFAP_Lqz3Xf#IECZTfr^ zNVS*th5_9jU`Je|=r@diJPwq6T>l>h6z=x@KjeCpEPoQv`0v}NAic59 z_Ho7VjL&uMrL^^%Ym8pgqxv)0H#~o5elTwHSkz#6P9b$XL(DuTMd*O*7*P@<^ z_LyJ)QDY6JZwa;nq(CTREJfJ$3Wj&=rsRL@?HUd2InixxUX3U$;?aZLE0YA!&Oel# z^X$#3aqnE%+<4p>aX4x`L3oY2cejO@dSHis+Z!`H>axbTJXiQ*dQ}x2G^d}$7w_<3 zW>n_hAbg_N2V>cKL)k_rA{!ARpv4_)A8Rz2n4F?)j=8UmMMOIzS)kkP&}nZoH#g01 z|FeI}=U;r0veDqDKl=s$%kTfMn6PW*jTT*$&WN(s<#w}{s^7tFC;UF1BE<78St~< zkb3PO9gEm(G?s!PDXEZne4owMu=VfBWDBm(j<>QY$cXQyPBKz_r-wz0lL_uYjZg@T zH8fL`V~DlfzqiE6W5?)jY+@`-j*aoa8v5NHtwxhOpWWv5pZu6FeEvnAI(drm$r%nG zJq{S&edk@K#wVD+Z~?6}`}Xalu4}s84rN)!=#O^0&FtJPr%#{dy?5W`$3Om&J91*Z z1DkApw-$}b9NwT4Je@s_1s}_q@u`bE@CJ{vvDm{febYQE#;4|4ICzrfdn>dzmocix z%2vT&{^%B})RbfAS>JAOe`TAMhkXtnImd&0i@f#G67uhU%J%jWEhR{?%H`{pZdI{w z!P2#DR@Y|Pf8Zd^VuCx1oAi1PxG!{pQku_h-s1gt-{;)9^U!P}x`C~Q&px@%+R6$G z2j`qjut0WHGiq`#Z(Pl@Og8r1*p&?jtdo2X{1=MSwA*bu{VukyT-0gVq^e!|hnK(b z1r8lM!tDGU2pl|c0HqYij~wOU>MB*$=jo@P;oyOTR8>uF4A*a5=LbLdTOK@kz&HQV zH+k;jb9{33D(}4g4%4$U965TFyNiolzWgx<4jiOWHt2SGT)TRWpZw^@99r1N@BH@f zFt>jn*FL$%2OoUE*|TRkckUdWPKPU3t}r(@&ygdCIC=6oZ@&36dUe-tJh=5(NP!d$ zHhNv!m11+NN2LY3)xi{&y092iqx>;LsTE~M(dY?US^DFaZhxJoXpDxLg-I?Pn&f!t zoZJS6w?4YgwQINOSO>h|fkG8dX;_IgH_Fmi79%wVcg)=Js-TSnHAArnvC8Gh6ot{y zFAbG8v6zT9P}D+UT{;1YV3CPAiL6*^Oe`EA7BmWtZ#&B&EJ{<0p%zQ6Ow1{$tp|II zJGw0Si53YN418o!7`GAkj!=afcRWUw3RP;Vx=I4q_-NE%uqv-}LsY-d<%tJ>oQ}^o zjhu08GPx)wY@h{zLqG*E(YRCr#!zd;eQS7F7W50hi7>(`F~*?%#-O$79zp!O6bgf` zYowuB?rrjJyTi@#23y*n-0+er$Q<{MEQ(ZoB%}@8;y+h5ejbmJW215Gx;*IEiLJoX z-^oBypF@dng7$IM(M4y4V4^;E@3zrE$Q%PV0Cz|wB4$apghxWYS0kj#=H7r8ar6t# zT9WbZr;4y~AJci-=$^(u4C44SM6W7uTZiCM@f&JBpR4i`)ZmWEhxhsv(y{GYEJhm# zmLkq}=;6Q~tFX$gA+Z*^?&QaGAD-wd@CC0fu$Nwo3&xQ%hAlo0Vz+V13r zsZDfpmC|6Xk63dXVM@8!oZ1?ye&zfHl6h{eL28#~CiJx z^P*GU)37NKPHQT^mSX-_mCK<_ZU*}d_r5}DL~9!3EyN1rV>66*HD$=(Zmkb`up|A) z-2j(SAk#=tZ}{<)?0MwZS!14mvpe#egWTF9Ae93!xfwbNgv`etK*-%{c|?$Muv?*_ z?Dyy??Ao_c*OP2;?DhHK&vzUDaQlyY*JuEI^gABazrFN%uYGzP4)Xs8pt+mQc#`M# z!pnL4kyn%ROYn)o;cq@oar`uEIFhn^kWb@ImPkRaV{asogT9Eo8v1cy49_Ca}#eE-mAk6+*r zPGMVPsp^Wt*3{PJ^}x-v zB>k!%`J}FD-g^68e*TMJxFeN?#u&@|+#J95t6$;R(W7WxB6_frpNKF&Gs)>=hZ!Fm zXKZ4c|MI{1m#l4U5G#B7xe3N^r`zSnufNWRAAN|eN6$s56uL9gHZx!b_$}j8O2+1~Cl zJu}U@i_cLuo9IHbymXJc-JvipA#gfz<(zH7MX-fqZz9T{dGYHhX+YnRQ0LSVkK_2c zivUt+FBr{U$XNfh@{zv-Ma2CxVJ!tBTF-N?;&S*-GP#v~XT720d03(nvi>e1IGR@6 zX}wYlP1SJoU@ErP*IBxClT)Wpv$5qOWhckR8E-bQRmBUJE^+qk)67myF)=%+&QjZ`Izs1_q*J_a~oS3w9?-2cSokQE_m~2KWB4eo%ZGi z%2t7LmTsZyFZ}T4Ng4&8f!*#e`%Q)U;Yhle{ze5W}I!^=X0;V%&p6pdHap;^WDGs8DotC zUDp)N364MgEQim0k;VHDD95H*IJuww`}b3AY;*j`7!%`Dh!W=K=J?Vpukq=@1HAIe zYcyLel-69jbdlA&iyS<#pMwVvQAk0vEco(kUt;y%G7lfDAXYI>4|{rh?5+%t@i zkJIn+dO5)zpUpkt~ItDU3e_TEBaFCqYW-kPN;x>uAT<`UTL%{!Y%X@30r>vTIE zl+rGbsrp7jOLbkRh;dOa#eh%o(C;}=T9lxR zVxWDCN%F@RNE6?e2qt^ED}j!_lgckLWAV}HNtT3-b?fOFQ$kpmc42@z;)B*bJw=@U zF!=Nn64F%=Wp0q27N2L+gP93vvo1Q<=f8Eq1a8e6-+}+UQL!yW(navflRq%{E+OXjEax$){edn|Lx4sXs!Es8}!R_ z0Isg=Q?vQ{dVt9>;7@kijM+!hChGO0H@*k4gbay4ZG0T{hjC- z*1sK^#3um`pQP_k0(d>Ho#8(06?opuyzB;`KMn{Td9Sy(Y$(bTeB_`b1!`reYvTaN z5Z#`Sm(gPzz@M^PQcA((E7nyVI_dJ;M}8sZu+QIOA498AV;{L`61pdY5uriTC>i)5 zb|T*lB+w0F-ti#8L*QFstf0!$g}VC9s+2nhqJ#0w>!9rRr|+@=Gsp;J^Y(Dem1{;_l)iGm}%4g^OaFotpz)u(JA) zpZ)A-D5cO^QI^7?1M__GmtW-6sWXU_5DkAnSYxqOA9`E#dR-m@O;`d_u>u92@nqJ)lWziyt zttqv__IptEP__nJjpTv#jZJRfxyI#>KIX=!Hytks=(54u+6Etg_yJ2xOMLaqUuJG@ zfo`|r>Xpm<m5<~VzjLiTe_3N<5_cO6nSKM$%7DBVRX@a8%gwHnC9`6(X*HfdvyHr(&T5ca%?TGP?rUAfm}- zs}eygHwUUVRJ}H$pw(<(>YB0faUQI1^UlwIPN&o6g_piS(QGj}F-5aEMz7aJHwwx| z$@1zdy>5qZe&g48?&3KX7w>WP@<%K!F7j|?CClAv!a`04O~FHy001BWNklZxkmfj)(^zTJ z>us@ke}%Q4Vq$iN-ozAFHY%FRaQfw!nI4;9e;O3^eL)$K4|%%S?>$XSqkLs8Y9##qC6qs8+V zpW*!Zv)uaZHh=w>e@*Y!eO@^K47yYtICPMixjAO0r_g1Cr%s>d#EFxPPmIy;SA2Hs zHgCWE4zIrQ3dfHgrNYqbciC89V{2=Re!tJY{R8GiBJ^KAVTD$zOB2W|sN-1t!zrkB?{DOb-&wh(DXHHYshHroSPh2!R&iPij9KvE; z5fAz`-G0U5nB~TpqNl}U+!kv+(1MR2^Tq=%poYT#ua)B`BVbJrF^*4-;PNfDN(a~( zu*yZ=)DRu~xYO;Av*x@WE2t;$1*Jp2Ws8W7V4n_0ul<}EUzh|fX(p{-Zv;cqKNEF% z?$Eh4<)XjRW=*t-qO%$w?kj3hevE?1+Aii{%9r1x0A zJ_BvS961mr9J9n%MEAzA&t>gy4UuEPR`FHXgTY9$8^SegBUtOJt&W%F z!yieVuJHbR2!!WS1fOL7hLh87p28-Y5#lUI8s@$AV++yMHhqJ|gD;^267-}mBqf2? z62~jxcN46SN_OvXN4&hY5U<~Tj%(x75Evvz@)`QBZy8;b`1LKx)>I<&@I}IE8;h}v z5>6gF$ydJmRbG4LW%eC7z+Zj;Z@Is^!hiaE|AM1OU7@sJ{Nfj^t*vqP?9+VVr58DK z_B59-U*RV|d4tVOOR2TXpH+1^dFB}Z$?yGBzWk+MrdgI$wc&#gKc?UBa_Q1V%A$d_ zmX9xA=HL9A|2YDg`7vlb*fc5{M??nf~3xHzy1CnFtcwT^JUXTkDJ`>0`K zjn7l=a(yai(!~utL@0_9RTMr4@-~aVxX)+zmstGhGW5Ecy_?O)llaEuew+;a9cI_b zX3x9+#6+HBI|$p5yUuNxq_%?881>$KP2}$$z9%2o?(%$ae0kLLj_u1fg4Xy1_=w0hV!^hT{Ay3$+qoe!xx zeV3TOs-Yn?l%eo>-a;f)sQl;(5J{Aw)`K5j9ws&n&V3Mu;(*!+%#jJ#;NUn9iD1hn zW8I4~Bc$WwHahx=FTL{+fJpDiDQef4aZbKF{^MOP8=NQaDcBH^TNCogiU>+8v=K@r zjMa+LmUOo|JXl=fog3HaY_wTfUZbGkm%jK#7H0PG(OYlxoj?0C?%iGF@QI^LD#gJg z$N1O(-G2`Vg($9naE(9z(?8{-4?m#a@6piCF|@e34SAFZETqFR)NJS5dy9dd&iJ%3 zRLDf(?&igKzQP!4Cmqn@k1HAPpn7K_RPhdut$VYp+H6ddW(`e&Mqwx`n4FyA!s%1o zUE8K-EMwAOwlT*ky6X>GEieJb=3(j&5S=9Dkm-oGF)8cD}+n{Gi_u_GOaXBZA6^yteim@ru@vaEksl>-Y zHn^xGMQy!&ko=zH+y|M2=i%r$hZlyM6%jmwAP*jAL^2!EU<7r{ssEm&eQxYhW zi0_WNh9k~ITLN-FPol02*A6hm>k5_8%Kb=@pP`6{sq1K6hY0YnH6xM?ekIB9)C~R(qJ#jGUn@L;hI;HRCpn_#PNme79ZUAODdP0U02O|5Nq*SZgT??Sae$L|1hM7GH48YXw_+`Pjonj~Z_yd%@;q zp$QSk8D9V{XhP{&Jw4>(4Kh$E<$zD6s7+0;>bXK)XiBY6DxdYeT~7iyB%~mED4C-1 zU&Ixj#sPFUs=b%Tx5kv;>*{%52p03CAIe*VyuHF3SksEA7&+P@j zcpT6d{hJZ**$v#xv7CVM9)4H&rL{jTwW{yi>%`0#goVwk}A z))Y~2FA2t$3P)6}2hBB7XmqKu z)?mcA!Wdd2*r~MxqD_n*PrFQcf?=mL!iI}+X*LYlUd8ga|Bmkds~lTyGx4Bc}PL`aE-37ljm#MH}Ofxccg`LD~6gDbC0UfP}H`IBBV7c3IJ32gF1x(6Z<_YP0_&R{4)uCW zN|(;!btXGg92;-2&o()G<`S>G`Z?5ilaK!LN3`$X=YW~!(1Uem(G-b&= zMK(L=jUK99q52lxvlOd_?$R2iZX(9gDhoEMbySgd`>n-C&ZWrI;BjwlcUWF|z%vN* zGqX%jPhmDX)EIP8FgZDi7=x;7s141s!NXq96@{^is;(FxYjWb`aV|c8k(b8C*x1}~ zJYTzfy<^QWrYEOqZ*Q@5=N_96*7(}jzQ)(T{&mL3Csmgu>A;T3YG%v^4XOdCdaUai-HNjWpqj;=mG1& zjKCwA;bWatuz?wa{{Mezd-E?@j_c0v6Oox!-PQZs@%9b-N}e}fABe;`C-PM@v&)nB#|0Qq(~9q22jKf5FnPfZ{7X+Exp&48P`9= zl95?i-TgFnl6YN}l^N@eyL{uuy~=|iC75HqxfI5z01^d08O*3sa|>V$-s>agLyW~8+>`V9*}INME1O7NE9;%?%d&H4$1LPUSK%LgA{j+##KcEz ziS8>m=i!KS)KNRm8l_x@V_lXh9*|RBw=Bm~;YrHVNL|k>mE8aoM)Of0M@!eXbvn?v zYppW`OYmA;8zNbV2T@BSScC}ZjmnGUCa8+fcd5oGH|IW_QNVbf>m8nW@(C6W?nf)wSYPAY-~KjFKmC-|QIukFago3KyB~7+@FAXf;&BFpAy=mi%?t7docV(S9S8;-W_KTH31ZHpQ?!;Z4dmi z^B#>-Sno-lXq1a(8N6rQ#)u!!p$J29Ys7i|`OR_<{CPXYwgV_ku%U8i%NS!%5X9~B zbnU*`&7UFr@Of3s@5e3lb(L#b z)esZEcA|nSZ^!K$2mFi!*+!qUC3|@g2(#9waphL8$7Sx^)}71VsjW@V)z?k@`)*+SPQNvQ zzZKqH`6w>lx<3hN^L>c8jjNz*I~g9GsY{91l;lcAoA8U90|N`BzmI$X-F> zy~xIKQ#kHT)!0^FSJ(!}o&RvbZoFY1H7cl(V+45<^4dfU<@f5s9n*ZuksKxXaY%d9 zRV65D3TFAtuF8aaxe|0#?QIg90*E)YvHsnIXye-Bf45_jkm|$|ti}e^4791jZeTbD zAJIqXzj&gkvifQt6|wVabud6Dio7uN`iARQmyq^l+H?E)r~l*|OioT=jA46wo2A<~ zSiE_Y?$!o~@blMR=h)GseD$ke;rP*qICuUu>l>Cb-7rx!2TBPfCSYO++( zR>E^nJFa^hsK7>~d{PD7V3$q+L zbO4fs*WY-P|MOda&dpm(PBChjg>iEuP^HzHb3@np(ifWQ%3JI z$Fl@eq!`mjahKw=yBL*ntd(&LFeX8#n#pxT_rqVK(~PtzXk{r!+AZ>YKw)ySR!ZR% z^ub_gJxmSAuznTb}m=GAM9v{B@CU3p}D@@k1M_va7Y0uE= zyX0p;HfWO-kQKs#?VO_%`WpL@Y>?A%^np})0-GH~VMW!`@K zZO)!Q#}iLI$>}p^Idk?b2aX)%_U#pZ@{^yEWtmNsU=bH~YRB}pGFjT9uScyMcUB4oIBKntLM2*VOW@V>=SxO1v zcrPbwfJ;Tsp;BCF7>^X~^*!a<=YH_Az6AMHw0@&P+?d3kP`(sZ>ti?qTUYL*PkxBJ z2a-`LW$dC}hR;&ou%8`osT<@CI+!=*l^Yy8KGEp}7ce@Qq9OK3d9$L^1zo6a zd`3N}>8$l@Hv_uzdrj8?+!5>QBbJ`4<&9SXy}=_bTX7{~R_0T|1SRTc%p3r%k^Zi8!!EtjFQGa}V?E=Re2f%oL&( zk38}SU;gr!dFtt>NHa?_r%#{crI%i$)0yJXp~Dyv&YnBX`yYJ3cmMPEt!JXxP4QzU?Gbr3%u zo2F~zzE<^RJ2EfzQ%)8e9;NM(`lY$NrZ&YkToce0Y!oT&3)}oMu6X=jZtT%=6QZ(# z6ksSSi(?dQasQ}@y2mXZzt|eapk8iKwZDDuY&_bKM6Hx6VUtPK*I3LiwPR29@vmmA zj`_U*K4L33b?*3)RAPQ+y;Pb96;lTxXO0@v!(>LD)P6n z+OchF0$l6AHGx3m`%v;*k?Q#DR!{g&gbPG;Htu;_YvloVvus-oil$lS9D4>MJW3Eov_5M@`*4@VmfQZl-L*TwWzJ244tU$@QO2uM~f^MI7 zT8F)tr5Rabk0yvI(4?5?XovQ(5S2o?%xzu)b&U?)RPl#sr!!{yv49b9M?`GgnU$6M z{P;&dq4n0Um^pZeufP1eJpSY}bbCF1@ejY````N>cke9IP7>x87C7?IL%i_fiyS%n z5NWH0=!A*RB*#vi;NYRd++18n>x9j%4F>rTt!#X?OasGh#PlhO0aveF==MHl#-{5B#ZgB0+ zb)+ze$|x$}oseO7)Qw0m>O~Z-mJOPYGMHY08I}sF_Q#^cA1`ul z>Y`w)RN_!B@qwQg<>=Zt52?f*-x~P9^Ae1*Ko@;$2qeBdAW;csXg$~yNl0=X#%a(m z7@7jX9YGaCLPDYv8_&cak-}h7Wxc?(?MoP7_c>A8Cc|>a2Zp&lQdLMnVWlLAPT`Km zr1q$<=N*N13BppF;KMlMLVMvL7HIcLQVJs&DM(DtFz->!WlYS^kq#7_ch+D~Fg4L( zZeo^VZ5!DtXqgl>w8FWaPSQ#hk_u`fg{d|h{T{>BocvCYw39GQG;L{9Y;0jtL#77I ztt8B}GKL5PtDpYYNbUP8U}BCU3t`*-j1*MIdLl75$G z9)EMI;Sahx>KoH=)vLq`tLo}8f5>F~&74}%ew zZ?Eu!zxyGt{p@E@7__ko8N5Fm2MzzKO1U&7RdamQD`$I9Vq>F%+9{F|g~}P~?q>8{%~*)bkG_;g zeHkm)eZAqg$dj*ra-&j@d)|W`O-6*@t&ihZ1#p6LZsKxuruDd*+5d)cCb&ZNp3gK``6GRpb)>UIf0xMTl27#tOS&b_Z)yPAG$EMv6r%2z*c z<5rDtuR!o_3_4czNk&)2D6Nk*B;xvNhHhQwZUkJSdzE!1Wt*Db@%JSH=Bxei8@Ync zGLlwm$I$WJt?o8A7H=_s@d{H@Q*3Q_$@7BSODlZz>rZL7GVZM0Wov62P^_%1u)e;| zy?ggvJYJ!@y$#B3tfp}H?mezwzs17*Hd|Yp0Bmn>a`oyp78dr|tm(D>jEq<4d$TdU z6POf(#cq#m#9nEPIae9pW~vJG?{o^~LjyI`-#DKQZD|6Nlu|Cvq%M!6`}f^3@j{BI zspI8Fh2Q!x3%+kV0374^so$h7D`=J-R+v|9DX-tC2rQ^PkLO*nSKS!9u-?D0W};Wb z5`Z_#t5U<3ZH!>iU{WDJg0>i1*O@J{?pTx;CUO51^=uz3XHO@bXT!m(|Bpl5r7u=;Qy-t3o5;Cf4QvQWr12d`6XVtt(ea zU*me=`{y%~E3IHCC?%)@$Z?61eIg)V&_5BV001BWNklNJ37w^d{A<_FgA+Yt|cSE{WG&nmGl?u;42+~^@+#?20 z!*k>MH)6;H1!7dOxAPuJQ}-v%5?+FnqsO?FQnXqvrlzLI^PJ7iO{?gP86&q2N*3gf zBp6emQyq*K#4374?xW_}7ptIK_4`iq5{!WwAgfWN4hTsUJp9O6{^U>pHHVI!;kDO) z$;#~|9)Iddj0{;_T_w+RzVel?@P(IN;LPc>OmwCg4EwCF-shtae~nHOPMto(;lqdc z(?9)h`0xJv{}w@U_wGIZ^LPJ-AHVu4#tbaDMS?%qSmZ<2*Y2~mvC8K9Di5DO%NM`+ z1-7@l{OIpr;l&r8=jE?|or$R_Zr;4f;;oysvlJQhF}*IBA|&Im6IkGnmQ?6ODMqQr zj&0$0@Pn~gCeIRaF-$SGXr;)FVW?m|Q+%d}98(3yl8m)9<$l(-UIG*)QAO9iMEIL) zpYYaStTH`2Z{sk!jGkISY7E72$o)J&9(2kUz;eh~0eYmdw2;x>ewBY?OK1cmLg zC;`#7{i0lE*iyZ<{q=GFv>Oi#?ut_7ynsKI7?ffW5RV{MgQuglppXDD*RXCMEH`*-i~`4@kK&h#XgE`H1_Kloej-&?hFsO$!mm;#d~ z^!mbQpL|TJ;QYDsT)TdYciwr2iHR2Tv(s#CZgBVRJwE*4eGVTz!uf|EW^HwiU%mSQ zZ@l?77cX9Bb87>J1C->>CMi{M$qR~nh$IS8h0R=!T?ZyNA!&|Km30z^Cejl`xntKl z3cppAyN#fUdq0-detx~Y1wxETo0mi?Wh!mggx}<81U4x-XZ2|DsIKA-B>PybTvEng zjDRV>@O*olKmn+FYr2M%&v?7B0e}kf)ydo7#p%~-R7dSF7cEFBCIm&%8rL6>bKoA@(1c`^XhnCT@sf-4`H4?=&I{& zj5`UXM|NbC`sT%$hNF@Iiuh;!(bQ56j0^)@dtLuR97QQl{8aR^N*Pu2r8!nvNw-aU zcs$P@n5uXM$NF&$4usB?WtU)Ob!_ZkKWEY4xju-pZP!_1H%&wkqqutQI{(|h{|_YT zw`fnaG1?k5-EJ5D4@E1>g8rQ61>gGC|4ovn7RYowx&&SdmXpY%8v!vrpL2S|kmzJrWRr~8UUA=QYgt8` zH2$6OS9D$=)KRr9N9wPAhBM$%CpMv30o~ z*S{M$*C_v@dy0lJ_4>QhE0lX@Fc>sHAHPbCR3cp{zQ&a?3ZO>|q2amu=h1Q7nSMs# zHPyVn>*Y5CspH3GuU8Xas^727c}; zbAwLd?}-m#Bcz4P)P}Z9RJ>HG#I~yLA~q>a)COk6u#hVAsdu*g?3v4$PVaKiU`J+M+q#9X+xdh;Cqv%IEBf{3C3JC7PC z9I=249`x~@5vPDRiT7Ha4GY&RX9{;@I|W6~!psDn>5ObLlJXUZ4`?i20rCc$&6+Od>&~Ooa`0dn2~K%bk#JVwYk^S4-?hMO zso&Zmmu6B)?+SO|TwmM%?7Mu#>sKD6RrkT<%!>5Z!<$16w`(Vq) zDQ60w)p)3(b$FWDWAn%~+|BQD=_eylskvv^8Ao%`vzoB?`QUl5A%!u74H4=mvpb+W3wepXeZPX z?Z6x3a-JzrHp{eV>z$h@pS2fkoIu-G!3KjAx8Y#~lrrRENV5c51zHc7%nK%SL!Nq% z92H`R#s(VX42MH5e)u8xZrtFVS6-*xn&$e=8*KJA+30TbxBt&ybKvALKKS_8?4O$F z{a1g%%H0(fSC(16bcJ8++t1Ri6$W>=Ne3=Kt8&Nsje&fCC|FzU^7^eM{^3VIrQM!F zCz{^+Chz{}$K3w(3d{F4SX#MFXQs`Gqer>Bc$aIJZ!pXaCQ+m!D3ha#p_^m7``bd8 z=;dT;fa(unDCo>O3+7T-kil=Hg;;z});M}rD2x==!7#U(6kDm?Xs{s)#51CxZ1}Yq z=-jQ}a?RPlTI)*eHihL!u_t^9zBrPNW~eG}<4w9!{3`s>Ua7o0(P}X@J!RW~joG%n zz0HCB``PaIxqt6IY2v)JwW8Cpn~$B&BuSF8zP`c4WSiC1b(?%CYL5>BU-RdW_*3^$I|M`c->Hn->vH-v4PYC0(#jWb!GIKkdaeRi2VBin?-jh!USH*=}Yznd4_)Q}Q~zEE205S+=JmSw^rQ^hl>lphtNg zNqtUzE1@(MB0a!X6rOQvSdgotBpf?0#jssZ)kFfN^am1uIwk)e<~ z&pUt2+3I|jZqhWhRV9uW8K3CNYA{KXhFFC#3~NOvzK;f_24S7sK2`NN<;RnFJ$NQ! zMxS?egoHQl(%1~w1f$b$u&MBDyv9qg+sBAhVI7TZEJeEBPT*+i(XGON{<~H>`duur z8$zpj{oVnZPX97S5Tjk3Rl*om?V)H~cCp!->@*+pXY^UyXAcsWtJ;&l3S%*9027VF zcyP-#-2E;5jmCDzz_kglMf_7m39M~Mtj-7yndV9uw9}kE@*yQ(& z5zk#~n-cV0G*AD`yj{Cm1_xyaiCaUpu?sUHikQo7E?9IxYH?^%%Ug|O9Pj!wP zpNZ<+D{!{cb0s}h=PdB;XbdamSaBI3`MBLpW7ssG|5)H}FSNYdrJb#1<-U`jX0*JW zXm=-|zWz=X?~;;V14;~9tgKH^&gn@}X#cEbwv8)7YPvMh()2tKyLy$ex=~9dK}M@Y z*`z}dY89yj><{94~N2GfxZyf3m<*r(Vh@o;{YITZ-4-m4aD@q#&`J~6_cHroqDp1NE6(+1>=h`k_s4#=LH!eLgJ>7|5GjU$ zA<0rF=Tdk$YYOl_WW5+o<7ZBV8O&hgfy|9hMT<} zZ@%#cZ@u##w{PF3yWJ&A!05;r6+@CF?Ay1GlP6CyIXlDcyLZWwgkwhz^YGczkSOl2 z-e+^GN4wSL;fEjPGGKa}7_GtWLMJuPh5F@{Brn|J`*k5u6?}Fr_;sNxyfB3T5!l@y$IwM8@yCKvsY{-sz=4D%6RRwvib6^NJUy_loML8z(O1rm*2=~c z^1$nUB*JbZIuuOYp-S*YS5OYS7 zQejNIk`7RaDUEs+sc}wNiZ`4*6(~p3E^$PuAXf)fDGOLDMesrt?D4olY1C{+`$U^! zN^|$>8W%S%ux7d(d-5zNjy%FoKlmldbPneZaB%-QZY;k|@A`da=4Q!MN_TCGVUctC zxifIO!@GAbv61xXwR7(F?r?Eqk*5qf^Jq&B!VQGT-)q;0!VJq ziS^{sMxjkcx;-HK)df=1;uIh%Cm}_Ba)a%QE1WJ2s@S6Y(RF592~%3=-Mz$ia+QUw z#axz=ryvXwQJBIw4>#vcTPV_@M(3cqHW2_Ku(nM9;(hkDr`X?0=x*JozqG=vWIWSu zQ)Jcy)`*}6ZIV1e_iZvR4Wvex9l)VPk*xP1H#U>A_X=@4!k#OL2uY$ia^wiJ)3e;Z zeVf($_W@ygdWIuM9zrQ$X>pmsV1S5WYHErD`wuc047qjdrd^}Dnmi`n{294|9T)@_ zUeno0A83;K_3u7Zg;J`Fi7Xa`IDh^uU--gHwAvGFZEbV+{(Y7fm-v_8{3btr?HByu z?|wj8jKo7EMb>)hEC$$|4zpIeJQ@M~4vDs^B${yu%n_G(LT{k{XIF-W|EB=vZse9GR z$tXswpZ{{e2;NzKAZ$Q^XYX5 z8!OvEn7$vz%P+9WQ+(Saa5fBxaq_;D=cxioK~HLoG8A@}Y|@xwh$)nF^!71z1(WBf zK|k0NvHr9Z$WsZs;Knhm3qQ`P>cQyC8V|%dh`hG0W1XmQ>o3m88o*-ZygiKxvBk%& zy|77(ufsKZ$}-V<7HEA-bw-t_l^JE(^HKGo9yMLKK9*O=| z$5&N;uf4TPolQ1$6Yx`)Z4Uy3H;u`-^6G8hD@fe*Zq$G(^^F;eMqGh$BkPN*($j;? z?M~odg(ulN?tUy&M(Zu*;&}Rdkn0}{O8#TZ^9~w*>3O|mT+4SYZwLC1@gi^IdEr>` zcou6nYL~FK>sUd@?Y%f(QrUAdPYpS`KqAe{rMnQ)B1go zO%1E+d(Fb#sL;RDJydwy@)iTB7rs@YRj<5M#$l96j4n16?2zgL*?nN@mA;I_Z>FZ?$N!!!kGgLB#%B!E9r3QvkUz4 z?Y9xFnQdh}cJ3SxKk*pTvj-Ur27LPIr;+=jop=6A~h9EV8yF$cY zYGa{Xxj5^&T%QgUH4X^X{+L*T!Ds0VB@?_e!Bk2s0!qDE|;ry4Mg;v7*-}^cFa+j6s*Fk1b445{G z49(2G1x_D6!L`k08#^>f(M66?cFYnVN7!~ZcrbYCu?Bzx`m`o^Lunnn2?Y=>Bu0^F zMNb>n>Crxv@z~U9&dp?OX9Y#epfZq&guJCGGOGmG4M01FDGE%18XB@5Bz<6Lpl{H* z!W2T5*kk@i2|Cpb$SD#*PbARRmb^`NKj7c|oBx&7)iq+z6u;Kv@BZ?9rNRfqE23@<>|lh9Ga3}7zEaJ= ziu3pqhDFXW&oR=YKj>o$Ls1lT`+W`_I>e*rAHn1U{^Y;@S4>Pzk)jDPRnt{BM*PNGU5Tu3j@EUh_`h+$ z@Lm-9SZ@@^ccbaD_gzvkBeCY<`d#^J!-pt=2~*G+*!5t%EEEa;LMF&ku^#`9&j;mK zoXk+}bb;5^OvF3g=?I(H(0R5i3xFyyAt{(|pz*q&6C@JCQ8-yAMKF}yWR!jNV~Us4 z_CYHH%F`F3AWvEh(v(~&cRHh#e_{kth4or=eGoVA)|*w_My93pB&kQ+rqad=@Qty+ zsJ47nBOva5DNrE;w3X)`2rhw8=Z%P=kQ{{~aYq%VR6<(>$p?1BTTBQaYs6rba^q|8 zo_B7+JN=@Wht&Jf1o}5!O*Fon?I9wS4H17Y2Ct#2v4!v!&LHyhq*duf7_a5hj;L5` zBkMF?zvKE7x6kW3!4eR6g2Am}KjvEEW8XCg6(RD1l~N}`g*cYc2x9dfV$;!;RX$M} zv|yPx3}p0%qNF+7xANiLyJed#is>V6a=`6`9@ee!Z-n z#%N~__5a#XDvA3l?Fc-*>MG|$FllE;rCH|mjK~`F z{5TpnMF^%gc5KE0c`+Wm^RvsViUOVWuWA+@oJ{7$Q{cz0$$+S6E2 z);m-x&Etq-C&Fo3h)n}n|2&TAmiHsV9%E=#4I?*lL8*-oK<$2c#JS$qW`?)J#5Hb*jv*+UyEg8 zDL{uelQivU|IMz?XA3ce#^F6PZEt(#;-7jB(WO^S=M4|VVYK}#m43a-K{P& zGjq($&T{9@9gH!|&CStnXY~7h`u!ojUXRJiN#^F}SYBRcV`F11(+}+mg0A6rWyO&_ zX=gmSg?St=m~}Aztq!hHX2@K7f{Cda);2fT{O~rPPQ1rfXO+pxNp9*b_{lQG{3eG_ z9z-wXw63k9iiAPGhu-dT?xA_s1{rrhy2jv2LhEpcwd`XC7nWJQeTNS>-elMwpbp%l zT_hZym?6=cndw<(Cudk+-C%uvgEY-Z)0BmM3m9X#efu^=p0lv9!2H4jNs_R+xyi=* zI{kh>jFTUuBGC*DY^HEyu+6WpTx5P~0z*ok0ja|1f?+BYZH-K1L4ip-2VO7xF|XGN7d>v_UIHrdue4q9{m=pmRYD719%0 zl9Cz)g;jcn{Q@xtooLJuhJ`T5a&qMY;~N6(ktGQo)k0c1QW%sG(zZsYDfidb*tmX& zRV{3{G&xug&DbauyDy~&In#yWnz?c58ypM2BaeeVVU+ z<*S@Ia~2U{etv=V^$m)mG}tS^o2OZ+&Q(m$-XM%s-#TBZyc5bU*RtPi5OJN7QU)eT z!ra^}B8pdD`7r{VIdg_yug~SnSNP6fe*jNGu)c>s`?Eh|W@3U*-~JWnkDa2ME`4Cs|rP$L-s{xGpGBhy%T~rz_>hNuyFPgzX&UgCROeZM;|4 z1}SXJ#~dLq44YDLqub-N-jLg^gkGB9%}v32aU_XNqEqBn7G+sRtCbTEM~*s<%%S`00T+Xfqn(+YmKah8WGWQeXv8?LewQf49`RRb5imKD2)VML{Zy`a zgcgS<6NdNJ$+`okQ%DP8sNF`Kh!216Xw8e`l6J=d^6|YUnyts`tKY9D;;N64H?%Cs zU0W};n2+v7XO~7ck9$@r_0M}YrfY3zoTDAfsq4Hd zteJ9L8p`(l65pq?z9w3(uZ5l3)FewvFh0_IV{1lC$q$q_XKW+IwNZuN)dRI>w`(t; zQN3>^ZCl%*I=y| z(`ZLdB`y*ed8j5{3C&h+Ig<7=l>DVUe;7UG`7@4#9d^5_aY@uBTW#yWVB)rvda*0Kf-&u#A_N_Y6=Mt}o_EJ%RP&0aO7G*xV1yw@ z<0vD~jFZvz%XmYydDRj4tmiv!e9|-|LQvsBYxLS~m7S2F>@_yNp060_?w=w&w=RC@n~v?ivHd0+LeKc^$HL#7z=|b3`wGGOd+L6 zly<>lt4Bg=`ckF7Ju|K78RKF=<_MxQlJ*p&lb}=P_aET?X3u6;Pf{L#;&HTwPe1yQ zOCP?2F+-q0DZ{=4`#F8)46TVNX4)zH4<96JwXM80h0PFW3d9Hr8d8PQ3AhZp8|xc< z{OKhoCMTGjoJ3548umE(&_NdF<|y)<#pM;2mv7VW^~jPIqJ(a5n{yAJbH}{Po%h`W zwC^}QZk(&*s(dO{#8_a~&Y;_&(V8^T_J|ig)|Xi0py8UGo#Djs<7}+0vAwm$WM`U% z`2{8>JLE>#?rwAc-d(!A9<#Hv%+1YVOu_QD)5C-Em4Qgs4oUjcH8@S} z+1eP6DWd|gj#=Av;1myf)_&W`Soi%5pS>`4)8&=WGR%qu&mNQoi02pPIr7jEilX4~ z;Um2K^2;1Pa+tT?dYkv(eUGP}dXjx2d_({MAOJ~3K~!g-dyYT<^FL?t)*>&y@FGW# z9c6QClkM$oR##V+9^NuGQ1SzdhpMGoyh!ngnOZ+Yvjf8flSlRW?Y^StoVi(J2UjcXI21O)9V?HRx)I79*uw41iYCbwi4l~s9&Jf0~ucmh@Ob82RKK9t-y!`UZY;SL~u&}^mk3Gu8iR% z0w$ZAn{>NfzWn7cbMNjdpM3HOTU(oZQj{y>)xc-t`%0DZ`05~qznZ|EI!}+mo~CvM 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

B$q%B;W8m{I1sV7;D54qd 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 literal 0 HcmV?d00001 diff --git a/event_smart/src/test/java/com/njcn/product/event/EventSmartApplicationTests.java b/event_smart/src/test/java/com/njcn/product/event/EventSmartApplicationTests.java new file mode 100644 index 0000000..310ef47 --- /dev/null +++ b/event_smart/src/test/java/com/njcn/product/event/EventSmartApplicationTests.java @@ -0,0 +1,13 @@ +package com.njcn.product.event; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class EventSmartApplicationTests { + + @Test + void contextLoads() { + } + +}

?wdaw*J0?QvW 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ëY#16ZIgkK|i^f7y*K>he3~FiF}|4nnD)9pBw$L1q9xQHsZm?9ZID zQ_^M5_~b@g$(fQypi`cpv*awrzdO3FV_^C+5(fw4{SMUdr(sB%6Fo8`0Q-Iy{)XE2uA3Wwk5 z^Wv@7uVV8^KM~|?zbqdpZg45e)aOwF=m`$hH-FbqIwacg&*qF|s7^n!) z?6%SYYDHL|O`(V|KR$-rW@B1aP>qIOHBgo}L}U&Wvh;elE*cN1zBqH{4F2R#{{(Nm z@j4Fn53s$xh3|apTiDv(!jmVDas87|@Z59H;qABI#yju4gHxNE_~hej*x%pBi!Z#0 z-+ljgarM<#@$t25_~nPc#C$%-d_KpofBg}dHLhO0iq~Gdiof}rzrkX0jJ36IV{2;* z5AWZ{ojZ4M>Cz>vt*_(y_3PN%*})5!FXPmyQ&?YL!{y7DasU23JlT2L32I7(>ph2r z58SiAa1)X$fB{w2V5M-pT%plj|FopPO8iXukzI@)_AWJL*gVP{mJOy(T)+i6>$;kN z*iJ1ah9>i{B4BfK1DBqE9+j%FTrNO%%CIBS?Mo2JM33lF8Vk%)&U|VaTrR+oa@I>U z`Z{Et(&z$y5C>b}0zGA?D(~!;XU)X zwR|8t=1I((bYJ(b_`J##0uLb~@a?`!-lg}J=-Y@sD2tCC+@q~5a*cBk!W}FcB#@$$*e*4n2r`slJaZ9AU?I48~Y)<#) z;eba6Ek%k>6#W z_4Re^@9!J>?0!^3GE>5wxqd3y!Lb-NrC-OSlgb0o-v>M{D~BP+)FsP!7TR(tg^Nn% zmt=#OeZXkwS)#7)bILIHiq_ezBz-LWEenWUpoL&{n^2t)FM3WY6Mp*_5tbYBsmAg) zcYjEarIN>Y&!X1|kTWv-eV$rfml=x`KEk|A2iG02cX=cI>7SR^L(rRiNXpnW@?v29 zyH5C#pg4S&-%|9u2^7AU3Ed#5Ad-mO0|03oOs#sX_}y>5i%qn6bnh;vRfTuo`38RX zci+cf{nh`D&p*F~mtMGx?|t{XxcJ<2xN-9aKKt}0_IG!?{ssrWP;U@*Q+K|+yC&{< znS}pMqQhdul7Shv-4KqEvRrdKCZC!8|ADVy29Z0i$NW`_aQgIV+_-)nhewBi)>vO# z!@>SOe)ZumasS?ZJb3T`_wL@oOD|l;D=%Nc;lUxEK74?Uy2h$$?ARxeHYaXRYIER@ z)^^W(X3SItY8o5N6ms5aBxf3LF`M92(=<3bKEmzWx6rH_oI86Kr%#{3mtWq+C)ciF zYikQ{z4aE>*Vpm*@e};~gP-Hmn>VmptZ?PZ6@2~eujA4Sm+VR)F`LbB<;oRYy?Pa=PoDu1VQYIEm)aKV>+6`#W_b0LSHQ2lg00i1 z!2n)-=_UN$?|ldV^wWRBlc!J2GI6Pog*+IU==*@3Pd5GNbsre2uEAA>XmacB@*eM(X!2Lb1Z1eOD z0V(;+vlz{orW_y*b&hi=<8n*%%j*o7*?oomGUaT+Tq-%+@qOon%D_d3CPvR8mc@lW zO9O>`-4b-T=d3(g=rGQqbM%Mna=V4t(J#%`jFWOGfV*;5Rh5~sBLJ7b?;F>bZLsY- zWxh-LVEdn$P1)RSLn-JW12pnlTEPdPV3zWBs0_-L$je2lzM=0q+JRv9Q|Tl@f>IzT zr<+paFK77TJTh;I?y*biz~x8=}%{tk`L7@wr@O-xTo_EH#WsGO`Gj%-lZDd>)|__>taN}6-D%e=B3 zZrhf4981~G>DXMm;{FAICr_Sq4)>GKr07k3k9m8NdvWqIJb+tIg8Os0FP9vq0n)Z> z41O(@9hY?yx=Q}QNwIz^c{z!$#oxR77m&8o%`8iSnjBWMzk~*jl7)+%8C4>XIi^k7 zBpk0Hx&JDCKPHXo!<@a7-^bHtnA(-04CZ8a?2DaY$$&X;I@X!)iIAU7;pQcnEYp8l zAIH*U{00)M9@&OAJQG5I)Y($Db=rF&V9CbzvN__@?Dq(e6DWpVLFVi@`EtwzgGrAU zr%!F-+&P8Q8*6yF^8`;HJ-~D4&tY?8-JAumT4ACHn;Yx6aPAzoHrMfxfYyx}`@e6s z)_{mQ2EGVC%5l^GAj`LCav2+bBEkXzqXd@um#Q{s>6rlYveFc0Fb|W(B2uES5|B_QPM{laGE4 zXhzenP_s5GLBP-qv}mDLjd|J9vY%}WZc!5fx`l2Vv&W+5&R*Z9;ltyLY`&eH9sKoQ z{T1GO_g#GbYhQ;te;#-4e1V_+>}PoJ-~nEJ^;NWOi@m))?CtGhePbPOe(h_hK$uRa zSgw}1bN4Q;-?)yucke=LjjF0}e0+@U?QKjZ6U^sxyz#~xc;%H>?6PtNRaMy7+{D`2 z8dgn%rfqTN%o)^m4Q9si@i87hevIASJv42=sou_u{!aM}Lx=lNohjCP+ty>67~6aQ+AQjHy#TlDUAG!-+d#K1n1QM?d!%ZuQ7;z@)O8hx zfgJ@5#^K>He)jWU0K5d#3P1%DZMRwNlwY|FrKCW|DAq9mJHDR-h^0DW2Po&%cW4L& z^y*K2kITq8D;{Jc*!`g(!`ODpXNLInEP$*GO3=F}KVzq2jO>&^U@4%Gqb=?Oz?^cc zl(N&B8I9e0BSoY9ErVR$0g8^}>%OM}la$GHJIZbBmLu~Kb(aA<=OyONpcCPNZkHbC z+&bJL57hvuqbEA;xA`4SskV}9oEAnL-M-B^rg4fLOE#7JvF8G&A5Q+X#&=w9iRXRe4)mGT zFVnTBZQD*Dv!p+BHXx-F^K#(Yoz`1%IjQ<`w3X_W<1$resod13tg}y}bt5QPLvz}D zU-q5;N}bvqw=1SQ)n~E(>(9#S>fJEhEXa@B!RETw#-Zrkb#x@utaR$*)S2bP*5P97 z4hfq2WtCHkztRr4?>(Mb$7?JOC^_Hp^Qr5pv0rLquJ6axWn%#tmN=}x7bjY^Hbljy2c`6tD#aXBS><#?T1 zsy_x_=aNB0{cAn?QqN}WP43#Wq!MmQT{rm|KG>KkBXzyxFC^_;B715ajj6k5FQYcO zfJRK`SCP4)l=ltt{2!B;N5ybLxtZ%qGb&4;@-P0)%(xDAwou*k5jI#T%JvJ$E&d~d z4L(wbfX<9W=U`a-YHn#LSDH2$o|M@Rgk_u7g|ou~q3>aFbw$cdf_d-|`%j+Y#rNLD3-7%PJ)MGxP}dUx3hQ-^-FtWOv!DC} zXxW&;RE?^hfY#QnJejjy4qL{>tu0gxaLZ;dQVRVHm>}l*`V7sg>FkT1N_`AuKAY7F z>uYPc^70kDa^(t?QaC<7#`*K-aq845R8@t^WP+`&EnK{K5!2}m)5*lp&cLc|aPza9 zxPSkC*viG)?)Tn%4}bi}e~gk>sBkU_*|{7^kbQYfP<@ZJGvc+cjiyU%Gnh zx;6?x0ARVlr(3FL_SXw140q+g#p@CQcA}2cOcXCVAjm)fdrKeMRaF&Mt5wly$vIjv zU!_CD02fCXnf`B6gEo}-g6OAQIb&rt#rqt6aUG?y^SU|& zmE*nBV%sfefX00iV}h|ZF>a5W-5xiE$nm$>9a;7(A8webS&n$|jA$Wn=8VyWX=Ya86>r zj$C_E_;pApj-k^%8{dmp4UEyZuAFDV>#3Kaao!X@m+4_SO`Y{yclh?^Q;?GIPW4{1|336YtC`Zp&*-Sk*lqaVnd>_KLgYtH7h_*4b z#Hob4Q{|`fqf@#;jYL2?8;p zCWVU4a&cw$t%4$J&&*NEAO#D*xujAgWvob4?p`9Up{<*-7oK=S}ja4lNoL%s`o|gi(B$DBW)XPFp$Xb z6)-_N#f}e#_XOql#3W@VpShF0-7H#V%QL|#Yu`B>+~dnrrjHz!P?f@@o`}N+ZBS0L z`2*}>EM|KE#b!I2iU_r;Q7NP?fW80#AOJ~3K~w_k7H!ku^>Y_+`L);4Y;U2prB#(e z)vWM%H3wdJ5ww2<(i*B#sOt&C>n2)?07k{Q2{^d-pCr`|LBUudjp1oOtf` z);u~o!g9GpRaMy7*nm>XuLuJSKfq>o>Q_~|r0h$4i+QOsGekAmY(>dwTP1x`sgLCM z)S<(vJ~6n0;AUgC+Ps*ktQ}shR(^sR5GHBW429)t1-L1u+DzkRkPR#tg`z6*r$`M3 zHU^CQP(xx+9Xlzhx=TtwzL!?=`QJHXnlosr`dlEZX_{nSW-bDjRA9hAH()GFV4w@M zyG~S~(I__uUdf>qD=*!?NE`iJ{Z&=@12jg1!U!nwkcBR!RA5K;QQNj+;B;w<9q2Gq z%~R%o_s5EJ)&aMy+&RYzW;+A-CM_rmW!^p^%)kkbWH+cGxZ~-O_uPJ&F%P7yB*(39 zM|Wut+xxBMl`E$r3VT309$P(PF9GC0L@3OS27ig|>k@K%b)uI>fnIiw3HeVcIw63O zb^=S~$A{mfWXt_7l{*%QyXU3uC4&@Q;i23SMlMxviY{hGJS#H?^kaD{>QNzHsrKi1 zJH%(ApfIuyW=_GfG=3mZ1j&(>J>`2oVwwx#>CkyeC@EpHNL4=aMP1PIIFvP!1 z3;d^2-94&ud`j^!Wgk;*Ny)9GU!k%-m02p+X{So#pHBT4YJymCBO>(7ES_0Ck@x?hL!qm`Tx> z)2k_&%pD?@TIQU3$rWRgp?01m4e5YV**SX}f0w^cQpb>dKT8U@tH0l^OgU_rqeo&I zBI^8>W%}Vv@qT#a^2hRTs4X46>WNR=d1s^IcEC?_@jv~3bZia&N&9ZdZsk%Ai8rg8QzUG@+rNaY<{S1Ml$^OjNt#+B&?UQuh9DxlV_Id!;Fs3tY4TKO&O zCbJ2sHnXk>45cclszQwlH4`QbRGOj4I00k>lL`cG8E7@QVd%Dn)~#9QY-dVWN};MP zP9O%cSxMLG2CA;nDh4ZsRokGhDyUkaay($OG};9G*sjFW#$O?gS_9|Lox!O`b9{N6 z9{+d#>o0Kd=u3RFC2Y?&G5P3Y9Jjy4=}O^;r?zqbi!bru(_5HNCpcOyae39?x%F)q zxy4kOK&!TX9TV(!UGNiH+@G6q==`oq0mMLUeImtVXGGfIBPl4w_H=KcVuu$LDzbZE zvh(ZU_x3grrHd(u%&NP(uECnE@5lh`WVTj=wYGsyW%^08z0R0aM!wosW7)Jg)iTzZ za0EijuzOx=sI~=`E9k>HRy(_9IdKI*nTb4_8EwmGRfWf`#@b?T<5t=R(3L;UoM1?t zFXo-IAyT$Tjt!|lnHg1G;lmF<#QiVt0Wgz-CX)#!lL?NFj_~;LW7KsGA~R9x$3Olt z&YwSz-Mu~h!%u#K-Mu{=93Eiz=?+>OTWFdF@4x>(UVr^{93CFx#*G_bW<2-YbC}I$ zAR@f^=9^e77MRUu&|2fIx8A~)D_1~7ICJI%F?JK!E9s&P(=CPw9o9`-~@rxg;@aSRF!sVp!`ZhZ*p@TrPcC<{3LP zb2^=1wQ5XbSUC1XU^agM!oCtwLrR8K0<@g78QN+$v0Lv4@%Ea#}@lTntP2_oY8Ah<5j-QPDdrY+~VyJu&!85WC0M>uok z5s`aY=c#pcx}^cJ;~tljx*yZqGwddL6ljil;l8H;lri{y-uZn|%njcW-Y`lI*jGFfv^z$! zDBHW{%$^^Pme`SChTWK6KA#I93@7LK%l*k#t5tH3*4&uN>9>@#UMe#p%(f@wOBvp{ zUej%2_E*%uC0gRLC5^edQ--soJ7xZ!&4^|sr<~rn-jdFc-%C7*ottFvhPiVurE61s zis>A3Q1UbaK)(%A4pVwQo;?-IZLYo)D9YKQlnqPqFaC`4D6jLVM{iE9;(RGRm$OOn zXYU@!>Kb#T4p|+{=(jb6F;e{UGaHPe?gT1wvTU^@KyFVfglm>gH(SL$B%;~(CZfWoP_OaWJ zA$=1+Thb3>${MmWskX;`QqucFbq?vPI8S^n9xE~bb7jQ+laiMlEm=BEhT^lb?o!I} zXehtbGmfUt?~L608RvMX2C)~qZ9GXj$#|2`oU|`fJSbsN z2oFZ)RZ3xfeH}+fM}x8o0K^(IFy7lbkKeDhz{>@c-NOzFm`o>Vp|Myk!OU15FQGQ3 z=5S#}&{zR=3*Bnywza-MYrezG7+kCxtJf5!^%TSktHlD~aC&je8Y^bB%utgFR7GeS zvq$1;xkL-biZxcO(dZVW6c%U!Vzk77ZUHti1l^k1n9L@ysg^68Q8mt;KZ_SPH}ReI z7xC9v^>}gi=oqi;>|%Y@LLKiwO(vMsGoS*NTI2H8Y1}PtEx71+wtDEX*`(2`EuN7t+6&+!*aRA@qCWc+uOKs;R2q2{&`HNQ(U`t z4cD$+!{MPBJ8NrezPzSsuv{*n+ZJ1=PGNiV6n1xa@$k_jy!g^f`0jVVi!*1=;MT2M z`26$F(KHP{`|L9)8#Ff0FR@%M@!WII;nJl`SYKZU0DSr7m-x+ZeuGDk9%0q2Y>X|m zv4o9Mc!G({_6!8`qChDy%p|;eGJ$GLFt~GOOjKr!!N+1DIbl&^8Xw3cBUD6Y$-4_o zyYlO*GCW(G&Cs;1!N>?8o2W9vX@y_gsjN~RrW%m~VYx;~p^<`>ax!Edc`e1%)b$vk zm%!6WfOpC|xpezdKvdRYo>zXh zYN;K)KGkdvijnUKXdHk~IdsMugPmC@X0Z@;mNh%RbPv%9WrR1Ivfb`J{B@Iu%%;T3 z4Pf_-x#!A`XVu7!bKUh&k8F3CF-x`&iSmbdbm{Lp!eX#{xp3~ZQv+F_Hjg9kDL)5`hhyKn6rnnjd7kZ7F;H~?496o5_3Fb)Afsv+Ar#| zZ+YeKM+QVYc8OtE3-s$7N*&CR z(qj?u&e0y16<^Qw|4?EM4j?-HF^sjIHKoRrOQ{#^CD~4m;SxqJ@ii_#_nhBSrkg3z z(sD}ClWJ!P?~S>isyA20*p!k%`=ui}V`+-ZI%%7R%E{daA`9*8itGn}D++?6FM9@8 zhv(RqnM*Ec70Y;zze8;b%}9^e*gqgaGjq9~+_m`oD4u70qTHDND#?{0_Y)k#4+?~u zTU=hNaA8v6`WJVxR$8NJ8)#-U0JtVJ$^_yX)@T)D#f%1wR>4nF(E{@_ z03x#=p;G3M;zq9EQzl5(=oUN|J{J*Mt+8q~S|YzsCu_5RVawVc{zKNcu!oE27Q`Ag z0TXWVIx5h#!Zv0&znlPxVCDu9Hs(ugwhdPEV@z9sYDTLY%%&4)RKR=ztq4tBp_w0< zmw;v}Bo^}}h*UVhhR6mKO|X{XW+U2}cSKk*W6`!~6|iDs_uZi_Ccj3@?8^ZgTw!Lm z**+#R!AWgqq-vPuyqeiA;|4T^m4(b2Eo-a8EdB>WTmBk6B=HR!~IWC>q> z@-ft%TUaQC*2z5siZ%Fn4qh#wnW4$Zgr-2ZCPJeW_7!11sqygm5W9;bsLI5wl&ZkZ zQutdAH+6lk(s*#GZCkwc)?4_~Klx*vJ9o}()n-?x?(Oa2rI%j9rAwDEo6W#lqpE8^ zd-CGNi};g2`BM;p$#jat!$bW2-~AnS_V)1lYp-E_eH~AqKE=l$e~f$g?qRW5;Mc$Y zHEvwLj;gK!m?hO)TU+?ikA8%WjSXn6ar5R){L@eW3BUdAZ*h2d==GzU(8ZQd9xJAN zJ#_tO?}0D?QdqS{qDWN$6k5H4sua?5E7N?G`pd@?iu{f-IJ{gr3f+V-GZWUxs;W%6 zwW=y3^eOz|HXeW*_CKJ?kjiaNZWVyNE!x*E|b%aiC zh~l=&GUI!)yfI~eRe*ewa{G>q6aNrobB?CVC;xIO#am|X9H21@P{&{cL3UGol|hgs zui2S^ZhP#l9_`YB?(;3>Y=n|hwh23^`pJ z_#^Ad9n9lSOpJANtjk;-P6}e(G32zx?M~?!`8$@=R9Pumi^q}VUA&yS+h+HdFEgei zW&4Q;1hdpC=7;!Y`{(1BkuX2V~_WW3oqY+y_WBK-8CdCRxtF?;(0l z+McocE`DaYT=weA)l38^W=tx^u|C4>;~m^q6Fi+&SSb_m;2LImFf>S2X8SraGx=D- z1gXdb(O4PVYJo7E=_8NJ6-WULg+`lMd<-)~v#x5iTA^uIAh5U3%mkj)08xlr+U2DIhy1CKCn#yYXv%9fYl1iSK9v2*n*yBtm5%v-2nR4i2G@ zj=-uiL1|_7#8hnl+=L9a0a6cth|rQkqZC@DpeGX?ESFGqZGu|XZg>52+gOLA1=g3J zadH@Hxm@DO(;YNTi}~UhPaZwO{reBFG+kv-TV1yf?oM%ciW4L_6ez`8DDDo$g1cKO z?ohlyDems>?(Xg${O0{;?w?HlWX@#f?6ucgkNH)`q!@S;Z+O3_gWB4ncJgRB{h{1# z=qLn>ywdbIGyd=}RjU3UQ~Unmp{l7V9E$1~ySWjLnyVd7ZBhTL28QyQROxP~biB}? zi#5g!nf@A3rudKk3Cfz_GGH$oMfSFR?EP{Gha0dq&W38Cfo(KbFPb_8AaJ#6s4-sk zai64QIpQ)G=n8n*WjDy02jihE+FM&XFm@m3EuYO%Et`Ijg*!-(hG7RF{v#1-&^cwswFEtnZ?MTY2wnV*%)S*@Y}kc=_t^!$T} z$F&?L=N^gK!Wq_sh8mOuXC{~#m&e-@o5E$nn z!dpk?@LoswZBJ8VXc2kOs+U6i@Mdwz)z*bfRv^qn#C6(G`h{$&2F6cD{zwap;G3&? zDA|W0M_B z#r=FB4SdQ93C$H|w4>FDG#eTl@`>)yU*Fu%x#O~slZlGUPJn0J5u|H#Flc%<873<` zaT4tGcM-=)9tHjL_fp+_ORV~C{+CK?-?}Bbq!{Hl%IH27F+*xWnbdwlW|WI)=+Zd4 zDXjG`{G7;+%3Z{S@TCR&@-bY~;)Tlg`q0O2))QWixiupObdz6-ow%6UahJ9(okjV) z>R&ZsHjBIUYdX*Nk&6uXQ)ag<#<=*>34S5lw!kKQS}Dzm30V7m=}~YM>@Q%rpp0$E z6<*w2vcGz5N$C#EgfXxF`8P0hD-A_dYi*m>nINpuyoggWW;N$>jbwK2Ji| zP8)8C70d6xDsg=a?DM|(7{CoZ%c=c`sAP%y{J`9x?l!LKghHyd^X|8oT=n7AU0DUh5`;l>kG@UNH`PKS3BvwQwq9FjkJ?vz^ zAub)TP3Kcxo*;0TxJKEWZ(w{G3yZy$mT5E+L3a7`>x%yf`bWXO)Mi+j_AFV z_l-&VSU!9XBdiOjvA^N{n&N{Hcdp=!G0dQ3wLpZSFFyD^U;}Rg{_?czuUywy^Y>q0=NNMGjX0fVeq;!E zVQ##?VSQC*fDDPf!7FEp_8GkG85rBzl2g8%!a=nR?`YA{`=XURX|wBvob$`uAF#c} zRhgOifV8|ltTTu(#of4(J+eugaNrcu7mYPzSl~@nLI0@8cXO;B0l2U`cqeH~?=NGc zO6aa(-*D0i)5TM*7*APP*A!gqqPV;0*si1tO}17JF)@vo2FRx{clwlRt~mUNx1?E5 zC4`O43;mhMbyt%NaI>Q?@XtBZkMi;LDLR+*QQs)w%(aAYv}tN9M{;@d${9g+yx*k_ z&5|e4SoWI|5-ihfX4TFb+QB|zSr>D{Lp|+fT0DthJRg~?TV+* zzWN_-qLRP70h+-nvsrGLIl#&B7%qd+DE+$Do9BOm=wVsS!#teezE{%`1Ey5sX7%zP zW=dfT%cqPwaRQohN_O$B@yurYdl&fiUzT*jtV)Ji+ptnwP6!v|)O5lw&U@78ZQ{%|D$9-vj54)3x5k}UW~ZPgNwhShJrD0}+nhw=)#{D>=3Snl=GDTIykfE#xKtRO#x1j%VlVrRs1Ig3$&Oa$^o@ zYnT^cp242sD^|1a1-NB*92w>w@6yIsZ`njOZDP6B2+sO6Af&yCQ>B&V6r7r@hKgc6 zJ(y7Jf@O#Q(vKw8_dvo#mW@~O{ZgEJt&nf>-Q$mUa;tW-ru`=4n zgd$Rt5-8TvQqS{|iUn`vwAm`IDn2A5<~9QZ%Pp^>xD zm7Iy?nETaU3V3JV<)vriUF81$A?JIDtey#X+V&X#MlY5xL=w8+J3T%B-@a}O%MY1z zk`E&~Ci?P;)ZVa8cl$pg!+%0r^Em+D3sNVm!9J%%NYD6hD$>fLoE&ZOictxvBIg|5 zbKnZ`ILSZ!M1igW_>IL;9-A)XA)bKKE=dK3Mj4y^P;dbifpf5LrT?cEs=_{kO#eTp z?4&JM(hd(1SOPNK=Y>L`jImS6071+~7*~PDgkN9Ae`0t4AY+w%rzgSjERhI~c1Sm?OXY+zLjpyRN)3-wZIkx| zDvhHslZeiCm#nH>s>*HJMiYI6_++G>ev9m;Ze0Pq{O#=cIWb=kHpV?i=rJM)L10~TZF+@;X|AHk*Wqr+UzZ)3$|D<2v7OvM zO%`7-$g+inpVw7y_^!g^#v+P3Eyxd<&wUo<J$T@+4f9F?!WwE?lcI$ zdD+@`-M77EyoajYqgZ3*0Qq0;OebHiN_VLSc!ad}VLUx;ofIM>trVQt1zwG>WfRSm zf|aIlQYcM71(t2)h-YQJ+27b`M~6PG7tHEMNjf)rv;Rx0kvtM4|MxS<;??j=DXm=X zt_=_H8LnEp>Cv~4^-%=yp4bWnO6_Ihf2_ow4xdn0q<=GdTGmtALVGCs5)x!kd@j#i zXaJ{oAMDiI+@tzq*i1GZmU${Gaa8I$#SyG}E1z{2G1}qHBP&5+nK)C`_9TG%6kj*K zt3UNm(BS{M008GM*8vS;F8!>#H(5O3v$zcyHV6ad1-Zs3E2%slg9vChFfY)%k?}UV z6slm}IhK#(xQAJ-vR>ZkW!2n&c%6_>O{UVU(VfSg1bI_i0rrpo?f>_z|Hmt`{Yzc; zPq4Rhq`5VMtpTFNm2=<=nYrv(=Yz}dUo(sBc>@EpLdPC~eSdm@rt)vr%u!?M(^%r} z0>_tPUwxNIPorPXdNk`-g%!=`{-zTn=TtM=3yCP3DrX}CYQlF|D%t|XUamTNj(~++ zS~>&ow_@)cR5VZ;G$vA*jQdpxAy=j%R2xZY=w(fM(f+_bZU72XhVJkC_Yr+BUn!yb z#*{PBphEE5iwAV@eCdKN$WF4o165DbEq{Mt*LTOX_j^5Yf6lbevCULQsXhN97Qgy^Ya2|l3BKFUoqG?0ikT z9z^(q0_OO;LNJ?(;?F)iD0mt&tn?N;Eg$NX;So-0!Y`|XIzxXI^noSDirD-UQCgC; znjR_HAW5ByX0}3?qRBG0QB#J&p|j^l%GfNA3tzeAH9nj!4M&$dTbD5#C3wf4B@4vI zFCbc0G1dEqgoDM{Ci?WNH*1F=)&2_jZnG@tvSGE|hxF!gRVe7ZqWB|^V$hOJ=wGXs zT78WNA~*x?a}$88Tx^&eHS>bqF9DtKI zy<)~73l-Bo2fnOG16SaHO0AG(cFxGTMXg6+vJ8=VY2PkwnyJr6EKT97S`yapJ=oIt zd5E?g-*&6xNa**kIe3-eGc*71Zb#O-${*`amB#AyY(^6HG@nmK9Be0G?EO#i9f2jY zTNuXI{^CPR3bMlh+%wbQs>A!_CD9s;`AZ;ofn6+5K_GI$cSq0d>j79jF)y^WE9?d0 z862d6PaSRp$xf&$T^LdqPrJZTb2MBX6WB^kZ0UI>r_pTeuehdN0f}#J{QWAg-{$+a zh_zw>#tzT809qHeB*q3A$6~S~nart&algMYl1-Ql{jU??nsim3{gDyBl-FdZ=v~t9 z&)*5Y6vXdH3*T>_oa=kH>B^9`qTBncP9T!uWJLv}+mpg_npUVr`QCY<2@D`$0;}}C z8NMWOp3EDwAiZg7(uHTYs_yis?gm)Pci0LjOv|5w4J>Cif#Fio4y2!Q}mj! zqqT`hv#f@_dNYwSOB=6n9;S#!eWrPOJ8Z!0-;`c-6RBA@o>8s77(3byUD^FRlUzt8 zj-e2N+Hf#@+3i;t$m)sqt1jf1sqqL#{dYoQF`m)2wM7kuxBs-MGxSE`W}2AoYr#cx zMMn&bVq=9lchwyJT$Eu?nIre&v%nfLE26@ybt0-WBMSNqcwzvL2watQbxcJ>v?$SHGebyi#ausU8YCB1LcHw?McHlfN& z(M`zQ@kLqve9d2U;qp7ap3%DNa`yYY^zZr^$vPfXMN>k7yIBB@me>J zoDtZc|J*VRc8|{w0D-(=_8Kw1IY7&yt*kGfg{Gq z2CHmfl+`_C_S^E}V(0uk0n~a4s))zto2!F)|7xDKPMxETT2BWD%PF^D`&HMGq`JDq z#t$Oct-xBy{}ZP8JNM5c%r%D^H9hpuB~~rtG0WwC_6E)uHyGH=Sg{N6GN0wXlE2#& z50qxsX{}+QVlrnJGLtIhs0 z5f=*TFIgmF%+G3a0y?n?G489)c$<=(WzT`si5C#!%^dm9>ucBhMk}h05EX8*@!X3W zXY_yJL5&WNSM^xeUaQf$4va^U7eiGv7FZw>5fRhf`V|&}vL7+pukKeCipy8B=#_}t zWx4o(V>?P?y1r*F^CbD*#j*N2oitqmWx#tlRlhIIhsjHmeo)f)jFSq{k zv)cZ|)wYDrp=gpG$g!2ST+aKK z%QX;pu27{pr|D~z+97XxZPf)8oo;l-c-DHp;b~1n(2}i(uDAUhpJ5g(m7jm6ho6LH zI)Tp03drdm-EOTpBn*pk8>*xXod=ex%L{u$u%VFtzmI0HOHf`G%h!UJA5%~f!_7?k zzBE}ebTUuUTvo1ZnezEROQ>W~dEWunKZMFBp8%idMg z70O5d06Dew(Akl_Ze<+)Gp@p)Lq3_ShOUeoaTaz|=*X%vCB@g|*@BlyQ%>9k{uwRj z!{H&twky#%V;7fbhi((3O* z4{u!iVcBYn3C{r;WX-twMBsr6*=@pJKA;o@xzSMcS#?d9xac}HNRoZe-rgR|cEy-4 zeaVMHd)8&mdgQ%ly+u^Vk<87wqx9VAybTz{FAkeS6-V_Y4wa)oGrp?!?y}s5xm9Jc z`dxAa=WQDdB2#Ayye@C6mY$)uXZob8Lc{!(PCO7EF_n1qMs)nB{5Gt(bhvoD_*`i7 zDWYMpOJq$irnJ<1LH>9S6mOf^jODsQ-(C>CbCtTRU0PfmDLGcCZAh09O34e&w>oZ9 z*7-Kz&?_!66`MDU)zqYKC`5+oAulZL5kSVd3BuXc#SE=e594ym?b~De1$CUS<-K{K213lx=@!c*m>p*2865C6# z+i;6?_zD6AiFe)&;EG{+7RYL%>W613RHg{sg#-E~`woW3! zp{hHb<0eaQ$yqjAmHT>ng?$&+cXL)OwmN6{o_W;3ZW*dl5_p2yIk*J>oWb2c8LikK z?{7nhJ7&?AAJyxS`IYjLcgwp$29~Wn&izas$LkbkmwQH5XX#Pfij0_Id z1H;FbN`0#y1J2%;?TT@Cd%^Zh;KoM4%n|)FhwXQdXoBMeH&XpF)?x@-f(Z-fS_SnS zlHF5H(hW3W7y`l`Q3bxZYz#NROk z6yU*GpyiJ;$F-H;ObkST=A4#bkdRPc(gPJ@zfq?h5g^;vIPSSF>Q2_#v0g+{uZv&B z78J4T+Uc>)+v#M#`w>`dnQq_!iZ6$*S>ZC8&7fZ1R&{`$A?}i0{Z^qP+`M zLRtjOY4&yca#q3{B{p9z#P}s)!oqiDtZ_tN2;eWb3^Od_1X)u>e3>jiOE9s2Uc1Ij^I4-6wCR-6lY=N8l~3X7kkZnm_yUgIs1RPu3d2 zOvEnvWo{x_fl#YqAHM#na8FkIEE7|0A_1t<(B4}ZS}>PQLRsGvT^nEVmEk%)@{P8< zwfK^ndB`o;-vzAj1iav_u&Jg+{VH%h5dXK7TV3w*n`Rm#YnpS}aZo*wHSO#Bdc5|% zW{DWXDfLc2V$j&1AXsZ2Ht=lgx0$Wqr*RhYcACw`Fi(iYb3dGN4VX?6>O2W9>GVST zq@X;zO2&!o{^Ue&5v}=>FsFdszj2-h4;3Rbq=t1iiOHB&Nf&!oON!d-I0zJ)gL1YL z5v;K&6e_M119Cs(GbYVaK-#F=hj!u#Mr9qI)qS4&31Tj7cd*!`_b2ot^_D~RiZ6jA z*1lCV^TBYUt8Zw2iLi5i;g{3$99uWp@4`@jhBx@a!ovTp9FednHj^~;yZ*CP zdG(F?PfqEu6)@m+(H3^$`_S?~%1uNBn)}3m_}$wk)*HaS{gL56wdHD~1Du70MWfF% z-@5O!t^38*<(4F%^Q599y5ePD;}Yup0!^&n=D4iMel7LDG47YjF?!6V^%oA}U^KL& zH8G7zAaD9{pB+O)T|!WQrBOS1lagT;M?E_Z%7rnZEB9{i)qLV8Ds~ zp@rNoD|0&$x#4|lV6z8@9Z@5kXjivU35NRIWA);)Qy?S0$2oP2BW97dbQx+C@b&bg zdZXqj9erxOZB)IAt;OMoqxh$U29LSQ`wA63lu_;+&H3DA?Kt4482mdG5HMj{?!&>p9bGBhN83z_Jk-l{xE-T2h zNbGVduE<=`aY=VTTgYQX$K7?96X)|ZkR;?=fEJ=xIjdX!WGhw;^}m!XS^Ya)K+T%>Svhls>F5FQhZ5rm_&E|Gl3~eQlG&v_WN2hCQk#RJkiJ)$oySQZn1Cmof?FVQbU9ctXGP8t18 zd7eyn;qcXIh+b#*Zm!#SxNPNY@@qnv zO(mphi^iEEW(Yjhq)YT*t8I3dg_ggrPiC_buY0+z84ePVG><>)x7j}a_4~Zk$24wZ z|A90NCK83}Ln93?FTU$C*A9!pBWn9ql>KI=+jHkjk|9-r?3;+BodK5k~BL9-^chj+>v++cb z;RKmZ>4?m3y}^U#3fw@R7CNG#4sL+|%$yP!m78B>FJ)>4K#^ISSQMu{_2w;$@WwH5 zD3JmgM~5gx-Kbraj=aYF8^40_-wUySI!A&_wSws%SNk{}8x#3Rt`pl?9_a*vw(#nI zMo{O&5d#o5R zA9~|0*%pQ^4$fZ>%z>V&?#Sx3K1s;#Z_Owr872F0w!2q3f2~jBw(Ik*GwMFn&A-y} z+IG6D5-6+~V|Nq0_hAP0wxh>rh4w+qf= zW(WL~Hz2l3(vH<&Cw^_q&&2qKDR`)2@O$#u{Z;Q+kPI{8re7QJf`qFM-~L4F*tYGY zuBi`nN(@KipKQBQNksE<&2wZs^~4kFK6_!#HhQ@mYx3o?h4(*!NsF+nvJe zN^ow?Xahsb8o+>Q)S`%nXl328u@ge5-+6SzOd;z16UwP9C|e{MC%IqtnAy;=t;>iO z{F;)vyLtPox+V~*PP{6(AlqWpS6xcx5@?d;Be-VEW~lP6_-iigTh-!i;rpweJJ$0Ca)q5WDFNcEHr54s~|)_(*J40NziQ*`ImZI9Li?gV#QVHd5xCU zhG538Mg+i@yk+^A)u}cRQqnnv_AnV#)Y=XU2dP^N+9g?cwZ@K+1x|cVs#8Hl{OP7q z;F9M%WWc-buo(3mldSK#9*ky)B52riG|M9QnwFUsC3I9 zpUdXmG=}X_c$Ps`f!}pYs9RK+j%`_;-s14o8{keOO!Y}QWIL9-Kc9{U^QFPgty;RJw+Uw5$%BChdqN585qd(?J03}2 zAOm8EI9pinmsk#tj<_7rf1E!b&&_=pA6J5LUv2Xu#G(-H6rKQ4#JC?7q+H7Q-O4~t zEyNn5amCw5Takucy|`d~0abhl z%@2)~ZbmGP4Gxc?lvf4Jy1$P5Mf%q;Wk|r!?jcyOI4Og7jPk!dE#d!s3qaWW7srC$8eh06Q#%4b!8+WWq4!QuMUsWEU+ z#!$KQay9ZG+lwG(y+g0G6YZ@yW+gYk347fByo#~A&l_Dcua(3p>j3{2uQkP)!jIvx z0Fw0Ng*NXKY1W?AUVf8mo180&hbf=J=Ip;40Y4-zY|W!WBjY_Gs;ucAnqvE6TtlN` z1h;=Cs9I{&0u>sHN|G=FPG4zK1?4(m;Re(Y$zlo7Ek9{%r|`sLSdJW}xIIwGyY+INxM3v%{OKE^Gg%rtL&uOj(pD`nFdN6GEgqX z7~}~4=5-)Ij$e6?9>Iy(^vX*c#iUz%ds;KW0B1!MYRc(xadj1h)Y#gpaA14bUWfih z;zoj{la$ulkip!!{)=vcpzNfz;EKXZqH{ZE*4pEv&ca<}#TQ&XgF{A%@tYD2_aDFU zv{B3o#B%p9`8@>C1m+SFL<;l98_XLQSRQIK+A;kWE_C)PSRzhDlOgq2@n#*jA}?ro zQMSK7x`XPiGbPK6PNphduEdo)h92hy>(p&@nhR32d3ujO6OQ%pdWRd-uB~i}*WH>5-;$z+(BqX49W#zN+D6n_VTIGEHM8D0iN>uor#y{!f7Z%g0Gt#bD% z<)hhErU^+xRSxE>eaK`YcPz7f7#?--B<~vQc3Y%ZJTqW=WnW#vdA${vd2Ag&kbWp4 zYH`{najCbcw<)LB!z-5Z$-REamfaeu{Bq*zX=1e3Fzo`a7lX%IAs2uZ(>0{8RA2$hsuBfdWM}WZypTbQGdz^%yZN7$LHk{q{r?;jDPlk( zB7OGn_x3x`KAaa_QuALg87y&F4-TcTJJNssJ5;B;YenN%SD2 zc~sJ(`l7+F)I|Z(C9mvQ-MO0DdG_r#prqJ57vF5G20LT+*!6Dq<2w-#ZP~2u^mA^H zVi2{4?NqkdtYT1G_H+(`KKpL-UC%pE0XzD{I|Y5APt zfbQP2Q!X#ca}ah5Zg3_k7bYfclcsAwPZ*l^lE7%qz^MiLGx;j0I|_4Mx+0+0;)CTZKbu=9OK6`~V3wVs#L zml(rT<_aF)+8;}Yeu6>%sSYj<0nkR&`*Z@+b$pl*ZF)bxz5K0}AKZ&?sp zU7p_NZy#>Xb@q}^|4#@|@uj*+&uaWuGvH)0CuAKIZOQd{%MD^d+|`_opg^X<`Art9 zs}qCp>9D!y!UiB_TWzQ%&l^<%oxNAKZEBBK_ZziK(smC+FO>o(JfEv=8va^NcyuHZ z^4iXxA^RN-#R-b97gx+#w9(WgJA9&a+&N%GsUNIjEug56IRr}q!fLwGVE*Z}W@54M z#Ao5T3)t9RW7Bfk(9Yb4&%mZ`?_x>o5e3D-A%H}PvZT!S9gRYYkfs2iH4DBWnMzU( z#>-US#S=Ez=+p=CCd;?hcPt2w*~H}5&zOZZCDIC5ek2^YGqfkKP&rt5opFt2Pcg0P z{OH1@Nb=cE3sE{+vpD{QSJ-96nseoBsa0dA78>nRRmPe=5ZLj7)oMtvcp-!5)Im|l zy6Aj@py_*$)sbn5o!`%5@ zJ3~Oubl<3nK54@8UdAqtck`#*mjy%1yUTkXn(oYehm77QyqwVuK<&Iw!K6fXVrw%)(=uj`YzMBbA zB;#(oML1-_5KOzyo)RR;#oLM)f&jEGb^P>ur`nII*~?tpoZDtBHdmKcPZmp zVg?K*IKVqB9`!ro{kWA6Yb@qoMC%--cq1}q-6aziP(bGL@99H`K8Sqv4Q7nl5jOfY zcXhhuQ`u)OHh`u_mSVm=zpDYW?o zKE~!^6yBY`Q^W}^#-rcwUF=PH5jwzagW<)Am~7I;^z}OxfvaP{Sgg*cH0<&;)P|l3 z%tj)_N#EW^QZB`T*a@7_HOv+x!Yte&s#;A^D5ZVt;97Jp>*WMMn&+H-4y z=i42+E6(BWQAXO59&u2uu64PIz}^AX<| z6Om%ZQ_*9KV`D$nI~;VY96;|9U44D_`ri%lpifr*v(J&KcWTIY7s*wNz*Dcgg>luF zW=N)eb{%ru^m7)Bb`P!8*IBE@#qG!=PtVZ0b6T9FIqOyu5v>v@`Q;y$I<`qWc8Ssi zpVjh`sGIFTwd-YF6*oFyor8aM$IQX<-^CHgj9KM3b{x;1n2=FSzxW)jR{ta0ZL2FT zA!sctv*kU#LuE@j#?Ysa2qCe45M8yqhob4N*ViK;A(1|)S!&>En#BvWWtJ~#*vm0o zn*Xy>zbkvv;{txgO(NR~-@lw1#4Y#7ON{rQz9K&vxz!$z-lK4*@Y%F-Fj| z|M(-uPHxwX&QsH#P6yTpED^IG0-QP-u_RfZUgj*7IoW2<$~1)f<-Rro26qk1RTYds zoJBJ|$8%ov%afVlRLkZ@2v+&*o@*1-@2ZqBosRvy??;bZl_CnQ$&g2P$uiJZCVGZp zk92O0V_o7k_Ug*RBZ_7`QP}KHxGDzx%_2w~w5fj-G*dRW6WFmex#?vcA;{PI#@Sm3 zAkW||8EV`^)`M*e&dGp>JuL_Z1ljk)3)MjXsreQH{S}E=vrG~(5mEc@8BR6py2)z4 za<~)i<@k%Dv(~#`8Y_s&+!dic&2)9_*8CgwxeeDzraGM=)4p~~glqV{L@K5q`G_vo zU}twv_!RhlA5DVGkWOkBQEEP=s8>VW&`KA^X8xM=O+=Z{bSo=6a}*1)ObpI<~0xWY~H#!I7W!i2H(< z#Mq=0f<*PVlhx$L8Pc{016-R@;oMUk|FL^zzDlaRVuflNc_sBlHql=MttD*1pZ5ck2 zAHVI;Wy4tsnCU>wPJ~LZtT#Nn((As*_=t$I9W;#7wid?ZPu^}D``eNsSu$q_iC>R% zTv{CouzcOu%a&f37VgTFY3^2Pw}3>roImA zBD0b@8$o%B+T$|Su>rQ%x_>M_!F7jqE52AU)_Y!j`@hfXKGe@^4sY-w z2^%l6DE$>cOy$Z{hTI#x(fdN${KAZgph0J}y83#d;e3dzpw0cVba%)m^&$9!n;(uj z<+xm8Qj+qy_s@>D8|rp`Oyn+3CA31&@~p7(BJsfebAciuaoM@d3lcEuJ%VNN(_2H6_LKQh(311ym z34Rn`o$ty0;pc-9QZgjH+Kf_s^@%)h_$v7vUWl&qV0s&veT*!j=on1QzUogA287_! za5$$s^y)#BNqi73sW`WrLWW>-cMrI5+@*fv#2L#)XNds!hju!F{jZoU)TiGC$QU6| zRev-QcK`M1DdPQAYGsVr8_^9<7Y61IXW*a_1sMaZyR$#7uCBg+D2ySdMJ9L9K5+rP z*0acw8igw@-WhrcI|dmKS4sYBIV=piH0@%Qh`b4Ru>=>s|)V+OJPu8ScfC7}+z zOHrgBVh}p&7-Q!5(#K1d=c`5b;}8;eK%o{F&_3n^0kaxsWR1p1YCz}eHVFBv<6Ilw zu!Wf>M^x!-T8TXZ}5e?TXPlOMMEFWl%e-1H5yA_t