From 58580ca11ecb5d38d2b31f09ae438649734845d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E4=B8=99?= <10604541+sanbing-os@user.noreply.gitee.com> Date: Tue, 9 Sep 2025 08:23:59 +0000 Subject: [PATCH] =?UTF-8?q?!45=20!44=20comment=20*=20!44=20comment=20*=20!?= =?UTF-8?q?39=20=E6=B7=BB=E5=8A=A0=E4=B8=8B=E8=A1=8C=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=89=93=E5=8D=B0=20*=20!36=20=E6=89=A9=E5=B1=95=E8=AE=A1?= =?UTF-8?q?=E4=BB=B7=E9=A2=86=E5=9F=9F=E6=A8=A1=E5=9E=8B=20*=20!35=20webui?= =?UTF-8?q?=20=E5=88=9D=E6=AD=A5=E6=88=90=E5=9E=8B=20*=20!34=20webui=20?= =?UTF-8?q?=E5=88=9D=E6=AD=A5=E6=88=90=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/qodana_code_quality.yml | 48 + docker/app.Dockerfile | 4 +- docker/base.Dockerfile | 4 +- docker/docker-compose.postgres.yml | 5 +- docker/protocol.Dockerfile | 2 +- docker/schema/schema-postgres.sql | 124 - jcpp-app-bootstrap/pom.xml | 24 +- jcpp-app-bootstrap/src/layers.xml | 35 - .../sanbing/jcpp/JCPPServerApplication.java | 16 +- .../src/main/resources/app-service.yml | 84 +- .../src/main/resources/log4j2.xml | 8 + .../java/sanbing/jcpp/AbstractTestBase.java | 2 +- .../jcpp/app/dal/mapper/GunMapperIT.java | 12 +- .../jcpp/app/dal/mapper/OrderMapperIT.java | 67 - .../jcpp/app/dal/mapper/PileMapperIT.java | 12 +- .../jcpp/app/dal/mapper/StationMapperIT.java | 9 +- .../jcpp/app/dal/mapper/UserMapperIT.java | 24 +- jcpp-app/pom.xml | 54 + .../app/adapter/config/MvcCorsProperties.java | 23 + .../adapter/controller/BaseController.java | 116 + .../controller/DashboardController.java | 38 + .../app/adapter/controller/GunController.java | 61 + .../adapter/controller/PileController.java | 73 + .../controller/ProtocolController.java | 41 + .../adapter/controller/StationController.java | 107 + .../{ => controller}/TestController.java | 127 +- .../adapter/controller/UserController.java | 55 + .../app/adapter/controller/WebController.java | 29 + .../app/adapter/request/GunCreateRequest.java | 36 + .../app/adapter/request/GunQueryRequest.java | 27 + .../app/adapter/request/GunUpdateRequest.java | 22 + .../jcpp/app/adapter/request/PageRequest.java | 38 + .../adapter/request/PileCreateRequest.java | 45 + .../app/adapter/request/PileQueryRequest.java | 37 + .../adapter/request/PileUpdateRequest.java | 35 + .../adapter/request/StationCreateRequest.java | 44 + .../adapter/request/StationQueryRequest.java | 29 + .../adapter/request/StationUpdateRequest.java | 40 + .../app/adapter/response/ApiResponse.java | 73 + .../app/adapter/response/DashboardStats.java | 119 + .../jcpp/app/adapter/response/ErrorCode.java | 167 + .../response/GunWithStatusResponse.java | 95 + .../app/adapter/response/LoginResponse.java | 40 + .../app/adapter/response/PageResponse.java | 43 + .../adapter/response/PileOptionResponse.java | 33 + .../response/PileWithStatusResponse.java | 111 + .../app/adapter/response/ProtocolOption.java | 40 + .../app/adapter/response/StationOption.java | 39 + .../config/ibatis/enums/AuthorityEnum.java | 49 + .../config/ibatis/enums/GunOptStatusEnum.java | 24 - .../config/ibatis/enums/GunRunStatusEnum.java | 10 +- .../config/ibatis/enums/OrderStatusEnum.java | 26 - .../config/ibatis/enums/OrderTypeEnum.java | 23 - .../config/ibatis/enums/OwnerTypeEnum.java | 23 - .../config/ibatis/enums/PileStatusEnum.java | 37 +- .../dal/config/ibatis/enums/PileTypeEnum.java | 2 +- .../ibatis/enums/StationStatusEnum.java | 28 - .../config/ibatis/enums/UserStatusEnum.java | 2 +- .../UserCredentialsTypeHandler.java | 91 + .../jcpp/app/dal/entity/Attribute.java | 102 + .../java/sanbing/jcpp/app/dal/entity/Gun.java | 21 +- .../sanbing/jcpp/app/dal/entity/Order.java | 71 - .../sanbing/jcpp/app/dal/entity/Pile.java | 19 +- .../sanbing/jcpp/app/dal/entity/Station.java | 19 +- .../sanbing/jcpp/app/dal/entity/User.java | 29 +- .../jcpp/app/dal/mapper/AttributeMapper.java | 53 + .../jcpp/app/dal/mapper/GunMapper.java | 109 +- .../jcpp/app/dal/mapper/OrderMapper.java | 16 - .../jcpp/app/dal/mapper/PileMapper.java | 69 +- .../jcpp/app/dal/mapper/StationMapper.java | 2 +- .../jcpp/app/dal/mapper/UserMapper.java | 16 +- .../app/dal/repository/GunRepository.java | 36 + .../{ => dal}/repository/PileRepository.java | 4 +- .../AttributeKvInsertRepository.java | 225 + .../attribute/AttributeRepository.java | 34 + .../attribute/DefaultAttributeRepository.java | 192 + .../dal/repository/attribute/KvValidator.java | 90 + .../batch/ScheduledLogExecutorComponent.java | 40 + .../repository/batch/SqlBlockingQueue.java | 131 + .../batch/SqlBlockingQueueParams.java | 25 + .../batch/SqlBlockingQueueWrapper.java | 59 + .../app/dal/repository/batch/SqlQueue.java | 22 + .../dal/repository/batch/SqlQueueElement.java | 14 + .../impl}/AbstractCachedEntityRepository.java | 7 +- .../impl}/AbstractEntityRepository.java | 2 +- .../CachedVersionedEntityRepository.java | 6 +- .../repository/impl/GunRepositoryImpl.java | 73 + .../repository/impl}/PileRepositoryImpl.java | 5 +- .../impl/RepositoryExecutorService.java | 24 + .../jcpp/app/data/InstallModeEnum.java | 58 + .../sanbing/jcpp/app/data/PileSession.java | 4 +- .../sanbing/jcpp/app/data/kv/AttrKeyEnum.java | 69 + .../jcpp/app/data/kv/AttributeKvEntry.java | 17 + .../app/data/kv/AttributesSaveResult.java | 23 + .../app/data/kv/BaseAttributeKvEntry.java | 190 + .../jcpp/app/data/kv/BasicKvEntry.java | 74 + .../jcpp/app/data/kv/BooleanDataEntry.java | 59 + .../sanbing/jcpp/app/data/kv/DataType.java | 26 + .../jcpp/app/data/kv/DoubleDataEntry.java | 60 + .../jcpp/app/data/kv/JsonDataEntry.java | 60 + .../sanbing/jcpp/app/data/kv/KvEntry.java | 31 + .../jcpp/app/data/kv/LongDataEntry.java | 60 + .../jcpp/app/data/kv/StringDataEntry.java | 65 + .../jcpp/app/data/page/PageDataIterable.java | 103 + .../JCPPCredentialsExpiredResponse.java | 27 + .../JCPPCredentialsViolationResponse.java | 21 + .../jcpp/app/exception/JCPPErrorCode.java | 39 + .../jcpp/app/exception/JCPPErrorResponse.java | 37 + .../exception/JCPPErrorResponseHandler.java | 219 + .../jcpp/app/exception/JCPPException.java | 44 + .../initializing/InstallInitializingBean.java | 478 + .../StatusCleanupInitializingBean.java | 243 + .../jcpp/app/service/AttributeService.java | 34 + .../jcpp/app/service/DashboardService.java | 22 + .../jcpp/app/service/DownlinkCallService.java | 2 +- .../sanbing/jcpp/app/service/GunService.java | 79 + .../jcpp/app/service/PileProtocolService.java | 28 +- .../sanbing/jcpp/app/service/PileService.java | 118 + .../jcpp/app/service/PileSessionService.java | 88 + .../jcpp/app/service/ProtocolService.java | 27 + .../jcpp/app/service/StationService.java | 61 + .../sanbing/jcpp/app/service/UserService.java | 72 + .../service/cache/CacheExecutorService.java | 24 + .../attribute/AttributeCacheEvictEvent.java | 21 + .../cache/attribute/AttributeCacheKey.java | 31 + .../attribute/AttributeCaffeineCache.java | 24 + .../cache/attribute/AttributeRedisCache.java | 43 + .../service/cache/gun/GunCacheEvictEvent.java | 19 + .../app/service/cache/gun/GunCacheKey.java | 44 + .../service/cache/gun/GunCaffeineCache.java | 23 + .../app/service/cache/gun/GunRedisCache.java | 35 + .../app/service/cache/pile/PileCacheKey.java | 15 +- .../cache/session/PileSessionCacheKey.java | 14 +- .../session/PileSessionCaffeineCache.java | 2 +- .../cache/session/PileSessionRedisCache.java | 2 +- .../DownlinkRestTemplateConfiguration.java | 2 +- .../app/service/grpc/DownlinkGrpcClient.java | 8 +- .../service/impl/CachedAttributeService.java | 232 + .../service/impl/DefaultDashboardService.java | 115 + .../app/service/impl/DefaultGunService.java | 234 + .../impl/DefaultPileProtocolService.java | 359 +- .../app/service/impl/DefaultPileService.java | 324 + .../impl/DefaultPileSessionService.java | 369 + .../service/impl/DefaultProtocolService.java | 36 + .../service/impl/DefaultStationService.java | 193 + .../app/service/impl/DefaultUserService.java | 251 + .../service/impl/GrpcDownlinkCallService.java | 2 +- .../service/impl/RestDownlinkCallService.java | 4 +- .../queue/AbstractConsumerService.java | 5 - .../app/service/queue/AppConsumerStats.java | 190 +- .../jcpp/app/service/queue/QueueEvent.java | 4 +- .../ProtocolUplinkConsumerService.java | 38 +- .../security/SecurityConfiguration.java | 182 + .../auth/AbstractJwtAuthenticationToken.java | 57 + .../security/auth/AuthExceptionHandler.java | 37 + .../security/auth/JwtAuthenticationToken.java | 24 + .../auth/RefreshAuthenticationToken.java | 24 + .../auth/jwt/JwtAuthenticationProvider.java | 46 + ...wtTokenAuthenticationProcessingFilter.java | 59 + .../RefreshTokenAuthenticationProvider.java | 59 + .../jwt/RefreshTokenProcessingFilter.java | 82 + .../auth/jwt/RefreshTokenRequest.java | 18 + .../auth/jwt/SkipPathRequestMatcher.java | 36 + .../extractor/JwtHeaderTokenExtractor.java | 35 + .../jwt/extractor/JwtQueryTokenExtractor.java | 33 + .../auth/jwt/extractor/TokenExtractor.java | 13 + .../security/auth/rest/LoginRequest.java | 19 + .../auth/rest/RestAuthenticationDetails.java | 39 + .../rest/RestAuthenticationDetailsSource.java | 18 + .../auth/rest/RestAuthenticationProvider.java | 85 + ...RestAwareAuthenticationFailureHandler.java | 35 + ...RestAwareAuthenticationSuccessHandler.java | 56 + .../auth/rest/RestLoginProcessingFilter.java | 87 + .../security/config/SecurityProperties.java | 141 + .../AuthMethodNotSupportedException.java | 17 + .../exception/JwtExpiredTokenException.java | 28 + .../UserPasswordExpiredException.java | 24 + .../UserPasswordNotValidException.java | 17 + .../app/service/security/model/JwtPair.java | 31 + .../app/service/security/model/JwtToken.java | 13 + .../service/security/model/SecurityUser.java | 56 + .../security/model/UserCredentials.java | 25 + .../service/security/model/UserPrincipal.java | 13 + .../security/model/token/AccessJwtToken.java | 23 + .../security/model/token/JwtTokenFactory.java | 190 + .../model/token/RawAccessJwtToken.java | 18 + .../main/resources/mapper/AttributeMapper.xml | 43 + .../src/main/resources/mapper/GunMapper.xml | 176 + .../src/main/resources/mapper/PileMapper.xml | 166 + .../src/main/resources/sql/schema-init.sql | 131 + jcpp-app/src/main/resources/xss-policy.xml | 153 + .../infrastructure/cache/CacheConstants.java | 4 + .../cache/VersionedCacheKey.java | 2 +- .../cache/VersionedRedisCache.java | 6 +- jcpp-infrastructure-proto/pom.xml | 4 - .../infrastructure/proto/ProtoConverter.java | 121 +- .../proto/model/PricingModel.java | 65 +- .../src/main/proto/protocol.proto | 84 +- .../queue/AbstractQueueConsumerTemplate.java | 2 +- .../queue/common/QueueConstants.java | 2 +- .../discovery/DefaultServiceInfoProvider.java | 2 +- .../discovery/HashPartitionProvider.java | 4 +- .../queue/discovery/ServiceInfoProvider.java | 2 +- .../event/JCPPApplicationEventListener.java | 4 +- .../discovery/event/PartitionChangeEvent.java | 2 +- .../queue/kafka/KafkaAdmin.java | 4 +- .../infrastructure/stats/StatsFactory.java | 6 - jcpp-infrastructure-util/pom.xml | 6 +- .../infrastructure/util/CollectionsUtil.java | 94 + .../infrastructure/util/JCPPHashUtil.java | 2 +- .../util/annotation/AppComponent.java | 2 +- .../util/annotation/ProtocolComponent.java | 2 +- .../util/async/AbstractListeningExecutor.java | 54 + .../util/async/JCPPVirtualThreadFactory.java | 1 + .../util/async/ListeningExecutor.java | 33 + .../infrastructure/util/codec/BCDUtil.java | 2 +- .../infrastructure/util/codec/ByteUtil.java | 8 +- .../util/config/ShardingThreadPool.java | 2 +- .../util/config/ThreadPoolConfiguration.java | 2 +- .../util/exception/DownlinkException.java | 2 +- .../util/jackson/DataTypeModule.java | 2 +- .../util/jackson/DateDeserializer.java | 2 +- .../util/jackson/DateSerializer.java | 2 +- .../util/jackson/InstantDeserializer.java | 2 +- .../util/jackson/InstantSerializer.java | 2 +- .../util/jackson/JacksonUtil.java | 125 +- .../util/jackson/LocalDateTimeSerializer.java | 2 +- .../util/jackson/LocalTimeSerializer.java | 2 +- .../jackson/LongTimestampDeserializer.java | 2 +- .../util/jackson/SqlDateDeserializer.java | 2 +- .../util/jackson/SqlDateSerializer.java | 2 +- .../util/jackson/TimestampDeserializer.java | 2 +- .../util/jackson/TimestampSerializer.java | 2 +- .../util/validation/Length.java | 2 + .../util/validation/NoNullChar.java | 26 + .../infrastructure/util/validation/NoXss.java | 31 + .../util/validation/NoXssValidator.java | 61 + .../util/validation/RateLimit.java | 30 + .../util/validation/ValidJsonSchema.java | 26 + .../jcpp/protocol/ProtocolBootstrap.java | 4 +- .../jcpp/protocol/ProtocolContext.java | 2 +- .../protocol/ProtocolMessageProcessor.java | 18 +- .../protocol/adapter/DownlinkController.java | 4 +- .../protocol/adapter/DownlinkGrpcService.java | 6 +- .../jcpp/protocol/annotation/ProtocolCmd.java | 2 +- .../sanbing/jcpp/protocol/cfg/MemoryCfg.java | 2 +- .../jcpp/protocol/cfg/TcpHandlerCfg.java | 2 +- .../protocol/cfg/enums/TcpHandlerType.java | 2 +- .../jcpp/protocol/domain/DownlinkCmdEnum.java | 2 +- .../jcpp/protocol/domain/ProtocolSession.java | 38 +- .../protocol/domain/SessionCloseReason.java | 27 - .../protocol/domain/SessionToHandlerMsg.java | 2 +- .../protocol/enums/SupportedProtocols.java | 75 + .../executor/ProtocolDownlinkCmdExe.java | 2 +- .../executor/ProtocolUplinkCmdExe.java | 2 +- .../jcpp/protocol/forwarder/Forwarder.java | 6 +- .../protocol/forwarder/KafkaForwarder.java | 4 +- .../protocol/forwarder/MemoryForwarder.java | 2 +- .../listener/ChannelHandlerInitializer.java | 4 +- .../jcpp/protocol/listener/Listener.java | 10 +- .../listener/tcp/TcpChannelHandler.java | 15 +- .../protocol/listener/tcp/TcpListener.java | 2 +- .../protocol/listener/tcp/TcpSession.java | 10 +- .../tcp/decoder/JCPPHeadTailFrameDecoder.java | 2 +- .../JCPPLengthFieldBasedFrameDecoder.java | 2 +- .../protocol/listener/tcp/enums/ReadAct.java | 2 +- .../tcp/enums/SequenceNumberLength.java | 2 +- .../tcp/handler/IdleEventHandler.java | 2 +- .../listener/tcp/handler/TracerHandler.java | 2 +- .../mapping/DownlinkCmdConverter.java | 4 +- .../ProtocolSessionRegistryProvider.java | 2 +- .../provider/ProtocolsConfigProvider.java | 2 +- ...efaultProtocolSessionRegistryProvider.java | 6 +- .../impl/DefaultProtocolsConfigProvider.java | 2 +- .../routing/ProtocolCommandRouter.java | 6 +- jcpp-protocol-bootstrap/pom.xml | 18 - jcpp-protocol-bootstrap/src/layers.xml | 35 - .../JCPPProtocolServiceApplication.java | 17 +- .../src/main/resources/log4j2.xml | 8 + .../protocol/AbstractProtocolTestBase.java | 9 +- .../adapter/DownlinkControllerIT.java | 20 +- .../protocol/lvneng/LvnengDownlinkCmdExe.java | 2 +- .../lvneng/LvnengDwonlinkMessage.java | 2 +- .../lvneng/LvnengProtocolConstants.java | 6 +- .../LvnengProtocolMessageProcessor.java | 6 +- .../protocol/lvneng/LvnengUplinkCmdExe.java | 7 +- .../mapping/LvnengDownlinkCmdConverter.java | 6 +- .../v340/cmd/LvnengV340LoginAckDLCmd.java | 4 +- .../v340/cmd/LvnengV340RealTimeDataULCmd.java | 13 +- .../cmd/LvnengV340TransactionRecordULCmd.java | 8 +- jcpp-protocol-yunkuaichong/READMD.md | 12 +- .../AbstractYunKuaiChongCmdExe.java | 12 +- .../YunKuaiChongDownlinkCmdExe.java | 2 +- .../YunKuaiChongDwonlinkMessage.java | 2 +- .../YunKuaiChongProtocolConstants.java | 12 +- .../YunKuaiChongProtocolMessageProcessor.java | 2 +- .../YunKuaiChongUplinkCmdExe.java | 7 +- .../YunKuaiChongDownlinkCmdConverter.java | 6 +- .../YunkuaichongV150ProtocolBootstrap.java | 2 +- .../cmd/YunKuaiChongV150BmsAbortULCmd.java | 5 - ...YunKuaiChongV150BmsChargingErrorULCmd.java | 4 - .../YunKuaiChongV150BmsChargingInfoULCmd.java | 4 - ...iChongV150BmsDemandChargerOutputULCmd.java | 110 + .../YunKuaiChongV150BmsHandshakeULCmd.java | 7 +- ...uaiChongV150BmsParamConfigReportULCmd.java | 5 - .../cmd/YunKuaiChongV150HeartbeatULCmd.java | 2 +- .../cmd/YunKuaiChongV150LockStatusULCmd.java | 4 - .../cmd/YunKuaiChongV150LoginAckDLCmd.java | 6 +- ...iChongV150OfflineCardSyncRequestDLCmd.java | 6 +- ...ChongV150OfflineCardSyncResponseULCmd.java | 7 +- .../cmd/YunKuaiChongV150OtaResponseULCmd.java | 7 +- ...uaiChongV150QueryPricingModelAckDLCmd.java | 23 +- ...unKuaiChongV150QueryPricingModelULCmd.java | 2 +- .../YunKuaiChongV150RealTimeDataULCmd.java | 10 +- .../cmd/YunKuaiChongV150RemoteStartDLCmd.java | 2 +- ...unKuaiChongV150RemoteStartResultULCmd.java | 7 +- .../cmd/YunKuaiChongV150RemoteStopDLCmd.java | 2 +- ...YunKuaiChongV150RemoteStopResultULCmd.java | 7 +- .../YunKuaiChongV150RestartPileAckULCmd.java | 5 - ...nKuaiChongV150SetPricingModelAckULCmd.java | 2 +- .../YunKuaiChongV150SetPricingModelDLCmd.java | 23 +- .../cmd/YunKuaiChongV150TimeSyncDLCmd.java | 4 +- .../YunKuaiChongV150TimeSyncResultULCmd.java | 7 +- ...uaiChongV150TransactionRecordAckDLCmd.java | 2 +- ...unKuaiChongV150TransactionRecordULCmd.java | 4 +- ...aiChongV150VerifyPricingModelAckDLCmd.java | 2 +- ...nKuaiChongV150VerifyPricingModelULCmd.java | 2 +- .../YunkuaichongV160ProtocolBootstrap.java | 2 +- ...KuaiChongV160RemoteParallelStartDLCmd.java | 2 +- ...ongV160RemoteParallelStartResultULCmd.java | 7 +- .../YunkuaichongV170ProtocolBootstrap.java | 2 +- ...unKuaiChongV170TransactionRecordULCmd.java | 4 +- jcpp-testing/src/main/java/module-info.java | 6 + jcpp-web-ui/.gitignore | 23 + jcpp-web-ui/LICENSE | 201 + jcpp-web-ui/package-lock.json | 19432 ++++++++++++++++ jcpp-web-ui/package.json | 44 + jcpp-web-ui/pom.xml | 159 + jcpp-web-ui/public/favicon.ico | Bin 0 -> 1118 bytes jcpp-web-ui/public/index.html | 28 + jcpp-web-ui/public/manifest.json | 15 + jcpp-web-ui/public/robots.txt | 10 + jcpp-web-ui/src/App.css | 181 + jcpp-web-ui/src/App.tsx | 85 + jcpp-web-ui/src/assets/icons/logo192.svg | 79 + jcpp-web-ui/src/components/AppLogo.tsx | 35 + jcpp-web-ui/src/components/Dashboard.tsx | 447 + jcpp-web-ui/src/components/GlobalToast.tsx | 111 + jcpp-web-ui/src/components/GunManagement.tsx | 970 + jcpp-web-ui/src/components/Layout.tsx | 171 + jcpp-web-ui/src/components/Login.css | 55 + jcpp-web-ui/src/components/Login.tsx | 98 + .../src/components/NotFoundRedirect.tsx | 47 + jcpp-web-ui/src/components/PileManagement.tsx | 1121 + jcpp-web-ui/src/components/ProtectedRoute.tsx | 40 + .../src/components/StationManagement.tsx | 1088 + jcpp-web-ui/src/contexts/AuthContext.tsx | 139 + jcpp-web-ui/src/contexts/ToastContext.tsx | 83 + jcpp-web-ui/src/data/china-regions.ts | 78 + jcpp-web-ui/src/index.css | 19 + jcpp-web-ui/src/index.tsx | 21 + jcpp-web-ui/src/react-app-env.d.ts | 7 + jcpp-web-ui/src/services/api.ts | 176 + jcpp-web-ui/src/services/dashboardService.ts | 69 + jcpp-web-ui/src/services/gunService.ts | 33 + jcpp-web-ui/src/services/pileService.ts | 71 + jcpp-web-ui/src/services/protocolService.ts | 22 + jcpp-web-ui/src/services/stationService.ts | 42 + jcpp-web-ui/src/types/index.ts | 228 + jcpp-web-ui/src/utils/index.ts | 178 + jcpp-web-ui/tsconfig.json | 26 + pom.xml | 179 +- 372 files changed, 37900 insertions(+), 1206 deletions(-) create mode 100644 .github/workflows/qodana_code_quality.yml delete mode 100644 docker/schema/schema-postgres.sql delete mode 100644 jcpp-app-bootstrap/src/layers.xml delete mode 100644 jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/OrderMapperIT.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/config/MvcCorsProperties.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/BaseController.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/DashboardController.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/GunController.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/PileController.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/ProtocolController.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/StationController.java rename jcpp-app/src/main/java/sanbing/jcpp/app/adapter/{ => controller}/TestController.java (66%) create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/UserController.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/WebController.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunCreateRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunQueryRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunUpdateRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PageRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileCreateRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileQueryRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileUpdateRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationCreateRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationQueryRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationUpdateRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ApiResponse.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/DashboardStats.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ErrorCode.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/GunWithStatusResponse.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/LoginResponse.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PageResponse.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileOptionResponse.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileWithStatusResponse.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ProtocolOption.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/StationOption.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/AuthorityEnum.java delete mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/GunOptStatusEnum.java delete mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OrderStatusEnum.java delete mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OrderTypeEnum.java delete mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OwnerTypeEnum.java delete mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/StationStatusEnum.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/typehandlers/UserCredentialsTypeHandler.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Attribute.java delete mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Order.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/AttributeMapper.java delete mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/OrderMapper.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/GunRepository.java rename jcpp-app/src/main/java/sanbing/jcpp/app/{ => dal}/repository/PileRepository.java (84%) create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/AttributeKvInsertRepository.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/AttributeRepository.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/DefaultAttributeRepository.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/KvValidator.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/ScheduledLogExecutorComponent.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueue.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueParams.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueWrapper.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlQueue.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlQueueElement.java rename jcpp-app/src/main/java/sanbing/jcpp/app/{repository => dal/repository/impl}/AbstractCachedEntityRepository.java (76%) rename jcpp-app/src/main/java/sanbing/jcpp/app/{repository => dal/repository/impl}/AbstractEntityRepository.java (90%) rename jcpp-app/src/main/java/sanbing/jcpp/app/{repository => dal/repository/impl}/CachedVersionedEntityRepository.java (83%) create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/GunRepositoryImpl.java rename jcpp-app/src/main/java/sanbing/jcpp/app/{repository => dal/repository/impl}/PileRepositoryImpl.java (93%) create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/RepositoryExecutorService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/InstallModeEnum.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttrKeyEnum.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttributeKvEntry.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttributesSaveResult.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BaseAttributeKvEntry.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BasicKvEntry.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BooleanDataEntry.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/DataType.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/DoubleDataEntry.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/JsonDataEntry.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/KvEntry.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/LongDataEntry.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/StringDataEntry.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/data/page/PageDataIterable.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPCredentialsExpiredResponse.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPCredentialsViolationResponse.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorCode.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorResponse.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorResponseHandler.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPException.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/initializing/InstallInitializingBean.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/initializing/StatusCleanupInitializingBean.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/AttributeService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/DashboardService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/GunService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/PileService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/PileSessionService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/ProtocolService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/StationService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/UserService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/CacheExecutorService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCacheEvictEvent.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCacheKey.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCaffeineCache.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeRedisCache.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheEvictEvent.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheKey.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCaffeineCache.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunRedisCache.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/CachedAttributeService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultDashboardService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultGunService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileSessionService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultProtocolService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultStationService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultUserService.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/SecurityConfiguration.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/AbstractJwtAuthenticationToken.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/AuthExceptionHandler.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/JwtAuthenticationToken.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/RefreshAuthenticationToken.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/JwtAuthenticationProvider.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenProcessingFilter.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/SkipPathRequestMatcher.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/TokenExtractor.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/LoginRequest.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationDetails.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationDetailsSource.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationProvider.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestLoginProcessingFilter.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/config/SecurityProperties.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/AuthMethodNotSupportedException.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/JwtExpiredTokenException.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/UserPasswordExpiredException.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/UserPasswordNotValidException.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/JwtPair.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/JwtToken.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/SecurityUser.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/UserCredentials.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/UserPrincipal.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/AccessJwtToken.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/JwtTokenFactory.java create mode 100644 jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/RawAccessJwtToken.java create mode 100644 jcpp-app/src/main/resources/mapper/AttributeMapper.xml create mode 100644 jcpp-app/src/main/resources/mapper/GunMapper.xml create mode 100644 jcpp-app/src/main/resources/mapper/PileMapper.xml create mode 100644 jcpp-app/src/main/resources/sql/schema-init.sql create mode 100644 jcpp-app/src/main/resources/xss-policy.xml create mode 100644 jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/CollectionsUtil.java create mode 100644 jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/AbstractListeningExecutor.java create mode 100644 jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/ListeningExecutor.java create mode 100644 jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoNullChar.java create mode 100644 jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoXss.java create mode 100644 jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoXssValidator.java create mode 100644 jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/RateLimit.java create mode 100644 jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/ValidJsonSchema.java delete mode 100644 jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/SessionCloseReason.java create mode 100644 jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/enums/SupportedProtocols.java delete mode 100644 jcpp-protocol-bootstrap/src/layers.xml create mode 100644 jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsDemandChargerOutputULCmd.java create mode 100644 jcpp-web-ui/.gitignore create mode 100644 jcpp-web-ui/LICENSE create mode 100644 jcpp-web-ui/package-lock.json create mode 100644 jcpp-web-ui/package.json create mode 100644 jcpp-web-ui/pom.xml create mode 100644 jcpp-web-ui/public/favicon.ico create mode 100644 jcpp-web-ui/public/index.html create mode 100644 jcpp-web-ui/public/manifest.json create mode 100644 jcpp-web-ui/public/robots.txt create mode 100644 jcpp-web-ui/src/App.css create mode 100644 jcpp-web-ui/src/App.tsx create mode 100644 jcpp-web-ui/src/assets/icons/logo192.svg create mode 100644 jcpp-web-ui/src/components/AppLogo.tsx create mode 100644 jcpp-web-ui/src/components/Dashboard.tsx create mode 100644 jcpp-web-ui/src/components/GlobalToast.tsx create mode 100644 jcpp-web-ui/src/components/GunManagement.tsx create mode 100644 jcpp-web-ui/src/components/Layout.tsx create mode 100644 jcpp-web-ui/src/components/Login.css create mode 100644 jcpp-web-ui/src/components/Login.tsx create mode 100644 jcpp-web-ui/src/components/NotFoundRedirect.tsx create mode 100644 jcpp-web-ui/src/components/PileManagement.tsx create mode 100644 jcpp-web-ui/src/components/ProtectedRoute.tsx create mode 100644 jcpp-web-ui/src/components/StationManagement.tsx create mode 100644 jcpp-web-ui/src/contexts/AuthContext.tsx create mode 100644 jcpp-web-ui/src/contexts/ToastContext.tsx create mode 100644 jcpp-web-ui/src/data/china-regions.ts create mode 100644 jcpp-web-ui/src/index.css create mode 100644 jcpp-web-ui/src/index.tsx create mode 100644 jcpp-web-ui/src/react-app-env.d.ts create mode 100644 jcpp-web-ui/src/services/api.ts create mode 100644 jcpp-web-ui/src/services/dashboardService.ts create mode 100644 jcpp-web-ui/src/services/gunService.ts create mode 100644 jcpp-web-ui/src/services/pileService.ts create mode 100644 jcpp-web-ui/src/services/protocolService.ts create mode 100644 jcpp-web-ui/src/services/stationService.ts create mode 100644 jcpp-web-ui/src/types/index.ts create mode 100644 jcpp-web-ui/src/utils/index.ts create mode 100644 jcpp-web-ui/tsconfig.json diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml new file mode 100644 index 0000000..3ab9857 --- /dev/null +++ b/.github/workflows/qodana_code_quality.yml @@ -0,0 +1,48 @@ +# +# 开源代码,仅供学习和交流研究使用,商用请联系三丙 +# 微信:mohan_88888 +# 抖音:程序员三丙 +# 付费课程知识星球:https://t.zsxq.com/aKtXo +# + +#-------------------------------------------------------------------------------# +# Discover additional configuration options in our documentation # +# https://www.jetbrains.com/help/qodana/github.html # +#-------------------------------------------------------------------------------# + +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - Feat_web_ui + +jobs: + qodana: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.2 + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + with: + args: --baseline,qodana.sarif.json + # In pr-mode: 'true' Qodana checks only changed files + pr-mode: false + use-caches: true + post-pr-comment: true + use-annotations: true + # Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job + upload-result: false + # quick-fixes available in Ultimate and Ultimate Plus plans + push-fixes: 'none' \ No newline at end of file diff --git a/docker/app.Dockerfile b/docker/app.Dockerfile index d5e3a3e..8c785fc 100644 --- a/docker/app.Dockerfile +++ b/docker/app.Dockerfile @@ -9,7 +9,7 @@ FROM registry.cn-hangzhou.aliyuncs.com/sanbing/jcpp-base:1.0 AS base WORKDIR /app COPY . . -RUN mvn -U -B -T 0.8C clean install -DskipTests +RUN mvn -U -B -T 4 clean package -DskipTests #分层 FROM registry.cn-hangzhou.aliyuncs.com/sanbing/openjdk:21-bullseye AS builder @@ -37,7 +37,7 @@ RUN chmod a+x start.sh \ && chmod 700 -R /home/sanbing/logs/* \ && chown -R sanbing:sanbing /home/sanbing -EXPOSE 8080 8080 +EXPOSE 8080/tcp 38001/tcp 38002/tcp 38003/tcp 38011/tcp ENV APP_LOG_LEVEL=INFO ENV PROTOCOLS_LOG_LEVEL=INFO diff --git a/docker/base.Dockerfile b/docker/base.Dockerfile index 7b9234b..2a15ae9 100644 --- a/docker/base.Dockerfile +++ b/docker/base.Dockerfile @@ -6,9 +6,9 @@ # -FROM registry.cn-hangzhou.aliyuncs.com/sanbing/mvn:3.9.9-jdk21 AS base +FROM registry.cn-hangzhou.aliyuncs.com/sanbing/openjdk:21-bullseye AS base WORKDIR /app COPY . . -RUN mvn -U -B -T 0.8C clean install -DskipTests \ +RUN mvn -U -B -T 4 clean compile -DskipTests \ && rm -rf /app diff --git a/docker/docker-compose.postgres.yml b/docker/docker-compose.postgres.yml index 383136e..1b08e67 100644 --- a/docker/docker-compose.postgres.yml +++ b/docker/docker-compose.postgres.yml @@ -7,7 +7,7 @@ volumes: - postgresql_data: {} + jcpp_pg_data: {} networks: sanbing-network: @@ -33,5 +33,4 @@ services: - 'POSTGRESQL_DEFAULT_TRANSACTION_ISOLATION=read committed' - 'TZ=Asia/Shanghai' volumes: - - postgresql_data:/bitnami/postgresql - - ./schema/schema-postgres.sql:/docker-entrypoint-initdb.d/init.sql \ No newline at end of file + - jcpp_pg_data:/bitnami/postgresql diff --git a/docker/protocol.Dockerfile b/docker/protocol.Dockerfile index 1c0ff2c..edb46c7 100644 --- a/docker/protocol.Dockerfile +++ b/docker/protocol.Dockerfile @@ -37,7 +37,7 @@ RUN chmod a+x start.sh \ && chmod 700 -R /home/sanbing/logs/* \ && chown -R sanbing:sanbing /home/sanbing -EXPOSE 8080 8080 +EXPOSE 8080/tcp 38001/tcp 38002/tcp 38003/tcp 38011/tcp ENV PROTOCOLS_LOG_LEVEL=INFO diff --git a/docker/schema/schema-postgres.sql b/docker/schema/schema-postgres.sql deleted file mode 100644 index bf6cde3..0000000 --- a/docker/schema/schema-postgres.sql +++ /dev/null @@ -1,124 +0,0 @@ --- --- 开源代码,仅供学习和交流研究使用,商用请联系三丙 --- 微信:mohan_88888 --- 抖音:程序员三丙 --- 付费课程知识星球:https://t.zsxq.com/aKtXo --- - - -CREATE TABLE IF NOT EXISTS jcpp_user -( - id uuid not null - constraint owner_pkey - primary key, - created_time timestamp default CURRENT_TIMESTAMP not null, - additional_info jsonb, - status varchar(16) not null, - user_name varchar(255) not null, - user_credentials jsonb not null, - version int default 1 -); - -CREATE UNIQUE INDEX IF NOT EXISTS uni_user_name - on jcpp_user (user_name); - -CREATE TABLE IF NOT EXISTS jcpp_station -( - id uuid not null - constraint station_pkey - primary key, - created_time timestamp default CURRENT_TIMESTAMP not null, - additional_info jsonb, - station_name varchar(255) not null, - station_code varchar(255) not null, - owner_id uuid not null, - longitude double precision not null, - latitude double precision not null, - owner_type varchar(16) not null, - province varchar(255), - city varchar(255), - county varchar(255), - address varchar(255), - status varchar(16) not null, - version int default 1 -); - -CREATE UNIQUE INDEX IF NOT EXISTS uni_station_code - on jcpp_station (station_code); - -CREATE TABLE IF NOT EXISTS jcpp_pile -( - id uuid not null - constraint pile_pkey - primary key, - created_time timestamp default CURRENT_TIMESTAMP not null, - additional_info jsonb, - pile_name varchar(255) not null, - pile_code varchar(255) not null, - protocol varchar(255) not null, - station_id uuid not null, - owner_id uuid not null, - owner_type varchar(16) not null, - brand varchar(255), - model varchar(255), - manufacturer varchar(255), - status varchar(16) not null, - type varchar(16) not null, - version int default 1 -); - -CREATE UNIQUE INDEX IF NOT EXISTS uni_pile_code - on jcpp_pile (pile_code); - - -CREATE TABLE IF NOT EXISTS jcpp_gun -( - id uuid not null - primary key, - created_time timestamp default CURRENT_TIMESTAMP not null, - additional_info varchar(255), - gun_no varchar(255) not null, - gun_name varchar(255) not null, - gun_code varchar(255) not null, - station_id uuid not null, - pile_id uuid not null, - owner_id uuid not null, - owner_type varchar(16) not null, - run_status varchar(16) not null, - run_status_updated_time timestamp not null, - opt_status varchar(16) not null, - version int default 1 -); - -CREATE UNIQUE INDEX IF NOT EXISTS uni_gun_code - on jcpp_gun (gun_code); - -CREATE TABLE IF NOT EXISTS jcpp_order -( - id uuid not null - primary key, - internal_order_no varchar(255) not null, - external_order_no varchar(255) not null, - pile_order_No varchar(255) not null, - created_time timestamp default CURRENT_TIMESTAMP not null, - additional_info jsonb, - updated_time timestamp, - cancelled_time timestamp, - status varchar(16) not null, - type varchar(16) not null, - creator_id uuid not null, - station_id uuid not null, - pile_id uuid not null, - gun_id uuid not null, - plate_no varchar(64), - settlement_amount numeric(16, 8) default 0 not null, - settlement_details jsonb, - electricity_quantity numeric(16, 8) default 0 not null -); - -CREATE UNIQUE INDEX IF NOT EXISTS uni_internal_order_no - on jcpp_order (internal_order_no); - -CREATE UNIQUE INDEX IF NOT EXISTS uni_external_order_no - on jcpp_order (external_order_no); - diff --git a/jcpp-app-bootstrap/pom.xml b/jcpp-app-bootstrap/pom.xml index 23150f1..063cf7a 100644 --- a/jcpp-app-bootstrap/pom.xml +++ b/jcpp-app-bootstrap/pom.xml @@ -33,6 +33,12 @@ sanbing jcpp-app + + sanbing + jcpp-web-ui + ${project.version} + runtime + sanbing jcpp-protocol-yunkuaichong @@ -54,24 +60,6 @@ org.springframework.boot spring-boot-maven-plugin - - false - ZIP - sanbing.jcpp.JCPPServerApplication - true - - true - ${project.basedir}/src/layers.xml - - - - - - repackage - build-info - - - org.apache.maven.plugins diff --git a/jcpp-app-bootstrap/src/layers.xml b/jcpp-app-bootstrap/src/layers.xml deleted file mode 100644 index 64e8aee..0000000 --- a/jcpp-app-bootstrap/src/layers.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - org/springframework/boot/loader/** - - - - - - - - - *:*:*SNAPSHOT - - - - - dependencies - spring-boot-loader - snapshot-dependencies - application - - diff --git a/jcpp-app-bootstrap/src/main/java/sanbing/jcpp/JCPPServerApplication.java b/jcpp-app-bootstrap/src/main/java/sanbing/jcpp/JCPPServerApplication.java index 36488db..8184358 100644 --- a/jcpp-app-bootstrap/src/main/java/sanbing/jcpp/JCPPServerApplication.java +++ b/jcpp-app-bootstrap/src/main/java/sanbing/jcpp/JCPPServerApplication.java @@ -6,26 +6,34 @@ */ package sanbing.jcpp; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.Banner; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.core.Ordered; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; +import sanbing.jcpp.infrastructure.util.annotation.AfterStartUp; import java.util.Arrays; +import java.util.concurrent.TimeUnit; /** - * @author baigod + * @author 九筒 */ @SpringBootApplication @EnableAsync @EnableScheduling +@Slf4j public class JCPPServerApplication { private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "app-service"; + private static long startTs; + public static void main(String[] args) { + startTs = System.currentTimeMillis(); new SpringApplicationBuilder(JCPPServerApplication.class).bannerMode(Banner.Mode.LOG).run(updateArguments(args)); } @@ -38,4 +46,10 @@ public class JCPPServerApplication { } return args; } + + @AfterStartUp(order = Ordered.LOWEST_PRECEDENCE) + public void afterStartUp() { + long startupTimeMs = System.currentTimeMillis() - startTs; + log.info("Started JChargePointProtocol App Service in {} seconds", TimeUnit.MILLISECONDS.toSeconds(startupTimeMs)); + } } \ No newline at end of file diff --git a/jcpp-app-bootstrap/src/main/resources/app-service.yml b/jcpp-app-bootstrap/src/main/resources/app-service.yml index 93e2131..863af8d 100644 --- a/jcpp-app-bootstrap/src/main/resources/app-service.yml +++ b/jcpp-app-bootstrap/src/main/resources/app-service.yml @@ -1,4 +1,4 @@ -server: +server: address: "${HTTP_BIND_ADDRESS:0.0.0.0}" port: "${HTTP_BIND_PORT:8080}" undertow: @@ -16,6 +16,10 @@ options: server: record-request-start-time: true + servlet: + encoding: + charset: UTF-8 + force: true spring: application: @@ -29,6 +33,50 @@ spring: leak-detection-threshold: "${SPRING_DATASOURCE_HIKARI_LEAK_DETECTION_THRESHOLD:0}" maximum-pool-size: "${SPRING_DATASOURCE_MAXIMUM_POOL_SIZE:64}" register-mbeans: "${SPRING_DATASOURCE_HIKARI_REGISTER_MBEANS:false}" + servlet: + multipart: + max-file-size: "${SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE:50MB}" + max-request-size: "${SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE:50MB}" + main: + allow-circular-references: "true" + mvc: + pathmatch.matching-strategy: "ANT_PATH_MATCHER" + async.request-timeout: "${SPRING_MVC_ASYNC_REQUEST_TIMEOUT:30000}" + cors: + mappings: + "[/api/**]": + allowed-origin-patterns: "*" + allowed-methods: "*" + allowed-headers: "*" + max-age: "1800" + allow-credentials: "true" + web: + resources: + chain: + compressed: "true" + strategy: + content: + enabled: "true" + +security: + jwt: + tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:9000}" + refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:604800}" + tokenIssuer: "${JWT_TOKEN_ISSUER:sanbing.io}" + tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:NUI3T1FCSVI0MFNweFAxRjEyUUJ1QkVPc3V0T1hEdUJON0hiZ2NydzdzN0RBSVZwTE9rMFFWeDVTRnZ6djMxSw==}" + # 安全设置配置 + settings: + # 密码策略 + passwordPolicy: + minimumLength: "${SECURITY_PASSWORD_POLICY_MINIMUM_LENGTH:6}" + maximumLength: "${SECURITY_PASSWORD_POLICY_MAXIMUM_LENGTH:72}" + minimumUppercaseLetters: "${SECURITY_PASSWORD_POLICY_MINIMUM_UPPERCASE_LETTERS:0}" + minimumLowercaseLetters: "${SECURITY_PASSWORD_POLICY_MINIMUM_LOWERCASE_LETTERS:0}" + minimumDigits: "${SECURITY_PASSWORD_POLICY_MINIMUM_DIGITS:0}" + minimumSpecialCharacters: "${SECURITY_PASSWORD_POLICY_MINIMUM_SPECIAL_CHARACTERS:0}" + allowWhitespaces: "${SECURITY_PASSWORD_POLICY_ALLOW_WHITESPACES:true}" + # 最大登录失败次数 + maxFailedLoginAttempts: "${SECURITY_MAX_FAILED_LOGIN_ATTEMPTS:5}" mybatis-plus: type-handlers-package: sanbing.jcpp.app.dal.config.ibatis.typehandlers @@ -111,17 +159,25 @@ queue: stats: enabled: "${QUEUE_APP_STATS_ENABLED:true}" print-interval-ms: "${QUEUE_APP_STATS_PRINT_INTERVAL_MS:60000}" + timer-top-n: "${QUEUE_APP_STATS_TIMER_TOP_N:5}" # 应用程序缓存配置 cache: type: "${CACHE_TYPE:caffeine}" # caffeine or redis + maximumPoolSize: "${CACHE_MAXIMUM_POOL_SIZE:16}" specs: piles: timeToLiveInMinutes: "${CACHE_SPECS_PILES_TTL:1440}" maxSize: "${CACHE_SPECS_PILES_MAX_SIZE:100000}" + guns: + timeToLiveInMinutes: "${CACHE_SPECS_GUNS_TTL:1440}" + maxSize: "${CACHE_SPECS_GUNS_MAX_SIZE:1000000}" pileSessions: - timeToLiveInMinutes: "${CACHE_SPECS_PILE_SESSIONS_TTL:1440}" + timeToLiveInMinutes: "${service.protocol.sessions.default-inactivity-timeout-in-sec}" maxSize: "${CACHE_SPECS_PILE_SESSIONS_MAX_SIZE:100000}" + attributes: + timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}" + maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}" redis: connection: @@ -133,7 +189,6 @@ redis: clientName: "${REDIS_CLIENT_NAME:standalone}" commandTimeout: "${REDIS_CLIENT_COMMAND_TIMEOUT:30000}" shutdownTimeout: "${REDIS_CLIENT_SHUTDOWN_TIMEOUT:1000}" - readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}" usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:true}" cluster: nodes: "${REDIS_NODES:redis-node-0:6379,redis-node-1:6379,redis-node-2:6379,redis-node-3:6379,redis-node-4:6379,redis-node-5:6379}" @@ -160,6 +215,29 @@ redis: blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}" evictTtlInMs: "${REDIS_EVICT_TTL_MS:60000}" +# 数据库安装配置 +install: + # 安装模式:init-初始化数据库,upgrade-升级,disabled-不做任何操作 + mode: "${INSTALL_MODE:disabled}" + +# SQL相关配置 +sql: + attributes: + # 批处理大小 + batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:1000}" + # 批处理最大延迟(毫秒) + batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY:100}" + # 统计打印间隔(毫秒) + stats_print_interval_ms: "${SQL_ATTRIBUTES_STATS_PRINT_INTERVAL_MS:1000}" + # 批处理线程数 + batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:4}" + # 值是否进行XSS验证 + value_no_xss_validation: "${SQL_ATTRIBUTES_VALUE_NO_XSS_VALIDATION:false}" + # 批处理排序 + batch_sort: "${SQL_BATCH_SORT:true}" + # 是否移除空字符 + remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" + service: # 服务类型:纯协议解析前置 - protocol,纯应用后端 - app,单体服务(包含protocol和app) - monolith type: "${SERVICE_TYPE:monolith}" diff --git a/jcpp-app-bootstrap/src/main/resources/log4j2.xml b/jcpp-app-bootstrap/src/main/resources/log4j2.xml index 0195198..6f937f4 100644 --- a/jcpp-app-bootstrap/src/main/resources/log4j2.xml +++ b/jcpp-app-bootstrap/src/main/resources/log4j2.xml @@ -1,4 +1,12 @@ + diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/AbstractTestBase.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/AbstractTestBase.java index 8a6d739..bb891fe 100644 --- a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/AbstractTestBase.java +++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/AbstractTestBase.java @@ -14,7 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; /** - * @author baigod + * @author 九筒 */ @ActiveProfiles("test") @SpringBootTest(classes = JCPPServerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/GunMapperIT.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/GunMapperIT.java index e87ce53..0b8afe0 100644 --- a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/GunMapperIT.java +++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/GunMapperIT.java @@ -10,9 +10,6 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import sanbing.jcpp.AbstractTestBase; -import sanbing.jcpp.app.dal.config.ibatis.enums.GunOptStatusEnum; -import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum; -import sanbing.jcpp.app.dal.config.ibatis.enums.OwnerTypeEnum; import sanbing.jcpp.app.dal.entity.Gun; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; @@ -22,10 +19,10 @@ import java.util.UUID; import static sanbing.jcpp.app.dal.mapper.PileMapperIT.NORMAL_PILE_ID; import static sanbing.jcpp.app.dal.mapper.StationMapperIT.NORMAL_STATION_ID; -import static sanbing.jcpp.app.dal.mapper.UserMapperIT.NORMAL_USER_ID; + /** - * @author baigod + * @author 九筒 */ class GunMapperIT extends AbstractTestBase { static final UUID[] NORMAL_GUN_ID = new UUID[]{ @@ -61,11 +58,6 @@ class GunMapperIT extends AbstractTestBase { .gunCode("202312120000" + new DecimalFormat("00").format(i + 1) + "-02") .stationId(NORMAL_STATION_ID) .pileId(pileId) - .ownerId(NORMAL_USER_ID) - .ownerType(OwnerTypeEnum.C) - .runStatus(GunRunStatusEnum.IDLE) - .runStatusUpdatedTime(LocalDateTime.now()) - .optStatus(GunOptStatusEnum.AVAILABLE) .build(); gunMapper.insertOrUpdate(gun); diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/OrderMapperIT.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/OrderMapperIT.java deleted file mode 100644 index 6697b5b..0000000 --- a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/OrderMapperIT.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * 开源代码,仅供学习和交流研究使用,商用请联系三丙 - * 微信:mohan_88888 - * 抖音:程序员三丙 - * 付费课程知识星球:https://t.zsxq.com/aKtXo - */ -package sanbing.jcpp.app.dal.mapper; - -import cn.hutool.core.util.IdUtil; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import jakarta.annotation.Resource; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.Test; -import sanbing.jcpp.AbstractTestBase; -import sanbing.jcpp.app.dal.config.ibatis.enums.OrderStatusEnum; -import sanbing.jcpp.app.dal.config.ibatis.enums.OrderTypeEnum; -import sanbing.jcpp.app.dal.entity.Order; -import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.UUID; - -import static sanbing.jcpp.app.dal.mapper.GunMapperIT.NORMAL_GUN_ID; -import static sanbing.jcpp.app.dal.mapper.PileMapperIT.NORMAL_PILE_ID; -import static sanbing.jcpp.app.dal.mapper.StationMapperIT.NORMAL_STATION_ID; -import static sanbing.jcpp.app.dal.mapper.UserMapperIT.NORMAL_USER_ID; - -/** - * @author baigod - */ -class OrderMapperIT extends AbstractTestBase { - - @Resource - OrderMapper orderMapper; - - @Test - void testOrderMapper() { - orderMapper.delete(Wrappers.lambdaQuery()); - - Order order = Order.builder() - .id(UUID.randomUUID()) - .internalOrderNo(IdUtil.getSnowflake(1, 1).nextIdStr()) - .externalOrderNo(IdUtil.getSnowflake(1, 1).nextIdStr()) - .pileOrderNo(RandomStringUtils.secure().nextNumeric(16)) - .createdTime(LocalDateTime.now()) - .additionalInfo(JacksonUtil.newObjectNode()) - .updatedTime(LocalDateTime.now()) - .cancelledTime(null) - .status(OrderStatusEnum.IN_CHARGING) - .type(OrderTypeEnum.CHARGE) - .creatorId(NORMAL_USER_ID) - .stationId(NORMAL_STATION_ID) - .pileId(NORMAL_PILE_ID[0]) - .gunId(NORMAL_GUN_ID[0]) - .plateNo("浙A88888") - .settlementAmount(new BigDecimal(100)) - .settlementDetails(JacksonUtil.newObjectNode()) - .electricityQuantity(new BigDecimal("100")) - .build(); - - orderMapper.insertOrUpdate(order); - - log.info("{}", orderMapper.selectById(order.getId())); - - } -} \ No newline at end of file diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/PileMapperIT.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/PileMapperIT.java index 5fa4d57..251b4c3 100644 --- a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/PileMapperIT.java +++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/PileMapperIT.java @@ -11,8 +11,6 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import sanbing.jcpp.AbstractTestBase; -import sanbing.jcpp.app.dal.config.ibatis.enums.OwnerTypeEnum; -import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum; import sanbing.jcpp.app.dal.entity.Pile; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; @@ -25,10 +23,10 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import static sanbing.jcpp.app.dal.mapper.StationMapperIT.NORMAL_STATION_ID; -import static sanbing.jcpp.app.dal.mapper.UserMapperIT.NORMAL_USER_ID; + /** - * @author baigod + * @author 九筒 */ class PileMapperIT extends AbstractTestBase { static final UUID[] NORMAL_PILE_ID = new UUID[]{ @@ -61,12 +59,9 @@ class PileMapperIT extends AbstractTestBase { .pileCode("202312120000" + new DecimalFormat("00").format(i + 1)) .protocol("yunkuaichongV150") .stationId(NORMAL_STATION_ID) - .ownerId(NORMAL_USER_ID) - .ownerType(OwnerTypeEnum.C) .brand("星星") .model("10A") .manufacturer("星星") - .status(PileStatusEnum.IDLE) .type(PileTypeEnum.AC) .build(); @@ -90,12 +85,9 @@ class PileMapperIT extends AbstractTestBase { .pileCode("20241015" + new DecimalFormat("000000").format(number.get())) .protocol("yunkuaichongV150") .stationId(NORMAL_STATION_ID) - .ownerId(NORMAL_USER_ID) - .ownerType(OwnerTypeEnum.C) .brand("星星") .model("10A") .manufacturer("星星") - .status(PileStatusEnum.IDLE) .type(PileTypeEnum.AC) .build(); piles.add(pile); diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/StationMapperIT.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/StationMapperIT.java index 412e349..1c907de 100644 --- a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/StationMapperIT.java +++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/StationMapperIT.java @@ -10,18 +10,16 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import sanbing.jcpp.AbstractTestBase; -import sanbing.jcpp.app.dal.config.ibatis.enums.OwnerTypeEnum; -import sanbing.jcpp.app.dal.config.ibatis.enums.StationStatusEnum; import sanbing.jcpp.app.dal.entity.Station; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; import java.time.LocalDateTime; import java.util.UUID; -import static sanbing.jcpp.app.dal.mapper.UserMapperIT.NORMAL_USER_ID; + /** - * @author baigod + * @author 九筒 */ class StationMapperIT extends AbstractTestBase { static final UUID NORMAL_STATION_ID = UUID.fromString("07d80c81-fe99-4a1f-a6aa-dc4d798b5626"); @@ -39,15 +37,12 @@ class StationMapperIT extends AbstractTestBase { .additionalInfo(JacksonUtil.newObjectNode()) .stationName("三丙家专属充电站") .stationCode("S20241001001") - .ownerId(NORMAL_USER_ID) .longitude(120.107936F) .latitude(30.267014F) - .ownerType(OwnerTypeEnum.C) .province("浙江省") .city("杭州市") .county("西湖区") .address("西溪路552-1号") - .status(StationStatusEnum.OPERATIONAL) .build(); stationMapper.insertOrUpdate(station); diff --git a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/UserMapperIT.java b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/UserMapperIT.java index 1d8535d..5604d16 100644 --- a/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/UserMapperIT.java +++ b/jcpp-app-bootstrap/src/test/java/sanbing/jcpp/app/dal/mapper/UserMapperIT.java @@ -10,15 +10,17 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jakarta.annotation.Resource; import org.junit.jupiter.api.Test; import sanbing.jcpp.AbstractTestBase; +import sanbing.jcpp.app.dal.config.ibatis.enums.AuthorityEnum; import sanbing.jcpp.app.dal.config.ibatis.enums.UserStatusEnum; import sanbing.jcpp.app.dal.entity.User; +import sanbing.jcpp.app.service.security.model.UserCredentials; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; import java.time.LocalDateTime; import java.util.UUID; /** - * @author baigod + * @author 九筒 */ class UserMapperIT extends AbstractTestBase { static final UUID NORMAL_USER_ID = UUID.fromString("21cbf909-a23a-4396-840a-f34061f59f95"); @@ -30,17 +32,33 @@ class UserMapperIT extends AbstractTestBase { void curdTest() { userMapper.delete(Wrappers.lambdaQuery()); + // 创建UserCredentials对象 + UserCredentials credentials = new UserCredentials(); + credentials.setPassword("$2a$10$mE.qmcV0mFU5NcKh73TZx.z4ueI/.bDWbj0T1BYyqP481kGGarKLG"); // encoded "password123" + credentials.setEnabled(true); + credentials.setFailedLoginAttempts(0); + User user = User.builder() .id(NORMAL_USER_ID) .createdTime(LocalDateTime.now()) .additionalInfo(JacksonUtil.newObjectNode()) .status(UserStatusEnum.ENABLE) .userName("sanbing") - .userCredentials(JacksonUtil.newObjectNode()) + .userCredentials(credentials) + .authority(AuthorityEnum.SYS_ADMIN) // 添加权限字段 + .version(1) // 添加版本字段 .build(); userMapper.insertOrUpdate(user); - log.info("{}", userMapper.selectById(NORMAL_USER_ID)); + User savedUser = userMapper.selectById(NORMAL_USER_ID); + log.info("Saved user: {}", savedUser); + + // 验证UserCredentials字段正确保存和读取 + assert savedUser != null; + assert savedUser.getUserCredentials() != null; + assert savedUser.getUserCredentials().isEnabled(); + assert "sanbing".equals(savedUser.getUserName()); + assert AuthorityEnum.SYS_ADMIN.equals(savedUser.getAuthority()); } } \ No newline at end of file diff --git a/jcpp-app/pom.xml b/jcpp-app/pom.xml index d6a8acd..d6d1908 100644 --- a/jcpp-app/pom.xml +++ b/jcpp-app/pom.xml @@ -52,6 +52,60 @@ com.baomidou mybatis-plus-spring-boot3-starter + + + org.springframework.boot + spring-boot-starter-security + + + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-impl + runtime + + + io.jsonwebtoken + jjwt-jackson + runtime + + + org.passay + passay + + + com.github.ua-parser + uap-java + + + org.bouncycastle + bcprov-jdk18on + + + org.bouncycastle + bcpkix-jdk18on + + + org.bouncycastle + bcutil-jdk18on + + + org.bouncycastle + bcprov-ext-jdk18on + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-web + diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/config/MvcCorsProperties.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/config/MvcCorsProperties.java new file mode 100644 index 0000000..6008037 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/config/MvcCorsProperties.java @@ -0,0 +1,23 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; + +import java.util.HashMap; +import java.util.Map; + +@Data +@Configuration +@ConfigurationProperties(prefix = "spring.mvc.cors") +public class MvcCorsProperties { + + private Map mappings = new HashMap<>(); +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/BaseController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/BaseController.java new file mode 100644 index 0000000..19dea66 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/BaseController.java @@ -0,0 +1,116 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.controller; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import sanbing.jcpp.app.exception.JCPPErrorCode; +import sanbing.jcpp.app.exception.JCPPErrorResponseHandler; +import sanbing.jcpp.app.exception.JCPPException; + +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 基础控制器 + * 提供统一的异常处理机制,所有Controller都应该继承此类 + * + * @author 九筒 + */ +@Slf4j +public abstract class BaseController { + + @Autowired + private JCPPErrorResponseHandler errorResponseHandler; + + /** + * 处理所有通用异常 + */ + @ExceptionHandler(Exception.class) + public void handleControllerException(Exception e, HttpServletResponse response) { + log.debug("Processing controller exception: {}", e.getMessage(), e); + + JCPPException jcppException = handleException(e); + + // 如果是通用错误且有具体的原因异常,则使用原始异常 + if (jcppException.getErrorCode() == JCPPErrorCode.GENERAL && + jcppException.getCause() instanceof Exception && + Objects.equals(jcppException.getCause().getMessage(), jcppException.getMessage())) { + e = (Exception) jcppException.getCause(); + } else { + e = jcppException; + } + + errorResponseHandler.handle(e, response); + } + + /** + * 处理JCPPException异常 + * 直接委托给统一的错误处理器 + */ + @ExceptionHandler(JCPPException.class) + public void handleJCPPException(JCPPException ex, HttpServletResponse response) { + log.debug("Processing JCPP exception: {}", ex.getMessage(), ex); + errorResponseHandler.handle(ex, response); + } + + /** + * 处理参数校验异常 + * 将Spring的验证异常转换为JCPPException + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public void handleValidationError(MethodArgumentNotValidException validationError, HttpServletResponse response) { + log.warn("Validation error occurred: {}", validationError.getMessage()); + + // 提取字段错误信息 + String errorMessage = validationError.getBindingResult() + .getFieldErrors() + .stream() + .map(FieldError::getDefaultMessage) + .filter(Objects::nonNull) + .collect(Collectors.joining(", ")); + + if (errorMessage.isEmpty()) { + errorMessage = "Validation failed"; + } else { + errorMessage = "Validation error: " + errorMessage; + } + + JCPPException jcppException = new JCPPException(errorMessage, JCPPErrorCode.BAD_REQUEST_PARAMS); + handleControllerException(jcppException, response); + } + + /** + * 异常转换处理方法 + * 将各种异常转换为JCPPException,统一异常处理流程 + */ + private JCPPException handleException(Exception e) { + if (e instanceof JCPPException jcppException) { + return jcppException; + } + + // 处理运行时异常 + if (e instanceof RuntimeException) { + if (e instanceof IllegalArgumentException) { + return new JCPPException("Invalid argument: " + e.getMessage(), e, JCPPErrorCode.BAD_REQUEST_PARAMS); + } else if (e instanceof IllegalStateException) { + return new JCPPException("Invalid state: " + e.getMessage(), e, JCPPErrorCode.VERSION_CONFLICT); + } else { + return new JCPPException("Runtime error: " + e.getMessage(), e, JCPPErrorCode.GENERAL); + } + } + + // 其他异常统一处理为通用错误 + return new JCPPException("Unexpected error: " + e.getMessage(), e, JCPPErrorCode.GENERAL); + } +} + + diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/DashboardController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/DashboardController.java new file mode 100644 index 0000000..69add58 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/DashboardController.java @@ -0,0 +1,38 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import sanbing.jcpp.app.adapter.response.ApiResponse; +import sanbing.jcpp.app.adapter.response.DashboardStats; +import sanbing.jcpp.app.service.DashboardService; + +/** + * 仪表盘控制器 + * + * @author 九筒 + */ +@RestController +@RequestMapping("/api/dashboard") +@RequiredArgsConstructor +public class DashboardController extends BaseController { + + private final DashboardService dashboardService; + + /** + * 获取仪表盘统计数据 + */ + @GetMapping("/stats") + public ResponseEntity> getStats() { + DashboardStats stats = dashboardService.getDashboardStats(); + return ResponseEntity.ok(ApiResponse.success("获取仪表盘数据成功", stats)); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/GunController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/GunController.java new file mode 100644 index 0000000..0a47932 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/GunController.java @@ -0,0 +1,61 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import sanbing.jcpp.app.adapter.request.GunCreateRequest; +import sanbing.jcpp.app.adapter.request.GunQueryRequest; +import sanbing.jcpp.app.adapter.request.GunUpdateRequest; +import sanbing.jcpp.app.adapter.response.ApiResponse; +import sanbing.jcpp.app.adapter.response.GunWithStatusResponse; +import sanbing.jcpp.app.adapter.response.PageResponse; +import sanbing.jcpp.app.dal.entity.Gun; +import sanbing.jcpp.app.service.GunService; + +import java.util.UUID; + +@RestController +@RequestMapping("/api/guns") +@RequiredArgsConstructor +public class GunController extends BaseController { + + private final GunService gunService; + + @PostMapping + public ResponseEntity> createGun(@Valid @RequestBody GunCreateRequest request) { + Gun gun = gunService.createGun(request); + return ResponseEntity.ok(ApiResponse.success("创建成功", gun)); + } + + @GetMapping("/{id}") + public ResponseEntity> getGun(@PathVariable UUID id) { + Gun gun = gunService.findById(id); + return ResponseEntity.ok(ApiResponse.success("查询成功", gun)); + } + + @PutMapping("/{id}") + public ResponseEntity> updateGun(@PathVariable UUID id, + @Valid @RequestBody GunUpdateRequest request) { + Gun gun = gunService.updateGun(id, request); + return ResponseEntity.ok(ApiResponse.success("更新成功", gun)); + } + + @DeleteMapping("/{id}") + public ResponseEntity> deleteGun(@PathVariable UUID id) { + gunService.deleteGun(id); + return ResponseEntity.ok(ApiResponse.success("删除成功", null)); + } + + @GetMapping + public ResponseEntity>> queryGunsWithStatus(GunQueryRequest request) { + PageResponse guns = gunService.queryGunsWithStatus(request); + return ResponseEntity.ok(ApiResponse.success("查询成功", guns)); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/PileController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/PileController.java new file mode 100644 index 0000000..2f202b4 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/PileController.java @@ -0,0 +1,73 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import sanbing.jcpp.app.adapter.request.PileCreateRequest; +import sanbing.jcpp.app.adapter.request.PileQueryRequest; +import sanbing.jcpp.app.adapter.request.PileUpdateRequest; +import sanbing.jcpp.app.adapter.response.ApiResponse; +import sanbing.jcpp.app.adapter.response.PageResponse; +import sanbing.jcpp.app.adapter.response.PileOptionResponse; +import sanbing.jcpp.app.adapter.response.PileWithStatusResponse; +import sanbing.jcpp.app.dal.entity.Pile; +import sanbing.jcpp.app.exception.JCPPException; +import sanbing.jcpp.app.service.PileService; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/api/piles") +@RequiredArgsConstructor +public class PileController extends BaseController { + + private final PileService pileService; + + @PostMapping + public ResponseEntity> createPile(@Valid @RequestBody PileCreateRequest request) { + Pile pile = pileService.createPile(request); + return ResponseEntity.ok(ApiResponse.success("创建成功", pile)); + } + + @GetMapping("/{id}") + public ResponseEntity> getPile(@PathVariable UUID id) { + Pile pile = pileService.findById(id); + if (pile == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(ApiResponse.success("查询成功", pile)); + } + + @PutMapping("/{id}") + public ResponseEntity> updatePile(@PathVariable UUID id, + @Valid @RequestBody PileUpdateRequest request) throws JCPPException { + Pile pile = pileService.updatePile(id, request); + return ResponseEntity.ok(ApiResponse.success("更新成功", pile)); + } + + @DeleteMapping("/{id}") + public ResponseEntity> deletePile(@PathVariable UUID id) throws JCPPException { + pileService.deletePile(id); + return ResponseEntity.ok(ApiResponse.success("删除成功", null)); + } + + @GetMapping + public ResponseEntity>> queryPilesWithStatus(PileQueryRequest request) { + PageResponse piles = pileService.queryPilesWithStatus(request); + return ResponseEntity.ok(ApiResponse.success("查询成功", piles)); + } + + @GetMapping("/options") + public ResponseEntity>> getPileOptions() { + List options = pileService.getPileOptions(); + return ResponseEntity.ok(ApiResponse.success("查询成功", options)); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/ProtocolController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/ProtocolController.java new file mode 100644 index 0000000..3d943ec --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/ProtocolController.java @@ -0,0 +1,41 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import sanbing.jcpp.app.adapter.response.ApiResponse; +import sanbing.jcpp.app.adapter.response.ProtocolOption; +import sanbing.jcpp.app.service.ProtocolService; + +import java.util.List; + +/** + * 协议管理控制器 + * + * @author 九筒 + * @since 2024-12-22 + */ +@RestController +@RequestMapping("/api/protocols") +@RequiredArgsConstructor +public class ProtocolController extends BaseController { + + private final ProtocolService protocolService; + + /** + * 获取所有支持的协议列表 + */ + @GetMapping("/supported") + public ResponseEntity>> getSupportedProtocols() { + List protocols = protocolService.getSupportedProtocols(); + return ResponseEntity.ok(ApiResponse.success("查询成功", protocols)); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/StationController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/StationController.java new file mode 100644 index 0000000..6e170f7 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/StationController.java @@ -0,0 +1,107 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import sanbing.jcpp.app.adapter.request.StationCreateRequest; +import sanbing.jcpp.app.adapter.request.StationQueryRequest; +import sanbing.jcpp.app.adapter.request.StationUpdateRequest; +import sanbing.jcpp.app.adapter.response.ApiResponse; +import sanbing.jcpp.app.adapter.response.PageResponse; +import sanbing.jcpp.app.adapter.response.StationOption; +import sanbing.jcpp.app.dal.entity.Station; +import sanbing.jcpp.app.exception.JCPPException; +import sanbing.jcpp.app.service.StationService; + +import java.util.List; +import java.util.UUID; + +/** + * 充电站管理控制器 + * + * @author 九筒 + */ +@RestController +@RequestMapping("/api/stations") +@RequiredArgsConstructor +public class StationController extends BaseController { + + private final StationService stationService; + + /** + * 分页查询充电站 + */ + @GetMapping + public ResponseEntity>> getStations(StationQueryRequest request) { + PageResponse result = stationService.getStations(request); + return ResponseEntity.ok(ApiResponse.success("查询成功", result)); + } + + /** + * 根据ID获取充电站详情 + */ + @GetMapping("/{id}") + public ResponseEntity> getStation(@PathVariable UUID id) { + Station station = stationService.getStationById(id); + if (station == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(ApiResponse.success("查询成功", station)); + } + + /** + * 创建充电站 + */ + @PostMapping + public ResponseEntity> createStation(@Valid @RequestBody StationCreateRequest request) { + Station station = stationService.createStation(request); + return ResponseEntity.ok(ApiResponse.success("创建成功", station)); + } + + /** + * 更新充电站 + */ + @PutMapping("/{id}") + public ResponseEntity> updateStation(@PathVariable UUID id, + @Valid @RequestBody StationUpdateRequest request) throws JCPPException { + Station station = stationService.updateStation(id, request); + return ResponseEntity.ok(ApiResponse.success("更新成功", station)); + } + + /** + * 删除充电站 + */ + @DeleteMapping("/{id}") + public ResponseEntity> deleteStation(@PathVariable UUID id) throws JCPPException { + stationService.deleteStation(id); + return ResponseEntity.ok(ApiResponse.success("删除成功", null)); + } + + /** + * 获取充电站选项列表(用于下拉选择) + */ + @GetMapping("/options") + public ResponseEntity>> getStationOptions() { + List options = stationService.getStationOptions(); + return ResponseEntity.ok(ApiResponse.success("查询成功", options)); + } + + /** + * 搜索充电站选项列表(支持关键字搜索和分页) + */ + @GetMapping("/search") + public ResponseEntity>> searchStationOptions( + @RequestParam(required = false) String keyword, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { + List options = stationService.searchStationOptions(keyword, page, size); + return ResponseEntity.ok(ApiResponse.success("查询成功", options)); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/TestController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/TestController.java similarity index 66% rename from jcpp-app/src/main/java/sanbing/jcpp/app/adapter/TestController.java rename to jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/TestController.java index c1d48b2..d0eb589 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/TestController.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/TestController.java @@ -4,46 +4,48 @@ * 抖音:程序员三丙 * 付费课程知识星球:https://t.zsxq.com/aKtXo */ -package sanbing.jcpp.app.adapter; +package sanbing.jcpp.app.adapter.controller; import com.google.common.collect.Lists; import jakarta.annotation.Resource; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import sanbing.jcpp.app.service.PileProtocolService; -import sanbing.jcpp.proto.gen.ProtocolProto; import sanbing.jcpp.proto.gen.ProtocolProto.*; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** - * @author baigod + * @author 九筒 */ @RestController -public class TestController { +@RequestMapping("/test") +public class TestController extends BaseController { @Resource private PileProtocolService pileProtocolService; - @GetMapping("/api/startCharge") + @GetMapping("/startCharge") public ResponseEntity startCharge() { String orderNo = "ORD" + RandomStringUtils.secure().nextNumeric(20); String logicalCardNo = RandomStringUtils.secure().nextNumeric(12); String physicalCardNo = RandomStringUtils.secure().nextNumeric(12); - pileProtocolService.startCharge("20231212000010", "01", new BigDecimal("50"), orderNo, + pileProtocolService.startCharge("20231212000010", "01", new BigDecimal("50"), orderNo, logicalCardNo, physicalCardNo, null); return ResponseEntity.ok("success"); } - @GetMapping("/api/parallelStartCharge") + @GetMapping("/parallelStartCharge") public ResponseEntity parallelStartCharge() { String orderNo = "PAR" + RandomStringUtils.secure().nextNumeric(20); @@ -51,13 +53,13 @@ public class TestController { String physicalCardNo = RandomStringUtils.secure().nextNumeric(12); String parallelNo = RandomStringUtils.secure().nextNumeric(6); - pileProtocolService.startCharge("20231212000010", "01", new BigDecimal("100"), + pileProtocolService.startCharge("20231212000010", "01", new BigDecimal("100"), orderNo, logicalCardNo, physicalCardNo, parallelNo); return ResponseEntity.ok("success"); } - @GetMapping("/api/stopCharge") + @GetMapping("/stopCharge") public ResponseEntity stopCharge() { pileProtocolService.stopCharge("20231212000010", "01"); @@ -65,7 +67,7 @@ public class TestController { return ResponseEntity.ok("success"); } - @GetMapping("/api/restartPile") + @GetMapping("/restartPile") public ResponseEntity restartPile() { pileProtocolService.restartPile("20231212000010", 1); @@ -73,7 +75,7 @@ public class TestController { return ResponseEntity.ok("success"); } - @GetMapping("/api/setPricing") + @GetMapping("/setPricing") public ResponseEntity setPricing() { String pileCode = "20231212000010"; @@ -159,12 +161,8 @@ public class TestController { flagPriceMap.put(PricingModelFlag.FLAT_VALUE, flagPriceFlat); flagPriceMap.put(PricingModelFlag.VALLEY_VALUE, flagPriceValley); - // 构建 PricingModelProto 对象 - PricingModelProto pricingModel = PricingModelProto.newBuilder() - .setType(PricingModelType.CHARGE) // 设置为充电计费模型 - .setRule(PricingModelRule.SPLIT_TIME) // 使用分时计费规则 - .setStandardElec("1.0") // 标准电费(默认值) - .setStandardServ("0.3") // 标准服务费(默认值) + // 构建峰谷计价配置 + PeakValleyPricingProto peakValleyPricing = PeakValleyPricingProto.newBuilder() .putAllFlagPrice(flagPriceMap) // 设置尖峰平谷对应的价格 .addPeriod(topPeriod1) // 添加尖峰时段1 .addPeriod(topPeriod2) // 添加尖峰时段2 @@ -174,6 +172,13 @@ public class TestController { .addPeriod(flatPeriod2) // 添加平时段2 .addPeriod(valleyPeriod) // 添加谷时段 .build(); + + // 构建 PricingModelProto 对象 + PricingModelProto pricingModel = PricingModelProto.newBuilder() + .setType(PricingModelType.CHARGE) // 设置为充电计费模型 + .setRule(PricingModelRule.PEAK_VALLEY_PRICING) // 使用峰谷计费规则 + .setPeakValleyPricing(peakValleyPricing) // 设置峰谷计价配置 + .build(); pileProtocolService.setPricing(pileCode, SetPricingRequest.newBuilder() @@ -185,11 +190,89 @@ public class TestController { return ResponseEntity.ok("success"); } + @GetMapping("/timePeriodPricing") + public ResponseEntity testTimePeriodPricing() { + String pileCode = "TEST001"; - @GetMapping("/api/otaRequest") + // 创建时段计价列表 + List timePeriodItems = new ArrayList<>(); + + // 深夜时段 (00:00-06:00) + timePeriodItems.add(TimePeriodItemProto.newBuilder() + .setPeriodNo(1) + .setStartTime("00:00:00") + .setEndTime("06:00:00") + .setElecPrice("0.40") + .setServPrice("0.20") + .setDescription("深夜时段") + .build()); + + // 早高峰时段 (06:00-10:00) + timePeriodItems.add(TimePeriodItemProto.newBuilder() + .setPeriodNo(2) + .setStartTime("06:00:00") + .setEndTime("10:00:00") + .setElecPrice("0.80") + .setServPrice("0.50") + .setDescription("早高峰时段") + .build()); + + // 日间平时段 (10:00-18:00) + timePeriodItems.add(TimePeriodItemProto.newBuilder() + .setPeriodNo(3) + .setStartTime("10:00:00") + .setEndTime("18:00:00") + .setElecPrice("0.65") + .setServPrice("0.35") + .setDescription("日间平时段") + .build()); + + // 晚高峰时段 (18:00-22:00) + timePeriodItems.add(TimePeriodItemProto.newBuilder() + .setPeriodNo(4) + .setStartTime("18:00:00") + .setEndTime("22:00:00") + .setElecPrice("0.90") + .setServPrice("0.60") + .setDescription("晚高峰时段") + .build()); + + // 夜间时段 (22:00-24:00) + timePeriodItems.add(TimePeriodItemProto.newBuilder() + .setPeriodNo(5) + .setStartTime("22:00:00") + .setEndTime("23:59:59") + .setElecPrice("0.50") + .setServPrice("0.25") + .setDescription("夜间时段") + .build()); + + // 构建时段计价配置 + TimePeriodPricingProto timePeriodPricing = TimePeriodPricingProto.newBuilder() + .addAllPeriods(timePeriodItems) + .build(); + + // 构建 PricingModelProto 对象 + PricingModelProto pricingModel = PricingModelProto.newBuilder() + .setType(PricingModelType.CHARGE) // 设置为充电计费模型 + .setRule(PricingModelRule.TIME_PERIOD_PRICING) // 使用时段计价规则 + .setTimePeriodPricing(timePeriodPricing) // 设置时段计价配置 + .build(); + + pileProtocolService.setPricing(pileCode, + SetPricingRequest.newBuilder() + .setPileCode(pileCode) + .setPricingId(2000L) + .setPricingModel(pricingModel) + .build()); + + return ResponseEntity.ok("Time period pricing test success"); + } + + @GetMapping("/otaRequest") public ResponseEntity otaRequest() { - pileProtocolService.otaRequest(ProtocolProto.OtaRequest.newBuilder() + pileProtocolService.otaRequest(OtaRequest.newBuilder() .setAddress("127.0.0.1") .setExecutionControl(1) .setDownloadTimeout(1) @@ -205,7 +288,7 @@ public class TestController { return ResponseEntity.ok("success"); } - @GetMapping("/api/offlineCardBalanceUpdateRequest") + @GetMapping("/offlineCardBalanceUpdateRequest") public ResponseEntity offlineCardBalanceUpdateRequest() { pileProtocolService.offlineCardBalanceUpdateRequest(OfflineCardBalanceUpdateRequest.newBuilder() @@ -218,7 +301,7 @@ public class TestController { return ResponseEntity.ok("success"); } - @GetMapping("/api/offlineCardSyncRequest") + @GetMapping("/offlineCardSyncRequest") public ResponseEntity offlineCardSyncRequest() { List cardInfos = Lists.newArrayList(CardInfo.newBuilder().setCardNo("1000000000123456").setLogicCardNo("1000000000123456").build(), @@ -234,7 +317,7 @@ public class TestController { return ResponseEntity.ok("success"); } - @GetMapping("/api/timeSync") + @GetMapping("/timeSync") public ResponseEntity timeSync() { pileProtocolService.timeSync("20231212000010", LocalDateTime.now()); return ResponseEntity.ok("success"); diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/UserController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/UserController.java new file mode 100644 index 0000000..10b68af --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/UserController.java @@ -0,0 +1,55 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import sanbing.jcpp.app.adapter.response.ApiResponse; +import sanbing.jcpp.app.adapter.response.ErrorCode; +import sanbing.jcpp.app.adapter.response.LoginResponse; +import sanbing.jcpp.app.service.security.model.SecurityUser; + +/** + * 用户控制器 + * + * @author 九筒 + */ +@Slf4j +@RestController +@RequestMapping("/api/user") +@RequiredArgsConstructor +public class UserController extends BaseController { + + @GetMapping("/info") + public ResponseEntity> getUserInfo() { + try { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || !(authentication.getPrincipal() instanceof SecurityUser securityUser)) { + return ResponseEntity.status(401).body(ApiResponse.error(ErrorCode.UNAUTHORIZED)); + } + + LoginResponse.UserInfo userInfo = LoginResponse.UserInfo.builder() + .id(securityUser.getId().toString()) + .username(securityUser.getUserName()) + .status(securityUser.isEnabled() ? "ENABLE" : "DISABLE") + .build(); + + return ResponseEntity.ok(ApiResponse.success(userInfo)); + + } catch (Exception e) { + log.error("获取用户信息异常", e); + return ResponseEntity.status(500).body(ApiResponse.error("获取用户信息失败")); + } + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/WebController.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/WebController.java new file mode 100644 index 0000000..c14bc63 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/controller/WebController.java @@ -0,0 +1,29 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * SPA(单页应用)路由控制器 + * 处理所有前端路由,返回index.html + * + * @author 九筒 + */ +@Controller +public class WebController extends BaseController { + + /** + * 处理所有业务页面路由 + * 统一使用 /page/ 前缀,便于扩展管理 + */ + @GetMapping(value = {"/", "/login", "/page/**"}) + public String redirect() { + return "forward:/index.html"; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunCreateRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunCreateRequest.java new file mode 100644 index 0000000..aae3757 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunCreateRequest.java @@ -0,0 +1,36 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import sanbing.jcpp.infrastructure.util.validation.NoXss; + +import java.util.UUID; + +@Data +public class GunCreateRequest { + + @NotBlank(message = "充电枪名称不能为空") + @NoXss + private String gunName; + + @NotBlank(message = "充电枪编号不能为空") + @NoXss + private String gunNo; + + @NotBlank(message = "充电枪编码不能为空") + @NoXss + private String gunCode; + + @NotNull(message = "充电站ID不能为空") + private UUID stationId; + + @NotNull(message = "充电桩ID不能为空") + private UUID pileId; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunQueryRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunQueryRequest.java new file mode 100644 index 0000000..d23866e --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunQueryRequest.java @@ -0,0 +1,27 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +public class GunQueryRequest extends PageRequest { + + private String gunName; + + private String gunNo; + + private String gunCode; + + private UUID stationId; + + private UUID pileId; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunUpdateRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunUpdateRequest.java new file mode 100644 index 0000000..d9c0149 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/GunUpdateRequest.java @@ -0,0 +1,22 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum; +import sanbing.jcpp.infrastructure.util.validation.NoXss; + +@Data +public class GunUpdateRequest { + + @NotBlank(message = "充电枪名称不能为空") + @NoXss + private String gunName; + + private GunRunStatusEnum runStatus; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PageRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PageRequest.java new file mode 100644 index 0000000..3aa4760 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PageRequest.java @@ -0,0 +1,38 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import lombok.Data; + +/** + * 分页查询请求基类 + * + * @author 九筒 + */ +@Data +public class PageRequest { + + private Integer page = 1; // 页码,从1开始 + private Integer size = 10; // 每页大小 + private String sortField; // 排序字段 + private String sortOrder = "desc"; // 排序方向:asc, desc + private String search; // 搜索关键词 + + /** + * 获取MyBatis-Plus的页码(从0开始) + */ + public long getOffset() { + return (long) (page - 1) * size; + } + + /** + * 兼容方法:获取排序字段 + */ + public String getSortBy() { + return sortField; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileCreateRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileCreateRequest.java new file mode 100644 index 0000000..fc9859b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileCreateRequest.java @@ -0,0 +1,45 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum; +import sanbing.jcpp.infrastructure.util.validation.NoXss; + +import java.util.UUID; + +@Data +public class PileCreateRequest { + + @NotBlank(message = "充电桩名称不能为空") + @NoXss + private String pileName; + + @NotBlank(message = "充电桩编码不能为空") + @NoXss + private String pileCode; + + @NotBlank(message = "协议不能为空") + @NoXss + private String protocol; + + @NotNull(message = "充电站ID不能为空") + private UUID stationId; + + @NoXss + private String brand; + + @NoXss + private String model; + + @NoXss + private String manufacturer; + + private PileTypeEnum type = PileTypeEnum.DC; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileQueryRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileQueryRequest.java new file mode 100644 index 0000000..9906a75 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileQueryRequest.java @@ -0,0 +1,37 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum; + +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +public class PileQueryRequest extends PageRequest { + + private String pileName; + + private String pileCode; + + private String protocol; + + private UUID stationId; + + private String brand; + + private String model; + + private String manufacturer; + + private PileTypeEnum type; + + private PileStatusEnum status; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileUpdateRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileUpdateRequest.java new file mode 100644 index 0000000..c6c5f80 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/PileUpdateRequest.java @@ -0,0 +1,35 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum; +import sanbing.jcpp.infrastructure.util.validation.NoXss; + +@Data +public class PileUpdateRequest { + + @NotBlank(message = "充电桩名称不能为空") + @NoXss + private String pileName; + + @NotBlank(message = "协议不能为空") + @NoXss + private String protocol; + + @NoXss + private String brand; + + @NoXss + private String model; + + @NoXss + private String manufacturer; + + private PileTypeEnum type; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationCreateRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationCreateRequest.java new file mode 100644 index 0000000..f3c797e --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationCreateRequest.java @@ -0,0 +1,44 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import sanbing.jcpp.infrastructure.util.validation.NoXss; + + +/** + * 创建充电站请求 + * + * @author 九筒 + */ +@Data +public class StationCreateRequest { + + @NotBlank(message = "充电站名称不能为空") + @NoXss + private String stationName; + + @NotBlank(message = "充电站编码不能为空") + @NoXss + private String stationCode; + + private Float longitude; // 经度 + private Float latitude; // 纬度 + + @NoXss + private String province; // 省份 + + @NoXss + private String city; // 城市 + + @NoXss + private String county; // 区县 + + @NoXss + private String address; // 详细地址 +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationQueryRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationQueryRequest.java new file mode 100644 index 0000000..7d5cb32 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationQueryRequest.java @@ -0,0 +1,29 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import lombok.Data; +import lombok.EqualsAndHashCode; + + +/** + * 充电站查询请求 + * + * @author 九筒 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StationQueryRequest extends PageRequest { + + private String stationName; // 充电站名称 + private String stationCode; // 充电站编码 + private String province; // 省份 + private String city; // 城市 + private String county; // 区县 + private String address; // 详细地址 + private String keyword; // 关键字搜索(站名或编码) +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationUpdateRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationUpdateRequest.java new file mode 100644 index 0000000..7c16acc --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/request/StationUpdateRequest.java @@ -0,0 +1,40 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import sanbing.jcpp.infrastructure.util.validation.NoXss; + + +/** + * 更新充电站请求 + * + * @author 九筒 + */ +@Data +public class StationUpdateRequest { + + @NotBlank(message = "充电站名称不能为空") + @NoXss + private String stationName; + + private Float longitude; // 经度 + private Float latitude; // 纬度 + + @NoXss + private String province; // 省份 + + @NoXss + private String city; // 城市 + + @NoXss + private String county; // 区县 + + @NoXss + private String address; // 详细地址 +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ApiResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ApiResponse.java new file mode 100644 index 0000000..dcd9237 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ApiResponse.java @@ -0,0 +1,73 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 通用API响应结果 + * + * @author 九筒 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ApiResponse { + + private String errorCode; + private String message; + private T data; + private long timestamp; + + public static ApiResponse success(T data) { + return ApiResponse.builder() + .message("操作成功") + .data(data) + .timestamp(System.currentTimeMillis()) + .build(); + } + + public static ApiResponse success(String message, T data) { + return ApiResponse.builder() + .message(message) + .data(data) + .timestamp(System.currentTimeMillis()) + .build(); + } + + public static ApiResponse error(String errorCode, String message) { + return ApiResponse.builder() + .errorCode(errorCode) + .message(message) + .timestamp(System.currentTimeMillis()) + .build(); + } + + public static ApiResponse error(ErrorCode errorCode, String message) { + return ApiResponse.builder() + .errorCode(errorCode.getCode()) + .message(message) + .timestamp(System.currentTimeMillis()) + .build(); + } + + public static ApiResponse error(ErrorCode errorCode) { + return ApiResponse.builder() + .errorCode(errorCode.getCode()) + .message(errorCode.getMessage()) + .timestamp(System.currentTimeMillis()) + .build(); + } + + public static ApiResponse error(String message) { + return error(ErrorCode.BUSINESS_ERROR, message); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/DashboardStats.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/DashboardStats.java new file mode 100644 index 0000000..28ff514 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/DashboardStats.java @@ -0,0 +1,119 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * 仪表盘统计数据 + * + * @author 九筒 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DashboardStats { + + /** + * 总览统计 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Overview { + private Long totalStations; // 总充电站数 + private Long totalPiles; // 总充电桩数 + private Long totalGuns; // 总充电枪数 + } + + /** + * 充电桩在线状态分布 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class PileStatusDistribution { + private Long onlinePiles; // 在线充电桩数 + private Long offlinePiles; // 离线充电桩数 + private Long totalPiles; // 总充电桩数 + + public double getOnlinePercentage() { + return totalPiles > 0 ? (onlinePiles * 100.0) / totalPiles : 0.0; + } + + public double getOfflinePercentage() { + return totalPiles > 0 ? (offlinePiles * 100.0) / totalPiles : 0.0; + } + } + + /** + * 充电枪运行状态分布 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class GunStatusDistribution { + private Long idleGuns; // 空闲 (IDLE) + private Long insertedGuns; // 已插枪未充电 (INSERTED) + private Long chargingGuns; // 充电中 (CHARGING) + private Long chargeCompleteGuns; // 充电完成 (CHARGE_COMPLETE) + private Long dischargeReadyGuns; // 放电准备 (DISCHARGE_READY) + private Long dischargingGuns; // 放电中 (DISCHARGING) + private Long dischargeCompleteGuns; // 放电完成 (DISCHARGE_COMPLETE) + private Long reservedGuns; // 预约 (RESERVED) + private Long faultGuns; // 故障 (FAULT) + private Long totalGuns; // 总充电枪数 + + public double getIdlePercentage() { + return totalGuns > 0 ? (idleGuns * 100.0) / totalGuns : 0.0; + } + + public double getInsertedPercentage() { + return totalGuns > 0 ? (insertedGuns * 100.0) / totalGuns : 0.0; + } + + public double getChargingPercentage() { + return totalGuns > 0 ? (chargingGuns * 100.0) / totalGuns : 0.0; + } + + public double getChargeCompletePercentage() { + return totalGuns > 0 ? (chargeCompleteGuns * 100.0) / totalGuns : 0.0; + } + + public double getDischargeReadyPercentage() { + return totalGuns > 0 ? (dischargeReadyGuns * 100.0) / totalGuns : 0.0; + } + + public double getDischargingPercentage() { + return totalGuns > 0 ? (dischargingGuns * 100.0) / totalGuns : 0.0; + } + + public double getDischargeCompletePercentage() { + return totalGuns > 0 ? (dischargeCompleteGuns * 100.0) / totalGuns : 0.0; + } + + public double getReservedPercentage() { + return totalGuns > 0 ? (reservedGuns * 100.0) / totalGuns : 0.0; + } + + public double getFaultPercentage() { + return totalGuns > 0 ? (faultGuns * 100.0) / totalGuns : 0.0; + } + } + + private Overview overview; + private PileStatusDistribution pileStatusDistribution; + private GunStatusDistribution gunStatusDistribution; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ErrorCode.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ErrorCode.java new file mode 100644 index 0000000..13a0715 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ErrorCode.java @@ -0,0 +1,167 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.response; + +/** + * 统一错误码管理 + * 避免魔法值硬编码,便于维护和扩展 + * + * @author 九筒 + */ +public enum ErrorCode { + + // ==================== 通用错误码 ==================== + /** + * 成功 + */ + SUCCESS("SUCCESS", "操作成功"), + + /** + * 系统异常 + */ + SYSTEM_ERROR("SYSTEM_ERROR", "系统异常,请稍后重试"), + + /** + * 业务异常 + */ + BUSINESS_ERROR("BUSINESS_ERROR", "业务处理失败"), + + // ==================== 参数校验相关 ==================== + /** + * 参数校验失败 + */ + VALIDATION_ERROR("VALIDATION_ERROR", "参数校验失败"), + + /** + * 数据绑定异常 + */ + BINDING_ERROR("BINDING_ERROR", "数据绑定异常"), + + /** + * 非法参数 + */ + ILLEGAL_ARGUMENT("ILLEGAL_ARGUMENT", "参数错误"), + + /** + * 非法状态 + */ + ILLEGAL_STATE("ILLEGAL_STATE", "状态错误"), + + // ==================== 认证授权相关 ==================== + /** + * 未认证 + */ + UNAUTHORIZED("UNAUTHORIZED", "用户未认证"), + + /** + * 认证失败 + */ + AUTH_FAILED("AUTH_FAILED", "用户名或密码错误"), + + /** + * JWT认证失败 + */ + JWT_AUTH_FAILED("JWT_AUTH_FAILED", "JWT Token认证失败"), + + /** + * 权限不足 + */ + FORBIDDEN("FORBIDDEN", "权限不足"), + + // ==================== 资源相关 ==================== + /** + * 资源不存在 + */ + NOT_FOUND("NOT_FOUND", "请求的资源不存在"), + + /** + * 资源冲突 + */ + CONFLICT("CONFLICT", "资源冲突"), + + // ==================== 业务特定错误码 ==================== + /** + * 充电桩编码已存在 + */ + PILE_CODE_EXISTS("PILE_CODE_EXISTS", "充电桩编码已存在"), + + /** + * 充电站名称已存在 + */ + STATION_NAME_EXISTS("STATION_NAME_EXISTS", "充电站名称已存在"), + + /** + * 充电枪编号已存在 + */ + GUN_CODE_EXISTS("GUN_CODE_EXISTS", "充电枪编号已存在"), + + /** + * 充电桩不存在 + */ + PILE_NOT_FOUND("PILE_NOT_FOUND", "充电桩不存在"), + + /** + * 充电站不存在 + */ + STATION_NOT_FOUND("STATION_NOT_FOUND", "充电站不存在"), + + /** + * 充电枪不存在 + */ + GUN_NOT_FOUND("GUN_NOT_FOUND", "充电枪不存在"); + + private final String code; + private final String message; + + ErrorCode(String code, String message) { + this.code = code; + this.message = message; + } + + public String getCode() { + return code; + } + + public String getMessage() { + return message; + } + + /** + * 根据错误码查找枚举 + * + * @param code 错误码 + * @return 对应的枚举,如果不存在返回null + */ + public static ErrorCode fromCode(String code) { + if (code == null) { + return null; + } + + for (ErrorCode errorCode : ErrorCode.values()) { + if (errorCode.getCode().equals(code)) { + return errorCode; + } + } + + return null; + } +} + + + + + + + + + + + + + + + diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/GunWithStatusResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/GunWithStatusResponse.java new file mode 100644 index 0000000..bef7c52 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/GunWithStatusResponse.java @@ -0,0 +1,95 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.response; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * 充电枪响应DTO(包含状态信息) + * + * @author 九筒 + */ +@Data +public class GunWithStatusResponse { + + /** + * 充电枪ID + */ + private UUID id; + + /** + * 创建时间 + */ + private LocalDateTime createdTime; + + /** + * 更新时间 + */ + private LocalDateTime updatedTime; + + /** + * 充电枪名称 + */ + private String gunName; + + /** + * 充电枪编号,不允许修改 + */ + private String gunNo; + + /** + * 充电枪编码,不允许修改 + */ + private String gunCode; + + /** + * 充电站ID + */ + private UUID stationId; + + /** + * 充电桩ID + */ + private UUID pileId; + + /** + * 充电站名称 + */ + private String stationName; + + /** + * 充电桩名称 + */ + private String pileName; + + /** + * 充电桩编码 + */ + private String pileCode; + + /** + * 附加信息 + */ + private JsonNode additionalInfo; + + /** + * 版本号 + */ + private Integer version; + + // ========== 状态信息 ========== + + /** + * 充电枪运行状态 + */ + private GunRunStatusEnum runStatus; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/LoginResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/LoginResponse.java new file mode 100644 index 0000000..480f41b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/LoginResponse.java @@ -0,0 +1,40 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 登录响应DTO + * + * @author 九筒 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LoginResponse { + + private String token; + private String refreshToken; + @Builder.Default + private String tokenType = "Bearer"; + private UserInfo user; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class UserInfo { + private String id; + private String username; + private String status; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PageResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PageResponse.java new file mode 100644 index 0000000..72b47a9 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PageResponse.java @@ -0,0 +1,43 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 分页响应 + * + * @author 九筒 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PageResponse { + + private List records; // 数据列表 + private Long total; // 总记录数 + private Integer page; // 当前页码 + private Integer size; // 每页大小 + private Integer totalPages; // 总页数 + + public static PageResponse of(List records, Long total, Integer page, Integer size) { + int totalPages = (int) Math.ceil((double) total / size); + return PageResponse.builder() + .records(records) + .total(total) + .page(page) + .size(size) + .totalPages(totalPages) + .build(); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileOptionResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileOptionResponse.java new file mode 100644 index 0000000..5eeb564 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileOptionResponse.java @@ -0,0 +1,33 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PileOptionResponse { + + private UUID id; + + private String label; // 显示名称,格式:pileName (pileCode) + + private String pileName; + + private String pileCode; + + private UUID stationId; +} + + diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileWithStatusResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileWithStatusResponse.java new file mode 100644 index 0000000..6a93d8a --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/PileWithStatusResponse.java @@ -0,0 +1,111 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.response; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * 充电桩响应DTO(包含状态信息) + * + * @author 九筒 + */ +@Data +public class PileWithStatusResponse { + + /** + * 充电桩ID + */ + private UUID id; + + /** + * 创建时间 + */ + private LocalDateTime createdTime; + + /** + * 更新时间 + */ + private LocalDateTime updatedTime; + + /** + * 充电桩名称 + */ + private String pileName; + + /** + * 充电桩编码,不允许修改 + */ + private String pileCode; + + /** + * 协议类型 + */ + private String protocol; + + /** + * 充电站ID + */ + private UUID stationId; + + /** + * 品牌 + */ + private String brand; + + /** + * 型号 + */ + private String model; + + /** + * 制造商 + */ + private String manufacturer; + + /** + * 充电桩类型(交流/直流) + */ + private PileTypeEnum type; + + /** + * 附加信息 + */ + private JsonNode additionalInfo; + + /** + * 版本号 + */ + private Integer version; + + // ========== 状态信息 ========== + + /** + * 充电桩状态 + */ + private PileStatusEnum status; + + /** + * 最近连接时间(13位时间戳) + */ + private Long connectedAt; + + /** + * 最后断线时间(13位时间戳) + */ + private Long disconnectedAt; + + /** + * 最后活跃时间(13位时间戳) + */ + private Long lastActiveTime; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ProtocolOption.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ProtocolOption.java new file mode 100644 index 0000000..3cae2b2 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/ProtocolOption.java @@ -0,0 +1,40 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import sanbing.jcpp.protocol.enums.SupportedProtocols; + +/** + * 协议选项响应 + * 用于前端下拉选择组件 + * + * @author 九筒 + * @since 2024-12-22 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ProtocolOption { + + private String value; // 协议标识符(用于表单提交) + private String label; // 显示名称(用于前端显示) + + /** + * 从协议信息创建选项 + * @param protocolInfo 协议信息 + * @return 协议选项 + */ + public static ProtocolOption fromProtocolInfo(SupportedProtocols.ProtocolInfo protocolInfo) { + return new ProtocolOption( + protocolInfo.protocolId(), + protocolInfo.displayName() + ); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/StationOption.java b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/StationOption.java new file mode 100644 index 0000000..adc48e8 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/adapter/response/StationOption.java @@ -0,0 +1,39 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.adapter.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +/** + * 充电站选项响应 + * 用于下拉选择组件 + * + * @author 九筒 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class StationOption { + + private UUID id; // 充电站ID + private String label; // 显示名称:stationName (stationCode) + private String stationName; // 充电站名称 + private String stationCode; // 充电站编码 + + public static StationOption of(UUID id, String stationName, String stationCode) { + StationOption option = new StationOption(); + option.setId(id); + option.setStationName(stationName); + option.setStationCode(stationCode); + option.setLabel(stationName + " (" + stationCode + ")"); + return option; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/AuthorityEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/AuthorityEnum.java new file mode 100644 index 0000000..08b30e5 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/AuthorityEnum.java @@ -0,0 +1,49 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.config.ibatis.enums; + +import com.baomidou.mybatisplus.annotation.IEnum; + +/** + * 用户权限枚举 + * 对应 sanbing.jcpp.app.service.security.model.Authority + * + * @author 九筒 + */ +public enum AuthorityEnum implements IEnum { + + /** + * 系统管理员 + */ + SYS_ADMIN, + + /** + * 刷新令牌 + */ + REFRESH_TOKEN, + ; + + + public static AuthorityEnum parse(String value) { + AuthorityEnum authority = null; + if (value != null && !value.isEmpty()) { + for (AuthorityEnum current : AuthorityEnum.values()) { + if (current.name().equalsIgnoreCase(value)) { + authority = current; + break; + } + } + } + return authority; + } + + @Override + public String getValue() { + return this.name(); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/GunOptStatusEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/GunOptStatusEnum.java deleted file mode 100644 index b0e4513..0000000 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/GunOptStatusEnum.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 开源代码,仅供学习和交流研究使用,商用请联系三丙 - * 微信:mohan_88888 - * 抖音:程序员三丙 - * 付费课程知识星球:https://t.zsxq.com/aKtXo - */ -package sanbing.jcpp.app.dal.config.ibatis.enums; - -import com.baomidou.mybatisplus.annotation.IEnum; - -/** - * @author baigod - */ -public enum GunOptStatusEnum implements IEnum { - AVAILABLE, // 可用状态 - IN_MAINTENANCE, // 维护中状态 - OUT_OF_SERVICE, // 停用状态 - RESERVED; // 已预约状态 - - @Override - public String getValue() { - return name(); - } -} \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/GunRunStatusEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/GunRunStatusEnum.java index 7976005..f3d7b52 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/GunRunStatusEnum.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/GunRunStatusEnum.java @@ -9,17 +9,17 @@ package sanbing.jcpp.app.dal.config.ibatis.enums; import com.baomidou.mybatisplus.annotation.IEnum; /** - * @author baigod + * @author 九筒 */ public enum GunRunStatusEnum implements IEnum { IDLE, // 空闲 - INSERTED, // 已插枪 - CHARGING, // 充电中 - CHARGE_COMPLETE, // 充电完成 + INSERTED, // 已插枪 占用(未充电) + CHARGING, // 充电中 占用(充电中) + CHARGE_COMPLETE, // 充电完成 占用(预约锁定) DISCHARGE_READY, // 放电准备 DISCHARGING, // 放电中 DISCHARGE_COMPLETE, // 放电完成 - RESERVED, // 预约 + RESERVED, // 预约 占用(预约锁定) FAULT; // 故障 @Override diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OrderStatusEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OrderStatusEnum.java deleted file mode 100644 index 0b6197d..0000000 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OrderStatusEnum.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 开源代码,仅供学习和交流研究使用,商用请联系三丙 - * 微信:mohan_88888 - * 抖音:程序员三丙 - * 付费课程知识星球:https://t.zsxq.com/aKtXo - */ -package sanbing.jcpp.app.dal.config.ibatis.enums; - -import com.baomidou.mybatisplus.annotation.IEnum; - -public enum OrderStatusEnum implements IEnum { - PENDING, - IN_CHARGING, - COMPLETED, - CANCELLED, - TERMINATED, - FAILED, - REFUNDED; - - - @Override - public String getValue() { - return name(); - } - -} \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OrderTypeEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OrderTypeEnum.java deleted file mode 100644 index f416ce7..0000000 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OrderTypeEnum.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 开源代码,仅供学习和交流研究使用,商用请联系三丙 - * 微信:mohan_88888 - * 抖音:程序员三丙 - * 付费课程知识星球:https://t.zsxq.com/aKtXo - */ -package sanbing.jcpp.app.dal.config.ibatis.enums; - -import com.baomidou.mybatisplus.annotation.IEnum; - -/** - * @author baigod - */ -public enum OrderTypeEnum implements IEnum { - CHARGE, - - DISCHARGE; - - @Override - public String getValue() { - return name(); - } -} \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OwnerTypeEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OwnerTypeEnum.java deleted file mode 100644 index f74c0ab..0000000 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/OwnerTypeEnum.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 开源代码,仅供学习和交流研究使用,商用请联系三丙 - * 微信:mohan_88888 - * 抖音:程序员三丙 - * 付费课程知识星球:https://t.zsxq.com/aKtXo - */ -package sanbing.jcpp.app.dal.config.ibatis.enums; - -import com.baomidou.mybatisplus.annotation.IEnum; - -/** - * @author baigod - */ -public enum OwnerTypeEnum implements IEnum { - C, - B, - G; - - @Override - public String getValue() { - return name(); - } -} \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/PileStatusEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/PileStatusEnum.java index 6bde772..e7f420d 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/PileStatusEnum.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/PileStatusEnum.java @@ -9,14 +9,39 @@ package sanbing.jcpp.app.dal.config.ibatis.enums; import com.baomidou.mybatisplus.annotation.IEnum; /** - * @author baigod + * 充电桩状态枚举 - 简化版本,只维护在线/离线状态 + *

+ * 设计原则: + * - 充电桩状态独立于充电枪状态,不受枪的工作状态影响 + * - 只关注设备的网络连接状态和基础可用性 + * - 充电枪的具体工作状态通过GunRunStatusEnum单独维护 + *

+ * 状态转换场景: + * 1. 设备登录成功 → ONLINE + * 2. 设备心跳正常 → 保持ONLINE + * 3. 设备断开连接 → OFFLINE + * 4. 设备超时无响应 → OFFLINE + * 5. 系统重启后清洗 → 根据连接状态决定ONLINE/OFFLINE + * + * @author 九筒 */ public enum PileStatusEnum implements IEnum { - IDLE, // 空闲 - WORKING, // 工作中 - FAULT, // 故障 - MAINTENANCE, // 维护中 - OFFLINE, // 离线 + /** + * 在线状态:设备已连接并能正常通信 + * - 设备登录成功 + * - 心跳正常 + * - 能接收和响应指令 + */ + ONLINE, + + /** + * 离线状态:设备未连接或无法通信 + * - 设备未登录 + * - 网络连接断开 + * - 心跳超时 + * - 系统重启后未重新连接 + */ + OFFLINE, ; @Override diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/PileTypeEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/PileTypeEnum.java index ddd6115..794262c 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/PileTypeEnum.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/PileTypeEnum.java @@ -9,7 +9,7 @@ package sanbing.jcpp.app.dal.config.ibatis.enums; import com.baomidou.mybatisplus.annotation.IEnum; /** - * @author baigod + * @author 九筒 */ public enum PileTypeEnum implements IEnum { AC, // 交流充电桩 diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/StationStatusEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/StationStatusEnum.java deleted file mode 100644 index 4eec4fd..0000000 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/StationStatusEnum.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 开源代码,仅供学习和交流研究使用,商用请联系三丙 - * 微信:mohan_88888 - * 抖音:程序员三丙 - * 付费课程知识星球:https://t.zsxq.com/aKtXo - */ -package sanbing.jcpp.app.dal.config.ibatis.enums; - -import com.baomidou.mybatisplus.annotation.IEnum; - -/** - * @author baigod - */ -public enum StationStatusEnum implements IEnum { - OPERATIONAL, // 正常运营 - PARTIAL_FAILURE, // 部分故障 - FULLY_LOADED, // 满载 - MAINTENANCE, // 维护中 - CLOSED, // 关闭 - WAITING_FOR_OPEN; // 待开放 - - @Override - public String getValue() { - return name(); - } - - -} \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/UserStatusEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/UserStatusEnum.java index cbbc084..48d754f 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/UserStatusEnum.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/enums/UserStatusEnum.java @@ -9,7 +9,7 @@ package sanbing.jcpp.app.dal.config.ibatis.enums; import com.baomidou.mybatisplus.annotation.IEnum; /** - * @author baigod + * @author 九筒 */ public enum UserStatusEnum implements IEnum { ENABLE, diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/typehandlers/UserCredentialsTypeHandler.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/typehandlers/UserCredentialsTypeHandler.java new file mode 100644 index 0000000..eba8807 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/config/ibatis/typehandlers/UserCredentialsTypeHandler.java @@ -0,0 +1,91 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.config.ibatis.typehandlers; + +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedTypes; +import org.postgresql.util.PGobject; +import sanbing.jcpp.app.service.security.model.UserCredentials; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * UserCredentials 类型处理器 + * 负责 PostgreSQL JSONB 和 UserCredentials 对象之间的转换 + * + * @author 九筒 + */ +@Slf4j +@MappedTypes({UserCredentials.class}) +public class UserCredentialsTypeHandler extends BaseTypeHandler { + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, UserCredentials parameter, JdbcType jdbcType) throws SQLException { + if (ps == null) { + throw new SQLException("PreparedStatement cannot be null"); + } + + if (parameter != null) { + try { + PGobject jsonObject = new PGobject(); + jsonObject.setType("jsonb"); + jsonObject.setValue(JacksonUtil.toString(parameter)); + ps.setObject(i, jsonObject); + log.debug("Set UserCredentials parameter at index {}: failedLoginAttempts={}", i, parameter.getFailedLoginAttempts()); + } catch (Exception e) { + log.error("Failed to serialize UserCredentials to JSONB", e); + throw new SQLException("Failed to serialize UserCredentials", e); + } + } else { + ps.setNull(i, java.sql.Types.OTHER); + } + } + + @Override + public UserCredentials getNullableResult(ResultSet rs, String columnName) throws SQLException { + return parseUserCredentials(rs.getString(columnName), columnName); + } + + @Override + public UserCredentials getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + return parseUserCredentials(rs.getString(columnIndex), "column_" + columnIndex); + } + + @Override + public UserCredentials getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + return parseUserCredentials(cs.getString(columnIndex), "column_" + columnIndex); + } + + /** + * 解析 JSON 字符串为 UserCredentials 对象 + */ + private UserCredentials parseUserCredentials(String jsonString, String columnIdentifier) { + if (jsonString == null || jsonString.trim().isEmpty()) { + log.debug("UserCredentials JSON is null or empty for {}", columnIdentifier); + return null; + } + + try { + UserCredentials userCredentials = JacksonUtil.fromString(jsonString, UserCredentials.class); + if (userCredentials != null) { + log.debug("Parsed UserCredentials from {}: failedLoginAttempts={}", + columnIdentifier, userCredentials.getFailedLoginAttempts()); + } + return userCredentials; + } catch (Exception e) { + log.error("Failed to parse UserCredentials from JSON: {} for {}", jsonString, columnIdentifier, e); + // 返回 null 而不是抛出异常,避免查询失败 + return null; + } + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Attribute.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Attribute.java new file mode 100644 index 0000000..cb13d5f --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Attribute.java @@ -0,0 +1,102 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import sanbing.jcpp.app.data.kv.*; +import sanbing.jcpp.infrastructure.cache.HasVersion; + +import java.io.Serializable; +import java.util.UUID; + +/** + * 属性实体,用于存储设备的最新属性数据 + * 采用键值对存储结构设计 + * + * @author 九筒 + */ +@Data +@TableName("t_attr") +public class Attribute implements Serializable, HasVersion { + + /** + * 实体ID (UUID保证全局唯一) + * 复合主键的一部分 + */ + @TableId(value = "entity_id", type = IdType.INPUT) + private UUID entityId; + + /** + * 属性键 (字符串类型提高可读性) + * 复合主键的一部分 + */ + @TableField("attr_key") + private String attrKey; + + /** + * 布尔值 + */ + @TableField("bool_v") + private Boolean boolV; + + /** + * 字符串值 + */ + @TableField("str_v") + private String strV; + + /** + * 长整型值 + */ + @TableField("long_v") + private Long longV; + + /** + * 双精度值 + */ + @TableField("dbl_v") + private Double dblV; + + /** + * JSON值 + */ + @TableField("json_v") + private String jsonV; + + /** + * 最后更新时间戳 + */ + @TableField("last_update_ts") + private Long lastUpdateTs; + + /** + * 版本号(用于乐观锁控制) + */ + @TableField + private Integer version; + + public AttributeKvEntry toData() { + KvEntry kvEntry = null; + if (strV != null) { + kvEntry = new StringDataEntry(attrKey, strV); + } else if (boolV != null) { + kvEntry = new BooleanDataEntry(attrKey, boolV); + } else if (dblV != null) { + kvEntry = new DoubleDataEntry(attrKey, dblV); + } else if (longV != null) { + kvEntry = new LongDataEntry(attrKey, longV); + } else if (jsonV != null) { + kvEntry = new JsonDataEntry(attrKey, jsonV); + } + + return new BaseAttributeKvEntry(kvEntry, lastUpdateTs, version); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Gun.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Gun.java index 4ed2831..2995fe0 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Gun.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Gun.java @@ -14,10 +14,8 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import sanbing.jcpp.app.dal.config.ibatis.enums.GunOptStatusEnum; -import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum; -import sanbing.jcpp.app.dal.config.ibatis.enums.OwnerTypeEnum; import sanbing.jcpp.infrastructure.cache.HasVersion; +import sanbing.jcpp.infrastructure.util.validation.NoXss; import java.io.Serializable; import java.time.LocalDateTime; @@ -25,7 +23,7 @@ import java.util.UUID; @Data -@TableName("jcpp_gun") +@TableName("t_gun") @Builder @AllArgsConstructor @NoArgsConstructor @@ -36,28 +34,23 @@ public class Gun implements Serializable, HasVersion { private LocalDateTime createdTime; + private LocalDateTime updatedTime; + private JsonNode additionalInfo; + @NoXss private String gunNo; + @NoXss private String gunName; + @NoXss private String gunCode; private UUID stationId; private UUID pileId; - private UUID ownerId; - - private OwnerTypeEnum ownerType; - - private GunRunStatusEnum runStatus; - - private LocalDateTime runStatusUpdatedTime; - - private GunOptStatusEnum optStatus; - private Integer version; } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Order.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Order.java deleted file mode 100644 index 425af71..0000000 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Order.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 开源代码,仅供学习和交流研究使用,商用请联系三丙 - * 微信:mohan_88888 - * 抖音:程序员三丙 - * 付费课程知识星球:https://t.zsxq.com/aKtXo - */ -package sanbing.jcpp.app.dal.entity; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import sanbing.jcpp.app.dal.config.ibatis.enums.OrderStatusEnum; -import sanbing.jcpp.app.dal.config.ibatis.enums.OrderTypeEnum; - -import java.io.Serializable; -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.UUID; - - -@Data -@TableName("jcpp_order") -@Builder -@AllArgsConstructor -@NoArgsConstructor -public class Order implements Serializable { - - @TableId(type = IdType.INPUT) - private UUID id; - - private String internalOrderNo; - - private String externalOrderNo; - - private String pileOrderNo; - - private LocalDateTime createdTime; - - private JsonNode additionalInfo; - - private LocalDateTime updatedTime; - - private LocalDateTime cancelledTime; - - private OrderStatusEnum status; - - private OrderTypeEnum type; - - private UUID creatorId; - - private UUID stationId; - - private UUID pileId; - - private UUID gunId; - - private String plateNo; - - private BigDecimal settlementAmount; - - private JsonNode settlementDetails; - - private BigDecimal electricityQuantity; - - -} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java index 114a552..6908f6e 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Pile.java @@ -14,17 +14,16 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import sanbing.jcpp.app.dal.config.ibatis.enums.OwnerTypeEnum; -import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum; import sanbing.jcpp.infrastructure.cache.HasVersion; +import sanbing.jcpp.infrastructure.util.validation.NoXss; import java.io.Serializable; import java.time.LocalDateTime; import java.util.UUID; @Data -@TableName(value = "jcpp_pile", autoResultMap = true) +@TableName(value = "t_pile", autoResultMap = true) @Builder @AllArgsConstructor @NoArgsConstructor @@ -35,28 +34,30 @@ public class Pile implements Serializable, HasVersion { private LocalDateTime createdTime; + private LocalDateTime updatedTime; + private JsonNode additionalInfo; + @NoXss private String pileName; + @NoXss private String pileCode; + @NoXss private String protocol; private UUID stationId; - private UUID ownerId; - - private OwnerTypeEnum ownerType; - + @NoXss private String brand; + @NoXss private String model; + @NoXss private String manufacturer; - private PileStatusEnum status; - private PileTypeEnum type; private Integer version; diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Station.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Station.java index c24c880..0fca715 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Station.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/Station.java @@ -14,9 +14,8 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import sanbing.jcpp.app.dal.config.ibatis.enums.OwnerTypeEnum; -import sanbing.jcpp.app.dal.config.ibatis.enums.StationStatusEnum; import sanbing.jcpp.infrastructure.cache.HasVersion; +import sanbing.jcpp.infrastructure.util.validation.NoXss; import java.io.Serializable; import java.time.LocalDateTime; @@ -24,7 +23,7 @@ import java.util.UUID; @Data -@TableName("jcpp_station") +@TableName("t_station") @Builder @AllArgsConstructor @NoArgsConstructor @@ -35,30 +34,32 @@ public class Station implements Serializable, HasVersion { private LocalDateTime createdTime; + private LocalDateTime updatedTime; + private JsonNode additionalInfo; + @NoXss private String stationName; + @NoXss private String stationCode; - private UUID ownerId; - private Float longitude; private Float latitude; - private OwnerTypeEnum ownerType; - + @NoXss private String province; + @NoXss private String city; + @NoXss private String county; + @NoXss private String address; - private StationStatusEnum status; - private Integer version; } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/User.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/User.java index ef08893..804f0a7 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/User.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/entity/User.java @@ -7,6 +7,7 @@ package sanbing.jcpp.app.dal.entity; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.databind.JsonNode; @@ -14,7 +15,10 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import sanbing.jcpp.app.dal.config.ibatis.enums.AuthorityEnum; import sanbing.jcpp.app.dal.config.ibatis.enums.UserStatusEnum; +import sanbing.jcpp.app.dal.config.ibatis.typehandlers.UserCredentialsTypeHandler; +import sanbing.jcpp.app.service.security.model.UserCredentials; import sanbing.jcpp.infrastructure.cache.HasVersion; import java.io.Serializable; @@ -23,24 +27,45 @@ import java.util.UUID; @Data -@TableName("jcpp_user") +@TableName("t_user") @Builder @AllArgsConstructor @NoArgsConstructor public class User implements Serializable, HasVersion { + public User(UUID id) { + this.id = id; + } + + public User(User user) { + this.id = user.getId(); + this.createdTime = user.getCreatedTime(); + this.updatedTime = user.getUpdatedTime(); + this.additionalInfo = user.getAdditionalInfo(); + this.status = user.getStatus(); + this.userName = user.getUserName(); + this.userCredentials = user.getUserCredentials(); + this.authority = user.getAuthority(); + this.version = user.getVersion(); + } + @TableId(type = IdType.INPUT) private UUID id; private LocalDateTime createdTime; + private LocalDateTime updatedTime; + private JsonNode additionalInfo; private UserStatusEnum status; private String userName; - private JsonNode userCredentials; + @TableField(typeHandler = UserCredentialsTypeHandler.class) + private UserCredentials userCredentials; + + private AuthorityEnum authority; private Integer version; diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/AttributeMapper.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/AttributeMapper.java new file mode 100644 index 0000000..2840042 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/AttributeMapper.java @@ -0,0 +1,53 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import sanbing.jcpp.app.dal.entity.Attribute; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +/** + * 属性数据访问层 + * + * @author 九筒 + */ +@Mapper +public interface AttributeMapper extends BaseMapper { + + /** + * 查询实体的所有属性 + */ + List findByEntity(@Param("entityId") UUID entityId); + + /** + * 查询实体的特定属性 + */ + Attribute findByEntityAndKey(@Param("entityId") UUID entityId, @Param("attrKey") String attrKey); + + /** + * 查询实体在指定属性类型下的所有属性 (兼容原JPA方法) + * 注意:此方法主要用于兼容性,实际t_attr表中没有attribute_type字段 + */ + List findAllByEntityIdAndAttributeType(@Param("entityId") UUID entityId); + + /** + * 删除指定实体的指定属性 + */ + void deleteByEntityIdAndKey(@Param("entityId") UUID entityId, + @Param("attrKey") String attrKey); + + /** + * 根据实体ID和属性键列表查询属性 + * + */ + List findAllByIdAndAttrKey(UUID entityId, Collection attrKeys); +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/GunMapper.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/GunMapper.java index cdfde34..c925933 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/GunMapper.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/GunMapper.java @@ -7,10 +7,117 @@ package sanbing.jcpp.app.dal.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import sanbing.jcpp.app.adapter.request.GunQueryRequest; +import sanbing.jcpp.app.adapter.response.GunWithStatusResponse; import sanbing.jcpp.app.dal.entity.Gun; +import java.util.UUID; + /** - * @author baigod + * @author 九筒 */ public interface GunMapper extends BaseMapper { + + /** + * 根据充电桩编码和充电枪编码查询充电枪 + */ + Gun selectByPileCodeAndGunCode(@Param("pileCode") String pileCode, @Param("gunCode") String gunCode); + + /** + * 分页查询充电枪及其状态信息 + * 使用MyBatis XML配置,避免魔法值错误,提高SQL可读性和可维护性 + */ + IPage selectGunWithStatusPage(Page page, @Param("request") GunQueryRequest request); + + /** + * 统计充电桩下的充电枪数量 + * + * @param pileId 充电桩ID + * @return 充电枪数量 + */ + long countByPileId(@Param("pileId") UUID pileId); + + /** + * 统计空闲状态的充电枪数量 (IDLE) + * + * @param statusKey 状态属性键 + * @param status 状态值 + * @return 空闲充电枪数量 + */ + long countIdleGuns(@Param("statusKey") String statusKey, @Param("status") String status); + + /** + * 统计已插枪未充电状态的充电枪数量 (INSERTED) + * + * @param statusKey 状态属性键 + * @param status 状态值 + * @return 已插枪未充电充电枪数量 + */ + long countInsertedGuns(@Param("statusKey") String statusKey, @Param("status") String status); + + /** + * 统计充电中状态的充电枪数量 (CHARGING) + * + * @param statusKey 状态属性键 + * @param status 状态值 + * @return 充电中充电枪数量 + */ + long countChargingGuns(@Param("statusKey") String statusKey, @Param("status") String status); + + /** + * 统计充电完成状态的充电枪数量 (CHARGE_COMPLETE) + * + * @param statusKey 状态属性键 + * @param status 状态值 + * @return 充电完成充电枪数量 + */ + long countChargeCompleteGuns(@Param("statusKey") String statusKey, @Param("status") String status); + + /** + * 统计放电准备状态的充电枪数量 (DISCHARGE_READY) + * + * @param statusKey 状态属性键 + * @param status 状态值 + * @return 放电准备充电枪数量 + */ + long countDischargeReadyGuns(@Param("statusKey") String statusKey, @Param("status") String status); + + /** + * 统计放电中状态的充电枪数量 (DISCHARGING) + * + * @param statusKey 状态属性键 + * @param status 状态值 + * @return 放电中充电枪数量 + */ + long countDischargingGuns(@Param("statusKey") String statusKey, @Param("status") String status); + + /** + * 统计放电完成状态的充电枪数量 (DISCHARGE_COMPLETE) + * + * @param statusKey 状态属性键 + * @param status 状态值 + * @return 放电完成充电枪数量 + */ + long countDischargeCompleteGuns(@Param("statusKey") String statusKey, @Param("status") String status); + + /** + * 统计预约状态的充电枪数量 (RESERVED) + * + * @param statusKey 状态属性键 + * @param status 状态值 + * @return 预约充电枪数量 + */ + long countReservedGuns(@Param("statusKey") String statusKey, @Param("status") String status); + + /** + * 统计故障状态的充电枪数量 (FAULT) + * + * @param statusKey 状态属性键 + * @param status 状态值 + * @return 故障充电枪数量 + */ + long countFaultGuns(@Param("statusKey") String statusKey, @Param("status") String status); } \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/OrderMapper.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/OrderMapper.java deleted file mode 100644 index f9bf5fa..0000000 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/OrderMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -/** - * 开源代码,仅供学习和交流研究使用,商用请联系三丙 - * 微信:mohan_88888 - * 抖音:程序员三丙 - * 付费课程知识星球:https://t.zsxq.com/aKtXo - */ -package sanbing.jcpp.app.dal.mapper; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import sanbing.jcpp.app.dal.entity.Order; - -/** - * @author baigod - */ -public interface OrderMapper extends BaseMapper { -} \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/PileMapper.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/PileMapper.java index 32ed9c7..b548580 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/PileMapper.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/PileMapper.java @@ -7,19 +7,72 @@ package sanbing.jcpp.app.dal.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.apache.ibatis.annotations.Select; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import sanbing.jcpp.app.adapter.request.PileQueryRequest; +import sanbing.jcpp.app.adapter.response.PileWithStatusResponse; import sanbing.jcpp.app.dal.entity.Pile; +import sanbing.jcpp.app.data.kv.AttrKeyEnum; + +import java.util.UUID; /** - * @author baigod + * @author 九筒 */ public interface PileMapper extends BaseMapper { - @Select("SELECT " + - " * " + - "FROM " + - " jcpp_pile " + - "WHERE " + - " pile_code = #{pileCode}") + /** + * 根据充电桩编码查询充电桩 + * + * @param pileCode 充电桩编码 + * @return 充电桩实体 + */ Pile selectByCode(String pileCode); + + /** + * 分页查询充电桩及其状态信息 + * 使用MyBatis XML配置,避免魔法值错误,提高SQL可读性和可维护性 + * + * @param page 分页参数 + * @param request 查询请求参数 + * @param statusKey 状态属性键 + * @param connectedAtKey 连接时间属性键 + * @param disconnectedAtKey 断开连接时间属性键 + * @param lastActiveTimeKey 最后活跃时间属性键 + */ + IPage selectPileWithStatusPage( + Page page, + @Param("request") PileQueryRequest request, + @Param("statusKey") AttrKeyEnum statusKey, + @Param("connectedAtKey") AttrKeyEnum connectedAtKey, + @Param("disconnectedAtKey") AttrKeyEnum disconnectedAtKey, + @Param("lastActiveTimeKey") AttrKeyEnum lastActiveTimeKey + ); + + /** + * 统计充电站下的充电桩数量 + * + * @param stationId 充电站ID + * @return 充电桩数量 + */ + long countByStationId(@Param("stationId") UUID stationId); + + /** + * 统计在线充电桩数量 + * + * @param statusKey 状态属性键 + * @param onlineStatus 在线状态值 + * @return 在线充电桩数量 + */ + long countOnlinePiles(@Param("statusKey") String statusKey, @Param("onlineStatus") String onlineStatus); + + /** + * 统计离线充电桩数量(包括未设置状态的) + * + * @param statusKey 状态属性键 + * @param offlineStatus 离线状态值 + * @return 离线充电桩数量 + */ + long countOfflinePiles(@Param("statusKey") String statusKey, @Param("offlineStatus") String offlineStatus); } \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/StationMapper.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/StationMapper.java index 8546fa4..d68ee59 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/StationMapper.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/StationMapper.java @@ -10,7 +10,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import sanbing.jcpp.app.dal.entity.Station; /** - * @author baigod + * @author 九筒 */ public interface StationMapper extends BaseMapper { } \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/UserMapper.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/UserMapper.java index 068c13d..d49c6ff 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/UserMapper.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/mapper/UserMapper.java @@ -7,10 +7,24 @@ package sanbing.jcpp.app.dal.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; import sanbing.jcpp.app.dal.entity.User; /** - * @author baigod + * @author 九筒 */ public interface UserMapper extends BaseMapper { + + /** + * 根据用户名查找用户(默认不区分大小写) + */ + @Select("SELECT * FROM t_user WHERE LOWER(user_name) = LOWER(#{userName})") + User findByUserName(@Param("userName") String userName); + + /** + * 检查用户名是否已存在(默认不区分大小写) + */ + @Select("SELECT COUNT(*) FROM t_user WHERE LOWER(user_name) = LOWER(#{userName})") + int countByUserName(@Param("userName") String userName); } \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/GunRepository.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/GunRepository.java new file mode 100644 index 0000000..0b5003c --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/GunRepository.java @@ -0,0 +1,36 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository; + +import sanbing.jcpp.app.dal.entity.Gun; + +import java.util.UUID; + +/** + * 充电枪数据访问接口 + * + * @author 九筒 + */ +public interface GunRepository { + + /** + * 根据充电桩编码和充电枪编码查询充电枪 + * + * @param pileCode 充电桩编码 + * @param gunCode 充电枪编码 + * @return 充电枪实体,如果不存在返回null + */ + Gun findByPileCodeAndGunCode(String pileCode, String gunCode); + + /** + * 根据充电枪ID查询充电枪 + * + * @param gunId 充电枪ID + * @return 充电枪实体,如果不存在返回null + */ + Gun findById(UUID gunId); +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/repository/PileRepository.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/PileRepository.java similarity index 84% rename from jcpp-app/src/main/java/sanbing/jcpp/app/repository/PileRepository.java rename to jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/PileRepository.java index deaee25..f1848de 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/repository/PileRepository.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/PileRepository.java @@ -4,12 +4,12 @@ * 抖音:程序员三丙 * 付费课程知识星球:https://t.zsxq.com/aKtXo */ -package sanbing.jcpp.app.repository; +package sanbing.jcpp.app.dal.repository; import sanbing.jcpp.app.dal.entity.Pile; /** - * @author baigod + * @author 九筒 */ public interface PileRepository { diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/AttributeKvInsertRepository.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/AttributeKvInsertRepository.java new file mode 100644 index 0000000..910610a --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/AttributeKvInsertRepository.java @@ -0,0 +1,225 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.attribute; + +import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.jdbc.core.SqlProvider; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.support.TransactionTemplate; +import sanbing.jcpp.app.dal.entity.Attribute; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +@Repository +public class AttributeKvInsertRepository { + + private static final ThreadLocal PATTERN_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pattern.compile(String.valueOf(Character.MIN_VALUE))); + private static final String EMPTY_STR = ""; + + @Value("${sql.remove_null_chars:true}") + private boolean removeNullChars; + + @Resource + protected JdbcTemplate jdbcTemplate; + + @Resource + protected TransactionTemplate transactionTemplate; + + private static final String BATCH_UPDATE = "UPDATE t_attr SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, json_v = cast(? AS json), last_update_ts = ?, version = nextval('attr_kv_version_seq') " + + "WHERE entity_id = ? and attr_key = ? RETURNING version;"; + + private static final String INSERT_OR_UPDATE = + "INSERT INTO t_attr (entity_id, attr_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts, version) " + + "VALUES(?, ?, ?, ?, ?, ?, cast(? AS json), ?, nextval('attr_kv_version_seq')) " + + "ON CONFLICT (entity_id, attr_key) " + + "DO UPDATE SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, json_v = cast(? AS json), last_update_ts = ?, version = nextval('attr_kv_version_seq') RETURNING version;"; + + // 合并自 AbstractInsertRepository 的方法 + protected String replaceNullChars(String strValue) { + if (removeNullChars && strValue != null) { + return PATTERN_THREAD_LOCAL.get().matcher(strValue).replaceAll(EMPTY_STR); + } + return strValue; + } + + // 合并自 AbstractVersionedInsertRepository 的方法 + public List saveOrUpdate(List entities) { + return transactionTemplate.execute(status -> { + List seqNumbers = new ArrayList<>(entities.size()); + + KeyHolder keyHolder = new GeneratedKeyHolder(); + + int[] updateResult = onBatchUpdate(entities, keyHolder); + + List> seqNumbersList = keyHolder.getKeyList(); + + int notUpdatedCount = entities.size() - seqNumbersList.size(); + + List toInsertIndexes = new ArrayList<>(notUpdatedCount); + List insertEntities = new ArrayList<>(notUpdatedCount); + for (int i = 0, keyHolderIndex = 0; i < updateResult.length; i++) { + if (updateResult[i] == 0) { + insertEntities.add(entities.get(i)); + seqNumbers.add(null); + toInsertIndexes.add(i); + } else { + seqNumbers.add((Integer) seqNumbersList.get(keyHolderIndex).get("version")); + keyHolderIndex++; + } + } + + if (insertEntities.isEmpty()) { + return seqNumbers; + } + + int[] insertResult = onInsertOrUpdate(insertEntities, keyHolder); + + seqNumbersList = keyHolder.getKeyList(); + + for (int i = 0, keyHolderIndex = 0; i < insertResult.length; i++) { + if (insertResult[i] != 0) { + seqNumbers.set(toInsertIndexes.get(i), (Integer) seqNumbersList.get(keyHolderIndex).get("version")); + keyHolderIndex++; + } + } + + return seqNumbers; + }); + } + + private int[] onBatchUpdate(List entities, KeyHolder keyHolder) { + return jdbcTemplate.batchUpdate(new SequencePreparedStatementCreator(getBatchUpdateQuery()), new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + setOnBatchUpdateValues(ps, i, entities); + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }, keyHolder); + } + + private int[] onInsertOrUpdate(List insertEntities, KeyHolder keyHolder) { + return jdbcTemplate.batchUpdate(new SequencePreparedStatementCreator(getInsertOrUpdateQuery()), new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + setOnInsertOrUpdateValues(ps, i, insertEntities); + } + + @Override + public int getBatchSize() { + return insertEntities.size(); + } + }, keyHolder); + } + + protected void setOnBatchUpdateValues(PreparedStatement ps, int i, List entities) throws SQLException { + Attribute kvEntity = entities.get(i); + ps.setString(1, replaceNullChars(kvEntity.getStrV())); + + if (kvEntity.getLongV() != null) { + ps.setLong(2, kvEntity.getLongV()); + } else { + ps.setNull(2, Types.BIGINT); + } + + if (kvEntity.getDblV() != null) { + ps.setDouble(3, kvEntity.getDblV()); + } else { + ps.setNull(3, Types.DOUBLE); + } + + if (kvEntity.getBoolV() != null) { + ps.setBoolean(4, kvEntity.getBoolV()); + } else { + ps.setNull(4, Types.BOOLEAN); + } + + ps.setString(5, replaceNullChars(kvEntity.getJsonV())); + + ps.setLong(6, kvEntity.getLastUpdateTs()); + ps.setObject(7, kvEntity.getEntityId()); + ps.setString(8, kvEntity.getAttrKey()); + } + + protected void setOnInsertOrUpdateValues(PreparedStatement ps, int i, List insertEntities) throws SQLException { + Attribute kvEntity = insertEntities.get(i); + ps.setObject(1, kvEntity.getEntityId()); + ps.setString(2, kvEntity.getAttrKey()); + + ps.setString(3, replaceNullChars(kvEntity.getStrV())); + ps.setString(9, replaceNullChars(kvEntity.getStrV())); + + if (kvEntity.getLongV() != null) { + ps.setLong(4, kvEntity.getLongV()); + ps.setLong(10, kvEntity.getLongV()); + } else { + ps.setNull(4, Types.BIGINT); + ps.setNull(10, Types.BIGINT); + } + + if (kvEntity.getDblV() != null) { + ps.setDouble(5, kvEntity.getDblV()); + ps.setDouble(11, kvEntity.getDblV()); + } else { + ps.setNull(5, Types.DOUBLE); + ps.setNull(11, Types.DOUBLE); + } + + if (kvEntity.getBoolV() != null) { + ps.setBoolean(6, kvEntity.getBoolV()); + ps.setBoolean(12, kvEntity.getBoolV()); + } else { + ps.setNull(6, Types.BOOLEAN); + ps.setNull(12, Types.BOOLEAN); + } + + ps.setString(7, replaceNullChars(kvEntity.getJsonV())); + ps.setString(13, replaceNullChars(kvEntity.getJsonV())); + + ps.setLong(8, kvEntity.getLastUpdateTs()); + ps.setLong(14, kvEntity.getLastUpdateTs()); + } + + protected String getBatchUpdateQuery() { + return BATCH_UPDATE; + } + + protected String getInsertOrUpdateQuery() { + return INSERT_OR_UPDATE; + } + + private record SequencePreparedStatementCreator(String sql) implements PreparedStatementCreator, SqlProvider { + + private static final String[] COLUMNS = {"version"}; + + @Override + public PreparedStatement createPreparedStatement(Connection con) throws SQLException { + return con.prepareStatement(sql, COLUMNS); + } + + @Override + public String getSql() { + return this.sql; + } + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/AttributeRepository.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/AttributeRepository.java new file mode 100644 index 0000000..a181f18 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/AttributeRepository.java @@ -0,0 +1,34 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.attribute; + +import com.google.common.util.concurrent.ListenableFuture; +import sanbing.jcpp.app.data.kv.AttributeKvEntry; +import sanbing.jcpp.infrastructure.util.JCPPPair; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface AttributeRepository { + + Optional find(UUID entityId, String attrKey); + + List find(UUID entityId, Collection attrKeys); + + List findAll( UUID entityId); + + ListenableFuture save(UUID entityId, AttributeKvEntry attribute); + + List> removeAll(UUID entityId, List keys); + + List>> removeAllWithVersions(UUID entityId, List keys); + + List removeAllByEntityId(UUID entityId); + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/DefaultAttributeRepository.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/DefaultAttributeRepository.java new file mode 100644 index 0000000..913b4c7 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/DefaultAttributeRepository.java @@ -0,0 +1,192 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.attribute; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.CollectionUtils; +import sanbing.jcpp.app.dal.entity.Attribute; +import sanbing.jcpp.app.dal.mapper.AttributeMapper; +import sanbing.jcpp.app.dal.repository.batch.ScheduledLogExecutorComponent; +import sanbing.jcpp.app.dal.repository.batch.SqlBlockingQueueParams; +import sanbing.jcpp.app.dal.repository.batch.SqlBlockingQueueWrapper; +import sanbing.jcpp.app.dal.repository.impl.RepositoryExecutorService; +import sanbing.jcpp.app.data.kv.AttributeKvEntry; +import sanbing.jcpp.infrastructure.stats.StatsFactory; +import sanbing.jcpp.infrastructure.util.JCPPPair; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Component +@Slf4j +public class DefaultAttributeRepository implements AttributeRepository { + + @Resource + protected RepositoryExecutorService service; + + @Resource + protected JdbcTemplate jdbcTemplate; + + @Resource + protected TransactionTemplate transactionTemplate; + + @Resource + ScheduledLogExecutorComponent logExecutor; + + @Resource + private AttributeMapper attributeMapper; + + @Resource + private AttributeKvInsertRepository attributeKvInsertRepository; + + @Resource + private StatsFactory statsFactory; + + @Value("${sql.attributes.batch_size:1000}") + private int batchSize; + + @Value("${sql.attributes.batch_max_delay:100}") + private long maxDelay; + + @Value("${sql.attributes.stats_print_interval_ms:1000}") + private long statsPrintIntervalMs; + + @Value("${sql.attributes.batch_threads:4}") + private int batchThreads; + + @Value("${sql.batch_sort:true}") + private boolean batchSortEnabled; + + private SqlBlockingQueueWrapper queue; + + @PostConstruct + private void init() { + SqlBlockingQueueParams params = SqlBlockingQueueParams.builder() + .logName("Attributes") + .batchSize(batchSize) + .maxDelay(maxDelay) + .statsPrintIntervalMs(statsPrintIntervalMs) + .statsNamePrefix("attributes") + .batchSortEnabled(batchSortEnabled) + .withResponse(true) + .build(); + + Function hashcodeFunction = entity -> entity.getEntityId().hashCode(); + queue = new SqlBlockingQueueWrapper<>(params, hashcodeFunction, batchThreads, statsFactory); + queue.init(logExecutor, v -> attributeKvInsertRepository.saveOrUpdate(v), + Comparator.comparing(Attribute::getEntityId) + .thenComparing(Attribute::getAttrKey), l -> l + ); + } + + @PreDestroy + private void destroy() { + if (queue != null) { + queue.destroy(); + } + } + + @Override + public Optional find(UUID entityId, String attrKey) { + Attribute attributeKvEntity = attributeMapper.findByEntityAndKey(entityId, attrKey); + if (attributeKvEntity != null) { + return Optional.ofNullable(attributeKvEntity.toData()); + } + return Optional.empty(); + } + + @Override + public List find(UUID entityId, Collection attrKeys) { + List attributes = attributeMapper.findAllByIdAndAttrKey(entityId, attrKeys); + return convertDataList(Lists.newArrayList(attributes)); + } + + @Override + public List findAll(UUID entityId) { + List attributes = attributeMapper.findAllByEntityIdAndAttributeType( + entityId); + return convertDataList(Lists.newArrayList(attributes)); + } + + + @Override + public ListenableFuture save(UUID entityId, AttributeKvEntry attribute) { + Attribute entity = new Attribute(); + entity.setEntityId(entityId); + entity.setAttrKey(attribute.getKey()); + entity.setLastUpdateTs(attribute.getLastUpdateTs()); + entity.setStrV(attribute.getStrValue().orElse(null)); + entity.setDblV(attribute.getDoubleValue().orElse(null)); + entity.setLongV(attribute.getLongValue().orElse(null)); + entity.setBoolV(attribute.getBooleanValue().orElse(null)); + entity.setJsonV(attribute.getJsonValue().orElse(null)); + return addToQueue(entity); + } + + private ListenableFuture addToQueue(Attribute entity) { + return queue.add(entity); + } + + @Override + public List> removeAll(UUID entityId, List keys) { + List> futuresList = new ArrayList<>(keys.size()); + for (String key : keys) { + futuresList.add(service.submit(() -> { + attributeMapper.deleteByEntityIdAndKey(entityId, key); + return key; + })); + } + return futuresList; + } + + @Override + public List>> removeAllWithVersions(UUID entityId, List keys) { + List>> futuresList = new ArrayList<>(keys.size()); + for (String key : keys) { + futuresList.add(service.submit(() -> { + Integer version = transactionTemplate.execute(status -> jdbcTemplate.query("DELETE FROM t_attr WHERE entity_id = ? " + + "AND attr_key = ? RETURNING nextval('attr_kv_version_seq')", + rs -> rs.next() ? rs.getInt(1) : null, entityId, key)); + return JCPPPair.of(key, version); + })); + } + return futuresList; + } + + @Transactional + @Override + public List removeAllByEntityId(UUID entityId) { + return jdbcTemplate.queryForList("DELETE FROM t_attr WHERE entity_id = ? " + + "RETURNING attr_key", entityId).stream() + .map(row -> row.get("attr_key").toString()) + .collect(Collectors.toList()); + } + + public static List convertDataList(Collection toConvert) { + if (CollectionUtils.isEmpty(toConvert)) { + return Collections.emptyList(); + } + List converted = new ArrayList<>(toConvert.size()); + for (Attribute attribute : toConvert) { + if (attribute != null) { + converted.add(attribute.toData()); + } + } + return converted; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/KvValidator.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/KvValidator.java new file mode 100644 index 0000000..47930ae --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/attribute/KvValidator.java @@ -0,0 +1,90 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.attribute; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.commons.lang3.StringUtils; +import sanbing.jcpp.app.data.kv.AttributeKvEntry; +import sanbing.jcpp.app.data.kv.KvEntry; +import sanbing.jcpp.infrastructure.util.exception.DataValidationException; +import sanbing.jcpp.infrastructure.util.exception.IncorrectParameterException; +import sanbing.jcpp.infrastructure.util.validation.NoXssValidator; +import sanbing.jcpp.infrastructure.util.validation.Validator; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class KvValidator { + + private static final Cache validatedKeys; + + static { + validatedKeys = Caffeine.newBuilder() + .expireAfterAccess(24, TimeUnit.HOURS) + .maximumSize(50000).build(); + } + + public static void validate(List tsKvEntries, boolean valueNoXssValidation) { + tsKvEntries.forEach(tsKvEntry -> validate(tsKvEntry, valueNoXssValidation)); + } + + public static void validate(KvEntry tsKvEntry, boolean valueNoXssValidation) { + if (tsKvEntry == null) { + throw new IncorrectParameterException("键值条目不能为空"); + } + + String key = tsKvEntry.getKey(); + + if (StringUtils.isBlank(key)) { + throw new DataValidationException("键不能为空"); + } + + if (key.length() > 255) { + throw new DataValidationException("验证错误:键的长度必须小于或等于255"); + } + + Boolean isValid = validatedKeys.asMap().get(key); + if (isValid == null) { + isValid = NoXssValidator.isValid(key); + validatedKeys.put(key, isValid); + } + if (!isValid) { + throw new DataValidationException("验证错误:键的格式不正确"); + } + + if (valueNoXssValidation) { + Object value = tsKvEntry.getValue(); + if (value instanceof CharSequence || value instanceof JsonNode) { + if (!NoXssValidator.isValid(value.toString())) { + throw new DataValidationException("验证错误:值的格式不正确"); + } + } + } + } + + + public static void validateId(UUID id) { + Validator.validateId(id, uuid -> "ID不正确: " + uuid); + } + + public static void validateAttributeList(List kvEntries, boolean valueNoXssValidation) { + kvEntries.forEach(tsKvEntry -> validateAttribute(tsKvEntry, valueNoXssValidation)); + } + + public static void validateAttribute(AttributeKvEntry kvEntry, boolean valueNoXssValidation) { + validate(kvEntry, valueNoXssValidation); + if (kvEntry.getDataType() == null) { + throw new IncorrectParameterException("键值条目的数据类型不能为空"); + } else { + Validator.validateString(kvEntry.getKey(), "键值条目错误:键不能为空"); + Validator.validatePositiveNumber(kvEntry.getLastUpdateTs(), "最后更新时间戳错误:时间戳必须为正数"); + } + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/ScheduledLogExecutorComponent.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/ScheduledLogExecutorComponent.java new file mode 100644 index 0000000..69b2804 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/ScheduledLogExecutorComponent.java @@ -0,0 +1,40 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.batch; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import org.springframework.stereotype.Component; +import sanbing.jcpp.infrastructure.util.async.JCPPThreadFactory; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Component +public class ScheduledLogExecutorComponent { + + private ScheduledExecutorService schedulerLogExecutor; + + @PostConstruct + public void init() { + schedulerLogExecutor = Executors.newSingleThreadScheduledExecutor( + JCPPThreadFactory.forName("sql-log-%d") + ); + } + + @PreDestroy + public void stop() { + if (schedulerLogExecutor != null) { + schedulerLogExecutor.shutdownNow(); + } + } + + public void scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + schedulerLogExecutor.scheduleAtFixedRate(command, initialDelay, period, unit); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueue.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueue.java new file mode 100644 index 0000000..81e6608 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueue.java @@ -0,0 +1,131 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.batch; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import lombok.extern.slf4j.Slf4j; +import sanbing.jcpp.infrastructure.stats.MessagesStats; +import sanbing.jcpp.infrastructure.util.CollectionsUtil; +import sanbing.jcpp.infrastructure.util.async.JCPPThreadFactory; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Slf4j +public class SqlBlockingQueue implements SqlQueue { + + private final BlockingQueue> queue = new LinkedBlockingQueue<>(); + private final SqlBlockingQueueParams params; + + private ExecutorService executor; + private final MessagesStats stats; + + public SqlBlockingQueue(SqlBlockingQueueParams params, MessagesStats stats) { + this.params = params; + this.stats = stats; + } + + @Override + public void init(ScheduledLogExecutorComponent logExecutor, Function, List> saveFunction, Comparator batchUpdateComparator, Function>, List>> filter, int index) { + executor = Executors.newSingleThreadExecutor(JCPPThreadFactory.forName("sql-queue-" + index + "-" + params.getLogName().toLowerCase())); + executor.submit(() -> { + String logName = params.getLogName(); + int batchSize = params.getBatchSize(); + long maxDelay = params.getMaxDelay(); + final List> entities = new ArrayList<>(batchSize); + while (!Thread.interrupted()) { + try { + long currentTs = System.currentTimeMillis(); + SqlQueueElement attr = queue.poll(maxDelay, TimeUnit.MILLISECONDS); + if (attr == null) { + continue; + } else { + entities.add(attr); + } + queue.drainTo(entities, batchSize - 1); + boolean fullPack = entities.size() == batchSize; + if (log.isDebugEnabled()) { + log.debug("[{}] Going to save {} entities", logName, entities.size()); + log.trace("[{}] Going to save entities: {}", logName, entities); + } + + List> entitiesToSave = filter.apply(entities); + + if (params.isBatchSortEnabled()) { + entitiesToSave = entitiesToSave.stream().sorted((o1, o2) -> batchUpdateComparator.compare(o1.entity(), o2.entity())).toList(); + } + + List result = saveFunction.apply(entitiesToSave.stream().map(SqlQueueElement::entity).collect(Collectors.toList())); + + if (params.isWithResponse()) { + for (int i = 0; i < entitiesToSave.size(); i++) { + entitiesToSave.get(i).future().set(result.get(i)); + } + + if (entities.size() > entitiesToSave.size()) { + CollectionsUtil.diffLists(entitiesToSave, entities).forEach(v -> v.future().set(null)); + } + } else { + entities.forEach(v -> v.future().set(null)); + } + + stats.incrementSuccessful(entities.size()); + if (!fullPack) { + long remainingDelay = maxDelay - (System.currentTimeMillis() - currentTs); + if (remainingDelay > 0) { + Thread.sleep(remainingDelay); + } + } + } catch (Throwable t) { + if (t instanceof InterruptedException) { + log.info("[{}] Queue polling was interrupted", logName); + break; + } else { + log.error("[{}] Failed to save {} entities", logName, entities.size(), t); + try { + stats.incrementFailed(entities.size()); + entities.forEach(entityFutureWrapper -> entityFutureWrapper.future().setException(t)); + } catch (Throwable th) { + log.error("[{}] Failed to set future exception", logName, th); + } + } + } finally { + entities.clear(); + } + } + log.info("[{}] Queue polling completed", logName); + }); + + logExecutor.scheduleAtFixedRate(() -> { + if (!queue.isEmpty() || stats.getTotal() > 0 || stats.getSuccessful() > 0 || stats.getFailed() > 0) { + log.info("Queue-{} [{}] queueSize [{}] totalAdded [{}] totalSaved [{}] totalFailed [{}]", index, + params.getLogName(), queue.size(), stats.getTotal(), stats.getSuccessful(), stats.getFailed()); + stats.reset(); + } + }, params.getStatsPrintIntervalMs(), params.getStatsPrintIntervalMs(), TimeUnit.MILLISECONDS); + } + + @Override + public void destroy() { + if (executor != null) { + executor.shutdownNow(); + } + } + + @Override + public ListenableFuture add(E element) { + SettableFuture future = SettableFuture.create(); + queue.add(new SqlQueueElement<>(future, element)); + stats.incrementTotal(); + return future; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueParams.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueParams.java new file mode 100644 index 0000000..42af8a5 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueParams.java @@ -0,0 +1,25 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.batch; + +import lombok.Builder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Data +@Builder +public class SqlBlockingQueueParams { + + private final String logName; + private final int batchSize; + private final long maxDelay; + private final long statsPrintIntervalMs; + private final String statsNamePrefix; + private final boolean batchSortEnabled; + private final boolean withResponse; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueWrapper.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueWrapper.java new file mode 100644 index 0000000..a9ab970 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlBlockingQueueWrapper.java @@ -0,0 +1,59 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.batch; + +import com.google.common.util.concurrent.ListenableFuture; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import sanbing.jcpp.infrastructure.stats.MessagesStats; +import sanbing.jcpp.infrastructure.stats.StatsFactory; + +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.function.Function; + +@Slf4j +@Data +public class SqlBlockingQueueWrapper { + private final CopyOnWriteArrayList> queues = new CopyOnWriteArrayList<>(); + private final SqlBlockingQueueParams params; + private final Function hashCodeFunction; + private final int maxThreads; + private final StatsFactory statsFactory; + + /** + * Starts JCPPSqlBlockingQueues. + * + * @param logExecutor executor that will be printing logs and statistics + * @param saveFunction function to save entities in database + * @param batchUpdateComparator comparator to sort entities by primary key to avoid deadlocks in cluster mode + * NOTE: you must use all of primary key parts in your comparator + */ + public void init(ScheduledLogExecutorComponent logExecutor, Consumer> saveFunction, Comparator batchUpdateComparator) { + init(logExecutor, l -> { saveFunction.accept(l); return null; }, batchUpdateComparator, l -> l); + } + + public void init(ScheduledLogExecutorComponent logExecutor, Function, List> saveFunction, Comparator batchUpdateComparator, Function>, List>> filter) { + for (int i = 0; i < maxThreads; i++) { + MessagesStats stats = statsFactory.createMessagesStats(params.getStatsNamePrefix() + ".queue." + i); + SqlBlockingQueue queue = new SqlBlockingQueue<>(params, stats); + queues.add(queue); + queue.init(logExecutor, saveFunction, batchUpdateComparator, filter, i); + } + } + + public ListenableFuture add(E element) { + int queueIndex = element != null ? (hashCodeFunction.apply(element) & 0x7FFFFFFF) % maxThreads : 0; + return queues.get(queueIndex).add(element); + } + + public void destroy() { + queues.forEach(SqlBlockingQueue::destroy); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlQueue.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlQueue.java new file mode 100644 index 0000000..0134e94 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlQueue.java @@ -0,0 +1,22 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.batch; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; + +public interface SqlQueue { + + void init(ScheduledLogExecutorComponent logExecutor, Function, List> saveFunction, Comparator batchUpdateComparator, Function>, List>> filter, int queueIndex); + + void destroy(); + + ListenableFuture add(E element); +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlQueueElement.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlQueueElement.java new file mode 100644 index 0000000..6c21eed --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/batch/SqlQueueElement.java @@ -0,0 +1,14 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.batch; + +import com.google.common.util.concurrent.SettableFuture; + +public record SqlQueueElement(SettableFuture future, E entity) { +} + + diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/repository/AbstractCachedEntityRepository.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/AbstractCachedEntityRepository.java similarity index 76% rename from jcpp-app/src/main/java/sanbing/jcpp/app/repository/AbstractCachedEntityRepository.java rename to jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/AbstractCachedEntityRepository.java index 8342ba6..780b295 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/repository/AbstractCachedEntityRepository.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/AbstractCachedEntityRepository.java @@ -4,14 +4,19 @@ * 抖音:程序员三丙 * 付费课程知识星球:https://t.zsxq.com/aKtXo */ -package sanbing.jcpp.app.repository; +package sanbing.jcpp.app.dal.repository.impl; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.support.TransactionSynchronizationManager; +import sanbing.jcpp.infrastructure.cache.TransactionalCache; import java.io.Serializable; public abstract class AbstractCachedEntityRepository extends AbstractEntityRepository { + @Autowired + protected TransactionalCache cache; + protected void publishEvictEvent(E event) { if (TransactionSynchronizationManager.isActualTransactionActive()) { eventPublisher.publishEvent(event); diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/repository/AbstractEntityRepository.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/AbstractEntityRepository.java similarity index 90% rename from jcpp-app/src/main/java/sanbing/jcpp/app/repository/AbstractEntityRepository.java rename to jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/AbstractEntityRepository.java index 19b04f3..18a0d82 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/repository/AbstractEntityRepository.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/AbstractEntityRepository.java @@ -4,7 +4,7 @@ * 抖音:程序员三丙 * 付费课程知识星球:https://t.zsxq.com/aKtXo */ -package sanbing.jcpp.app.repository; +package sanbing.jcpp.app.dal.repository.impl; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/repository/CachedVersionedEntityRepository.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/CachedVersionedEntityRepository.java similarity index 83% rename from jcpp-app/src/main/java/sanbing/jcpp/app/repository/CachedVersionedEntityRepository.java rename to jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/CachedVersionedEntityRepository.java index ef8aa64..0c76c3a 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/repository/CachedVersionedEntityRepository.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/CachedVersionedEntityRepository.java @@ -4,9 +4,9 @@ * 抖音:程序员三丙 * 付费课程知识星球:https://t.zsxq.com/aKtXo */ -package sanbing.jcpp.app.repository; +package sanbing.jcpp.app.dal.repository.impl; -import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowired; import sanbing.jcpp.infrastructure.cache.HasVersion; import sanbing.jcpp.infrastructure.cache.VersionedCache; import sanbing.jcpp.infrastructure.cache.VersionedCacheKey; @@ -15,7 +15,7 @@ import java.io.Serializable; public abstract class CachedVersionedEntityRepository extends AbstractCachedEntityRepository { - @Resource + @Autowired protected VersionedCache cache; } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/GunRepositoryImpl.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/GunRepositoryImpl.java new file mode 100644 index 0000000..5e581e1 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/GunRepositoryImpl.java @@ -0,0 +1,73 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.impl; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.event.TransactionalEventListener; +import sanbing.jcpp.app.dal.entity.Gun; +import sanbing.jcpp.app.dal.mapper.GunMapper; +import sanbing.jcpp.app.dal.repository.GunRepository; +import sanbing.jcpp.app.service.cache.gun.GunCacheEvictEvent; +import sanbing.jcpp.app.service.cache.gun.GunCacheKey; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static sanbing.jcpp.infrastructure.util.validation.Validator.validateId; +import static sanbing.jcpp.infrastructure.util.validation.Validator.validateString; + +/** + * 充电枪数据访问实现 + * + * @author 九筒 + */ +@Repository +@Slf4j +public class GunRepositoryImpl extends CachedVersionedEntityRepository implements GunRepository { + + @Resource + GunMapper gunMapper; + + @TransactionalEventListener(classes = GunCacheEvictEvent.class) + @Override + public void handleEvictEvent(GunCacheEvictEvent event) { + // 如果修改或删除充电枪,需要在这里消费删除事件 + List toEvict = new ArrayList<>(3); + + // 基于gunId的缓存key + if (event.getGunId() != null) { + toEvict.add(new GunCacheKey(event.getGunId())); + } + + // 基于pileCode+gunCode的缓存key + if (event.getPileCode() != null && event.getGunCode() != null) { + toEvict.add(new GunCacheKey(event.getPileCode(), event.getGunCode())); + } + + cache.evict(toEvict); + } + + @Override + public Gun findByPileCodeAndGunCode(String pileCode, String gunCode) { + validateString(pileCode, code -> "无效的桩编号: " + pileCode); + validateString(gunCode, code -> "无效的枪编号: " + gunCode); + + return cache.get(new GunCacheKey(pileCode, gunCode), + () -> gunMapper.selectByPileCodeAndGunCode(pileCode, gunCode)); + } + + @Override + public Gun findById(UUID gunId) { + validateId(gunId, id -> "无效的充电枪ID: " + gunId); + + return cache.get(new GunCacheKey(gunId), + () -> gunMapper.selectById(gunId)); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/repository/PileRepositoryImpl.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java similarity index 93% rename from jcpp-app/src/main/java/sanbing/jcpp/app/repository/PileRepositoryImpl.java rename to jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java index fecc72a..c41b51b 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/repository/PileRepositoryImpl.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/PileRepositoryImpl.java @@ -4,7 +4,7 @@ * 抖音:程序员三丙 * 付费课程知识星球:https://t.zsxq.com/aKtXo */ -package sanbing.jcpp.app.repository; +package sanbing.jcpp.app.dal.repository.impl; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -12,6 +12,7 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.event.TransactionalEventListener; import sanbing.jcpp.app.dal.entity.Pile; import sanbing.jcpp.app.dal.mapper.PileMapper; +import sanbing.jcpp.app.dal.repository.PileRepository; import sanbing.jcpp.app.service.cache.pile.PileCacheEvictEvent; import sanbing.jcpp.app.service.cache.pile.PileCacheKey; @@ -21,7 +22,7 @@ import java.util.List; import static sanbing.jcpp.infrastructure.util.validation.Validator.validateString; /** - * @author baigod + * @author 九筒 */ @Repository @Slf4j diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/RepositoryExecutorService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/RepositoryExecutorService.java new file mode 100644 index 0000000..f67f3af --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/dal/repository/impl/RepositoryExecutorService.java @@ -0,0 +1,24 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.dal.repository.impl; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import sanbing.jcpp.infrastructure.util.async.AbstractListeningExecutor; + +@Component +public class RepositoryExecutorService extends AbstractListeningExecutor { + + @Value("${spring.datasource.hikari.maximum-pool-size}") + private int poolSize; + + @Override + protected int getThreadPollSize() { + return poolSize; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/InstallModeEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/InstallModeEnum.java new file mode 100644 index 0000000..76e1c90 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/InstallModeEnum.java @@ -0,0 +1,58 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data; + +import lombok.Getter; + +/** + * 数据库安装模式枚举 + * + * @author 九筒 + */ +@Getter +public enum InstallModeEnum { + + /** + * 初始化数据库,执行schema-init.sql并加载演示数据 + */ + INIT("init", "初始化数据库"), + + /** + * 升级数据库,根据版本执行升级脚本 + */ + UPGRADE("upgrade", "升级数据库"), + + /** + * 不执行任何操作 + */ + DISABLED("disabled", "禁用安装功能"); + + private final String mode; + private final String description; + + InstallModeEnum(String mode, String description) { + this.mode = mode; + this.description = description; + } + + /** + * 根据mode字符串获取枚举值 + */ + public static InstallModeEnum fromMode(String mode) { + if (mode == null || mode.isEmpty()) { + return DISABLED; + } + + for (InstallModeEnum installMode : values()) { + if (installMode.mode.equals(mode)) { + return installMode; + } + } + + return DISABLED; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/PileSession.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/PileSession.java index 15c4698..8da3644 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/data/PileSession.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/PileSession.java @@ -14,7 +14,7 @@ import java.io.Serializable; import java.util.UUID; /** - * @author baigod + * @author 九筒 */ @Data public class PileSession implements Serializable { @@ -37,6 +37,8 @@ public class PileSession implements Serializable { private int nodeGrpcPort; + + public PileSession(UUID pileId, String pileCode, String protocolName) { this.pileId = pileId; this.pileCode = pileCode; diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttrKeyEnum.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttrKeyEnum.java new file mode 100644 index 0000000..16c8ae1 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttrKeyEnum.java @@ -0,0 +1,69 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * 属性键枚举,定义系统内置的属性键 + * 使用String类型提高可读性 + * + * @author 九筒 + */ +public enum AttrKeyEnum { + + /** + * 状态 + */ + STATUS( "status"), + + /** + * 连接时间 + */ + CONNECTED_AT("connectedAt"), + + /** + * 断开连接时间 + */ + DISCONNECTED_AT("disconnectedAt"), + + /** + * 最后活跃时间 + */ + LAST_ACTIVE_TIME("lastActiveTime"), + + /** + * 充电枪运行状态 + */ + GUN_RUN_STATUS("gunRunStatus"), + + /** + * 地锁状态 + */ + LOCK_STATUS("lockStatus"), + + /** + * 车位状态 + */ + PARK_STATUS("parkStatus"); + + @JsonValue + private final String code; + + AttrKeyEnum( String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + @Override + public String toString() { + return code; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttributeKvEntry.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttributeKvEntry.java new file mode 100644 index 0000000..93ba24c --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttributeKvEntry.java @@ -0,0 +1,17 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + + +import sanbing.jcpp.infrastructure.cache.HasVersion; + + +public interface AttributeKvEntry extends KvEntry, HasVersion { + + long getLastUpdateTs(); + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttributesSaveResult.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttributesSaveResult.java new file mode 100644 index 0000000..b479744 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/AttributesSaveResult.java @@ -0,0 +1,23 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + +import java.util.Collections; +import java.util.List; + +public record AttributesSaveResult(List versions) { + + public static final AttributesSaveResult EMPTY = new AttributesSaveResult(Collections.emptyList()); + + public static AttributesSaveResult of(List versions) { + if (versions == null) { + return EMPTY; + } + return new AttributesSaveResult(versions); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BaseAttributeKvEntry.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BaseAttributeKvEntry.java new file mode 100644 index 0000000..0b271d9 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BaseAttributeKvEntry.java @@ -0,0 +1,190 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.validation.Valid; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +import java.util.Optional; + +@Slf4j +@Data +public class BaseAttributeKvEntry implements AttributeKvEntry { + + private static final long serialVersionUID = -6460767583563159407L; + + private final long lastUpdateTs; + @Valid + private final KvEntry kv; + + private final Integer version; + + public BaseAttributeKvEntry(KvEntry kv, long lastUpdateTs) { + this.kv = kv; + this.lastUpdateTs = lastUpdateTs; + this.version = null; + } + + public BaseAttributeKvEntry(KvEntry kv, long lastUpdateTs, Integer version) { + this.kv = kv; + this.lastUpdateTs = lastUpdateTs; + this.version = version; + } + + public BaseAttributeKvEntry(long lastUpdateTs, KvEntry kv) { + this(kv, lastUpdateTs); + } + + @Override + public String getKey() { + return kv.getKey(); + } + + @Override + public DataType getDataType() { + return kv.getDataType(); + } + + @Override + public Optional getStrValue() { + return kv.getStrValue(); + } + + @Override + public Optional getLongValue() { + return kv.getLongValue(); + } + + @Override + public Optional getBooleanValue() { + return kv.getBooleanValue(); + } + + @Override + public Optional getDoubleValue() { + return kv.getDoubleValue(); + } + + @Override + public Optional getJsonValue() { + return kv.getJsonValue(); + } + + @Override + public String getValueAsString() { + return kv.getValueAsString(); + } + + @Override + public Object getValue() { + return kv.getValue(); + } + + /** + * 将当前对象转换为JSON字节数组 + * 避免Jackson序列化Optional类型的问题 + */ + @JsonIgnore + public byte[] toJsonBytes() { + try { + ObjectNode json = JacksonUtil.newObjectNode(); + json.put("lastUpdateTs", lastUpdateTs); + if (version != null) { + json.put("version", version); + } + + // 处理KvEntry序列化 + ObjectNode kvJson = JacksonUtil.newObjectNode(); + kvJson.put("key", kv.getKey()); + kvJson.put("dataType", kv.getDataType().name()); + + // 根据数据类型序列化值,避免Optional问题 + switch (kv.getDataType()) { + case STRING: + kv.getStrValue().ifPresent(value -> kvJson.put("value", value)); + break; + case LONG: + kv.getLongValue().ifPresent(value -> kvJson.put("value", value)); + break; + case BOOLEAN: + kv.getBooleanValue().ifPresent(value -> kvJson.put("value", value)); + break; + case DOUBLE: + kv.getDoubleValue().ifPresent(value -> kvJson.put("value", value)); + break; + case JSON: + kv.getJsonValue().ifPresent(value -> kvJson.put("value", value)); + break; + default: + // 如果没有匹配的类型,将值作为字符串处理 + kvJson.put("value", kv.getValueAsString()); + break; + } + + json.set("kv", kvJson); + return JacksonUtil.writeValueAsBytes(json); + } catch (Exception e) { + log.error("Failed to serialize BaseAttributeKvEntry to JSON bytes", e); + throw new RuntimeException("Failed to serialize BaseAttributeKvEntry", e); + } + } + + /** + * 从JSON字节数组反序列化为BaseAttributeKvEntry对象 + * 避免Jackson反序列化Optional类型的问题 + */ + public static BaseAttributeKvEntry fromJsonBytes(byte[] jsonBytes) { + try { + JsonNode json = JacksonUtil.fromBytes(jsonBytes); + + long lastUpdateTs = json.get("lastUpdateTs").asLong(); + Integer version = json.has("version") ? json.get("version").asInt() : null; + + // 解析KvEntry + JsonNode kvJson = json.get("kv"); + String key = kvJson.get("key").asText(); + DataType dataType = DataType.valueOf(kvJson.get("dataType").asText()); + + KvEntry kvEntry; + switch (dataType) { + case STRING: + String strValue = kvJson.has("value") ? kvJson.get("value").asText() : null; + kvEntry = new StringDataEntry(key, strValue); + break; + case LONG: + Long longValue = kvJson.has("value") ? kvJson.get("value").asLong() : null; + kvEntry = new LongDataEntry(key, longValue); + break; + case BOOLEAN: + Boolean boolValue = kvJson.has("value") ? kvJson.get("value").asBoolean() : null; + kvEntry = new BooleanDataEntry(key, boolValue); + break; + case DOUBLE: + Double doubleValue = kvJson.has("value") ? kvJson.get("value").asDouble() : null; + kvEntry = new DoubleDataEntry(key, doubleValue); + break; + case JSON: + String jsonValue = kvJson.has("value") ? kvJson.get("value").asText() : null; + kvEntry = new JsonDataEntry(key, jsonValue); + break; + default: + throw new IllegalArgumentException("Unsupported data type: " + dataType); + } + + return new BaseAttributeKvEntry(kvEntry, lastUpdateTs, version); + } catch (Exception e) { + log.error("Failed to deserialize BaseAttributeKvEntry from JSON bytes", e); + throw new RuntimeException("Failed to deserialize BaseAttributeKvEntry", e); + } + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BasicKvEntry.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BasicKvEntry.java new file mode 100644 index 0000000..eb1be86 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BasicKvEntry.java @@ -0,0 +1,74 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + + +import sanbing.jcpp.infrastructure.util.validation.Length; +import sanbing.jcpp.infrastructure.util.validation.NoXss; + +import java.util.Objects; +import java.util.Optional; + +public abstract class BasicKvEntry implements KvEntry { + + @Length(fieldName = "attribute key") + @NoXss + private final String key; + + protected BasicKvEntry(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } + + @Override + public Optional getStrValue() { + return Optional.ofNullable(null); + } + + @Override + public Optional getLongValue() { + return Optional.ofNullable(null); + } + + @Override + public Optional getBooleanValue() { + return Optional.ofNullable(null); + } + + @Override + public Optional getDoubleValue() { + return Optional.ofNullable(null); + } + + @Override + public Optional getJsonValue() { + return Optional.ofNullable(null); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BasicKvEntry that)) return false; + return Objects.equals(key, that.key); + } + + @Override + public int hashCode() { + return Objects.hash(key); + } + + @Override + public String toString() { + return "BasicKvEntry{" + + "key='" + key + '\'' + + '}'; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BooleanDataEntry.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BooleanDataEntry.java new file mode 100644 index 0000000..51c820b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/BooleanDataEntry.java @@ -0,0 +1,59 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public class BooleanDataEntry extends BasicKvEntry { + private final Boolean value; + + public BooleanDataEntry(String key, Boolean value) { + super(key); + this.value = value; + } + + @Override + public DataType getDataType() { + return DataType.BOOLEAN; + } + + @Override + public Optional getBooleanValue() { + return Optional.ofNullable(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BooleanDataEntry that)) return false; + if (!super.equals(o)) return false; + return Objects.equals(value, that.value); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "BooleanDataEntry{" + + "value=" + value + + "} " + super.toString(); + } + + @Override + public String getValueAsString() { + return Boolean.toString(value); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/DataType.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/DataType.java new file mode 100644 index 0000000..5ba104d --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/DataType.java @@ -0,0 +1,26 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + +import lombok.Getter; + +public enum DataType { + + BOOLEAN(0), + LONG(1), + DOUBLE(2), + STRING(3), + JSON(4); + + @Getter + private final int protoNumber; // Corresponds to KeyValueType + + DataType(int protoNumber) { + this.protoNumber = protoNumber; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/DoubleDataEntry.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/DoubleDataEntry.java new file mode 100644 index 0000000..f9ea01f --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/DoubleDataEntry.java @@ -0,0 +1,60 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public class DoubleDataEntry extends BasicKvEntry { + + private final Double value; + + public DoubleDataEntry(String key, Double value) { + super(key); + this.value = value; + } + + @Override + public DataType getDataType() { + return DataType.DOUBLE; + } + + @Override + public Optional getDoubleValue() { + return Optional.ofNullable(value); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DoubleDataEntry that)) return false; + if (!super.equals(o)) return false; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "DoubleDataEntry{" + + "value=" + value + + "} " + super.toString(); + } + + @Override + public String getValueAsString() { + return Double.toString(value); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/JsonDataEntry.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/JsonDataEntry.java new file mode 100644 index 0000000..bce1483 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/JsonDataEntry.java @@ -0,0 +1,60 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public class JsonDataEntry extends BasicKvEntry { + + private final String value; + + public JsonDataEntry(String key, String value) { + super(key); + this.value = value; + } + + @Override + public DataType getDataType() { + return DataType.JSON; + } + + @Override + public Optional getJsonValue() { + return Optional.ofNullable(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof JsonDataEntry that)) return false; + if (!super.equals(o)) return false; + return Objects.equals(value, that.value); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "JsonDataEntry{" + + "value=" + value + + "} " + super.toString(); + } + + @Override + public String getValueAsString() { + return value; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/KvEntry.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/KvEntry.java new file mode 100644 index 0000000..e8f6d0a --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/KvEntry.java @@ -0,0 +1,31 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + +import java.io.Serializable; +import java.util.Optional; + +public interface KvEntry extends Serializable { + + String getKey(); + + DataType getDataType(); + + Optional getStrValue(); + + Optional getLongValue(); + + Optional getBooleanValue(); + + Optional getDoubleValue(); + + Optional getJsonValue(); + + String getValueAsString(); + + Object getValue(); +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/LongDataEntry.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/LongDataEntry.java new file mode 100644 index 0000000..5485b92 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/LongDataEntry.java @@ -0,0 +1,60 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public class LongDataEntry extends BasicKvEntry { + + private final Long value; + + public LongDataEntry(String key, Long value) { + super(key); + this.value = value; + } + + @Override + public DataType getDataType() { + return DataType.LONG; + } + + @Override + public Optional getLongValue() { + return Optional.ofNullable(value); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof LongDataEntry that)) return false; + if (!super.equals(o)) return false; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "LongDataEntry{" + + "value=" + value + + "} " + super.toString(); + } + + @Override + public String getValueAsString() { + return Long.toString(value); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/StringDataEntry.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/StringDataEntry.java new file mode 100644 index 0000000..8a7ea9b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/kv/StringDataEntry.java @@ -0,0 +1,65 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.kv; + +import java.io.Serial; +import java.util.Objects; +import java.util.Optional; + +public class StringDataEntry extends BasicKvEntry { + + @Serial + private static final long serialVersionUID = 1L; + + private final String value; + + public StringDataEntry(String key, String value) { + super(key); + this.value = value; + } + + @Override + public DataType getDataType() { + return DataType.STRING; + } + + @Override + public Optional getStrValue() { + return Optional.ofNullable(value); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof StringDataEntry that)) + return false; + if (!super.equals(o)) + return false; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "StringDataEntry{" + "value='" + value + '\'' + "} " + super.toString(); + } + + @Override + public String getValueAsString() { + return value; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/data/page/PageDataIterable.java b/jcpp-app/src/main/java/sanbing/jcpp/app/data/page/PageDataIterable.java new file mode 100644 index 0000000..2b9ea10 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/data/page/PageDataIterable.java @@ -0,0 +1,103 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.data.page; + +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * 分页数据迭代器,用于处理大数据量查询,避免内存溢出 + * + * @param 数据类型 + * @author 九筒 + */ +public class PageDataIterable implements Iterable { + + private final FetchFunction fetchFunction; + private final int pageSize; + + public PageDataIterable(FetchFunction fetchFunction, int pageSize) { + this.fetchFunction = fetchFunction; + this.pageSize = pageSize; + } + + @Override + public Iterator iterator() { + return new PageDataIterator(); + } + + /** + * 分页获取函数接口 + */ + @FunctionalInterface + public interface FetchFunction { + /** + * 获取指定页的数据 + * + * @param offset 偏移量 + * @param limit 限制数量 + * @return 数据列表 + */ + List fetch(int offset, int limit); + } + + private class PageDataIterator implements Iterator { + private int currentOffset = 0; + private List currentPage; + private int currentIndex = 0; + private boolean hasMorePages = true; + + @Override + public boolean hasNext() { + // 如果当前页还有数据,直接返回true + if (currentPage != null && currentIndex < currentPage.size()) { + return true; + } + + // 如果没有更多页了,返回false + if (!hasMorePages) { + return false; + } + + // 尝试加载下一页 + loadNextPage(); + + // 检查加载后是否有数据 + return currentPage != null && !currentPage.isEmpty(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + return currentPage.get(currentIndex++); + } + + private void loadNextPage() { + try { + currentPage = fetchFunction.fetch(currentOffset, pageSize); + currentIndex = 0; + + // 如果返回的数据少于页大小,说明没有更多页了 + if (currentPage == null || currentPage.size() < pageSize) { + hasMorePages = false; + } + + // 更新偏移量 + currentOffset += pageSize; + + } catch (Exception e) { + hasMorePages = false; + currentPage = null; + throw new RuntimeException("Failed to fetch next page", e); + } + } + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPCredentialsExpiredResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPCredentialsExpiredResponse.java new file mode 100644 index 0000000..e3f6583 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPCredentialsExpiredResponse.java @@ -0,0 +1,27 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +public class JCPPCredentialsExpiredResponse extends JCPPErrorResponse { + + @Getter + private final String resetToken; + + protected JCPPCredentialsExpiredResponse(String message, String resetToken) { + super(message, JCPPErrorCode.CREDENTIALS_EXPIRED, HttpStatus.UNAUTHORIZED); + this.resetToken = resetToken; + } + + public static JCPPCredentialsExpiredResponse of(final String message, final String resetToken) { + return new JCPPCredentialsExpiredResponse(message, resetToken); + } + +} + diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPCredentialsViolationResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPCredentialsViolationResponse.java new file mode 100644 index 0000000..9ecf7c2 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPCredentialsViolationResponse.java @@ -0,0 +1,21 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.exception; + +import org.springframework.http.HttpStatus; + +public class JCPPCredentialsViolationResponse extends JCPPErrorResponse { + + protected JCPPCredentialsViolationResponse(String message) { + super(message, JCPPErrorCode.PASSWORD_VIOLATION, HttpStatus.UNAUTHORIZED); + } + + public static JCPPCredentialsViolationResponse of(final String message) { + return new JCPPCredentialsViolationResponse(message); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorCode.java b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorCode.java new file mode 100644 index 0000000..4c9c355 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorCode.java @@ -0,0 +1,39 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.exception; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum JCPPErrorCode { + + GENERAL(2), + AUTHENTICATION(10), + JWT_TOKEN_EXPIRED(11), + CREDENTIALS_EXPIRED(15), + PERMISSION_DENIED(20), + INVALID_ARGUMENTS(30), + BAD_REQUEST_PARAMS(31), + ITEM_NOT_FOUND(32), + TOO_MANY_REQUESTS(33), + TOO_MANY_UPDATES(34), + VERSION_CONFLICT(35), + SUBSCRIPTION_VIOLATION(40), + PASSWORD_VIOLATION(45), + DATABASE(46); + + private final int errorCode; + + JCPPErrorCode(int errorCode) { + this.errorCode = errorCode; + } + + @JsonValue + public int getErrorCode() { + return errorCode; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorResponse.java b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorResponse.java new file mode 100644 index 0000000..90116e2 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorResponse.java @@ -0,0 +1,37 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.exception; + +import lombok.Data; +import org.springframework.http.HttpStatus; + +@Data +public class JCPPErrorResponse { + private final HttpStatus status; + + private final String message; + + private final JCPPErrorCode errorCode; + + private final long timestamp; + + protected JCPPErrorResponse(final String message, final JCPPErrorCode errorCode, HttpStatus status) { + this.message = message; + this.errorCode = errorCode; + this.status = status; + this.timestamp = System.currentTimeMillis(); + } + + public static JCPPErrorResponse of(final String message, final JCPPErrorCode errorCode, HttpStatus status) { + return new JCPPErrorResponse(message, errorCode, status); + } + + public Integer getStatus() { + return status.value(); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorResponseHandler.java b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorResponseHandler.java new file mode 100644 index 0000000..014f3c7 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPErrorResponseHandler.java @@ -0,0 +1,219 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.exception; + +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.dao.DataAccessException; +import org.springframework.http.*; +import org.springframework.lang.Nullable; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import org.springframework.web.util.WebUtils; +import sanbing.jcpp.app.service.security.exception.AuthMethodNotSupportedException; +import sanbing.jcpp.app.service.security.exception.JwtExpiredTokenException; +import sanbing.jcpp.app.service.security.exception.UserPasswordExpiredException; +import sanbing.jcpp.app.service.security.exception.UserPasswordNotValidException; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Controller +@RestControllerAdvice +public class JCPPErrorResponseHandler extends ResponseEntityExceptionHandler implements AccessDeniedHandler, ErrorController { + + private static final Map statusToErrorCodeMap = new HashMap<>(); + + static { + statusToErrorCodeMap.put(HttpStatus.BAD_REQUEST, JCPPErrorCode.BAD_REQUEST_PARAMS); + statusToErrorCodeMap.put(HttpStatus.UNAUTHORIZED, JCPPErrorCode.AUTHENTICATION); + statusToErrorCodeMap.put(HttpStatus.FORBIDDEN, JCPPErrorCode.PERMISSION_DENIED); + statusToErrorCodeMap.put(HttpStatus.NOT_FOUND, JCPPErrorCode.ITEM_NOT_FOUND); + statusToErrorCodeMap.put(HttpStatus.METHOD_NOT_ALLOWED, JCPPErrorCode.BAD_REQUEST_PARAMS); + statusToErrorCodeMap.put(HttpStatus.NOT_ACCEPTABLE, JCPPErrorCode.BAD_REQUEST_PARAMS); + statusToErrorCodeMap.put(HttpStatus.UNSUPPORTED_MEDIA_TYPE, JCPPErrorCode.BAD_REQUEST_PARAMS); + statusToErrorCodeMap.put(HttpStatus.TOO_MANY_REQUESTS, JCPPErrorCode.TOO_MANY_REQUESTS); + statusToErrorCodeMap.put(HttpStatus.INTERNAL_SERVER_ERROR, JCPPErrorCode.GENERAL); + statusToErrorCodeMap.put(HttpStatus.SERVICE_UNAVAILABLE, JCPPErrorCode.GENERAL); + } + + private static final Map errorCodeToStatusMap = new HashMap<>(); + + static { + errorCodeToStatusMap.put(JCPPErrorCode.GENERAL, HttpStatus.INTERNAL_SERVER_ERROR); + errorCodeToStatusMap.put(JCPPErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED); + errorCodeToStatusMap.put(JCPPErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED); + errorCodeToStatusMap.put(JCPPErrorCode.CREDENTIALS_EXPIRED, HttpStatus.UNAUTHORIZED); + errorCodeToStatusMap.put(JCPPErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN); + errorCodeToStatusMap.put(JCPPErrorCode.INVALID_ARGUMENTS, HttpStatus.BAD_REQUEST); + errorCodeToStatusMap.put(JCPPErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST); + errorCodeToStatusMap.put(JCPPErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND); + errorCodeToStatusMap.put(JCPPErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS); + errorCodeToStatusMap.put(JCPPErrorCode.TOO_MANY_UPDATES, HttpStatus.TOO_MANY_REQUESTS); + errorCodeToStatusMap.put(JCPPErrorCode.SUBSCRIPTION_VIOLATION, HttpStatus.FORBIDDEN); + errorCodeToStatusMap.put(JCPPErrorCode.VERSION_CONFLICT, HttpStatus.CONFLICT); + } + + private static JCPPErrorCode statusToErrorCode(HttpStatus status) { + return statusToErrorCodeMap.getOrDefault(status, JCPPErrorCode.GENERAL); + } + + private static HttpStatus errorCodeToStatus(JCPPErrorCode errorCode) { + return errorCodeToStatusMap.getOrDefault(errorCode, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @RequestMapping("/error") + public ResponseEntity handleError(HttpServletRequest request) { + HttpStatus httpStatus = Optional.ofNullable(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE)) + .map(status -> HttpStatus.resolve(Integer.parseInt(status.toString()))) + .orElse(HttpStatus.INTERNAL_SERVER_ERROR); + String errorMessage = Optional.ofNullable(request.getAttribute(RequestDispatcher.ERROR_EXCEPTION)) + .map(e -> (ExceptionUtils.getMessage((Throwable) e))) + .orElse(httpStatus.getReasonPhrase()); + return new ResponseEntity<>(JCPPErrorResponse.of(errorMessage, statusToErrorCode(httpStatus), httpStatus), httpStatus); + } + + @Override + @ExceptionHandler(AccessDeniedException.class) + public void handle(HttpServletRequest request, HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException, + ServletException { + if (!response.isCommitted()) { + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(HttpStatus.FORBIDDEN.value()); + JacksonUtil.writeValue(response.getWriter(), + JCPPErrorResponse.of("You don't have permission to perform this operation!", + JCPPErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)); + } + } + + @ExceptionHandler(Exception.class) + public void handle(Exception exception, HttpServletResponse response) { + log.debug("Processing exception {}", exception.getMessage(), exception); + if (!response.isCommitted()) { + try { + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + switch (exception) { + case JCPPException jcppException -> { + if (jcppException.getErrorCode() == JCPPErrorCode.SUBSCRIPTION_VIOLATION) { + handleSubscriptionException(jcppException, response); + } else if (jcppException.getErrorCode() == JCPPErrorCode.DATABASE) { + handleDatabaseException(jcppException.getCause(), response); + } else { + handleJCPPException(jcppException, response); + } + } + case AccessDeniedException ignored -> handleAccessDeniedException(response); + case AuthenticationException authenticationException -> + handleAuthenticationException(authenticationException, response); + default -> { + if (exception instanceof DataAccessException e) { + handleDatabaseException(e, response); + } else { + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + JacksonUtil.writeValue(response.getWriter(), JCPPErrorResponse.of(exception.getMessage(), + JCPPErrorCode.GENERAL, HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + } + } catch (IOException e) { + log.error("Can't handle exception", e); + } + } + } + + @Override + protected ResponseEntity handleExceptionInternal( + Exception ex, @Nullable Object body, + HttpHeaders headers, HttpStatusCode statusCode, + WebRequest request) { + if (HttpStatus.INTERNAL_SERVER_ERROR.equals(statusCode)) { + request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST); + } + JCPPErrorCode errorCode = statusToErrorCode((HttpStatus) statusCode); + return new ResponseEntity<>(JCPPErrorResponse.of(ex.getMessage(), errorCode, (HttpStatus) statusCode), headers, statusCode); + } + + private void handleJCPPException(JCPPException jcppException, HttpServletResponse response) throws IOException { + JCPPErrorCode errorCode = jcppException.getErrorCode(); + HttpStatus status = errorCodeToStatus(errorCode); + response.setStatus(status.value()); + JacksonUtil.writeValue(response.getWriter(), JCPPErrorResponse.of(jcppException.getMessage(), errorCode, status)); + } + + private void handleSubscriptionException(JCPPException subscriptionException, HttpServletResponse response) throws IOException { + response.setStatus(HttpStatus.FORBIDDEN.value()); + JacksonUtil.writeValue(response.getWriter(), + JacksonUtil.fromBytes(((HttpClientErrorException) subscriptionException.getCause()).getResponseBodyAsByteArray(), Object.class)); + } + + private void handleDatabaseException(Throwable databaseException, HttpServletResponse response) throws IOException { + log.warn("Database error: {} - {}", databaseException.getClass().getSimpleName(), ExceptionUtils.getRootCauseMessage(databaseException)); + JCPPErrorResponse errorResponse = JCPPErrorResponse.of("Database error", JCPPErrorCode.DATABASE, HttpStatus.INTERNAL_SERVER_ERROR); + writeResponse(errorResponse, response); + } + + private void handleAccessDeniedException(HttpServletResponse response) throws IOException { + response.setStatus(HttpStatus.FORBIDDEN.value()); + JacksonUtil.writeValue(response.getWriter(), + JCPPErrorResponse.of("You don't have permission to perform this operation!", + JCPPErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)); + + } + + private void handleAuthenticationException(AuthenticationException authenticationException, HttpServletResponse response) throws IOException { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + if (authenticationException instanceof BadCredentialsException || authenticationException instanceof UsernameNotFoundException) { + JacksonUtil.writeValue(response.getWriter(), JCPPErrorResponse.of("Invalid username or password", JCPPErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); + } else if (authenticationException instanceof DisabledException) { + JacksonUtil.writeValue(response.getWriter(), JCPPErrorResponse.of("User account is not active", JCPPErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); + } else if (authenticationException instanceof LockedException) { + JacksonUtil.writeValue(response.getWriter(), JCPPErrorResponse.of("User account is locked due to security policy", JCPPErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); + } else if (authenticationException instanceof JwtExpiredTokenException) { + JacksonUtil.writeValue(response.getWriter(), JCPPErrorResponse.of("Token has expired", JCPPErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)); + } else if (authenticationException instanceof AuthMethodNotSupportedException) { + JacksonUtil.writeValue(response.getWriter(), JCPPErrorResponse.of(authenticationException.getMessage(), JCPPErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); + } else if (authenticationException instanceof UserPasswordExpiredException expiredException) { + String resetToken = expiredException.getResetToken(); + JacksonUtil.writeValue(response.getWriter(), JCPPCredentialsExpiredResponse.of(expiredException.getMessage(), resetToken)); + } else if (authenticationException instanceof UserPasswordNotValidException expiredException) { + JacksonUtil.writeValue(response.getWriter(), JCPPCredentialsViolationResponse.of(expiredException.getMessage())); + } else { + JacksonUtil.writeValue(response.getWriter(), JCPPErrorResponse.of("Authentication failed", JCPPErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); + } + } + + + private void writeResponse(JCPPErrorResponse errorResponse, HttpServletResponse response) throws IOException { + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(errorResponse.getStatus()); + JacksonUtil.writeValue(response.getWriter(), errorResponse); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPException.java b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPException.java new file mode 100644 index 0000000..e4aa34d --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/exception/JCPPException.java @@ -0,0 +1,44 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.exception; + +import lombok.Getter; + +import java.io.Serial; + +@Getter +public class JCPPException extends Exception { + + @Serial + private static final long serialVersionUID = 1L; + + private JCPPErrorCode errorCode; + + public JCPPException() { + super(); + } + + public JCPPException(JCPPErrorCode errorCode) { + this.errorCode = errorCode; + } + + public JCPPException(String message, JCPPErrorCode errorCode) { + super(message); + this.errorCode = errorCode; + } + + public JCPPException(String message, Throwable cause, JCPPErrorCode errorCode) { + super(message, cause); + this.errorCode = errorCode; + } + + public JCPPException(Throwable cause, JCPPErrorCode errorCode) { + super(cause); + this.errorCode = errorCode; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/initializing/InstallInitializingBean.java b/jcpp-app/src/main/java/sanbing/jcpp/app/initializing/InstallInitializingBean.java new file mode 100644 index 0000000..b1a8209 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/initializing/InstallInitializingBean.java @@ -0,0 +1,478 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.initializing; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.FileCopyUtils; +import sanbing.jcpp.app.dal.config.ibatis.enums.AuthorityEnum; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileTypeEnum; +import sanbing.jcpp.app.dal.config.ibatis.enums.UserStatusEnum; +import sanbing.jcpp.app.dal.entity.Gun; +import sanbing.jcpp.app.dal.entity.Pile; +import sanbing.jcpp.app.dal.entity.Station; +import sanbing.jcpp.app.dal.entity.User; +import sanbing.jcpp.app.dal.mapper.GunMapper; +import sanbing.jcpp.app.dal.mapper.PileMapper; +import sanbing.jcpp.app.dal.mapper.StationMapper; +import sanbing.jcpp.app.dal.mapper.UserMapper; +import sanbing.jcpp.app.data.InstallModeEnum; +import sanbing.jcpp.app.data.kv.*; +import sanbing.jcpp.app.service.AttributeService; +import sanbing.jcpp.app.service.security.model.UserCredentials; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * 数据库安装组件 + * 在Spring容器初始化时执行数据库操作 + * 如果失败会阻止应用启动,确保数据库环境正确 + * + * @author 九筒 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@Order(0) +public class InstallInitializingBean implements InitializingBean { + + /** + * 安装模式 + * init - 初始化数据库并加载演示数据 + * upgrade - 升级数据库 + * disabled - 不执行任何操作 + */ + @Value("${install.mode:disabled}") + private String mode; + + private final JdbcTemplate jdbcTemplate; + private final PasswordEncoder passwordEncoder; + + // Mappers for data insertion + private final UserMapper userMapper; + private final StationMapper stationMapper; + private final PileMapper pileMapper; + private final GunMapper gunMapper; + + // Services for demo data + private final AttributeService attributeService; + + @Override + @Transactional + public void afterPropertiesSet() throws Exception { + if (isDisabled()) { + log.info("数据库安装功能已禁用,跳过安装操作"); + return; + } + + try { + performInstallation(); + log.info("数据库安装操作完成"); + } catch (Exception e) { + log.error("数据库安装操作失败,应用启动终止", e); + // 抛出异常阻止Spring容器启动 + throw new RuntimeException("数据库初始化失败,应用无法启动", e); + } + } + + private boolean isDisabled() { + return "disabled".equals(mode) || mode == null || mode.isEmpty(); + } + + private void performInstallation() { + InstallModeEnum installMode = InstallModeEnum.fromMode(mode); + log.info("开始执行数据库安装操作,模式: {}", installMode.getDescription()); + + switch (installMode) { + case INIT: + doInitDatabase(); + break; + case UPGRADE: + doUpgradeDatabase(); + break; + case DISABLED: + log.info("数据库安装功能已禁用"); + break; + default: + log.warn("未知的安装模式: {}", mode); + } + } + + /** + * 实际执行数据库初始化的内部方法 + * 包括创建表结构和加载演示数据 + */ + private void doInitDatabase() { + log.info("开始初始化数据库..."); + + try { + // 1. 执行数据库架构初始化脚本 + String schemaScript = loadResourceFile("sql/schema-init.sql"); + log.info("执行数据库架构初始化脚本"); + jdbcTemplate.execute(schemaScript); + log.info("数据库架构初始化完成"); + + // 2. 加载演示数据 + log.info("开始加载演示数据..."); + doLoadDemoData(); + log.info("演示数据加载完成"); + + log.info("数据库初始化完成"); + } catch (Exception e) { + log.error("数据库初始化失败", e); + throw new RuntimeException("数据库初始化失败", e); + } + } + + /** + * 升级数据库 + */ + private void doUpgradeDatabase() { + log.info("开始升级数据库..."); + // TODO: 实现升级逻辑 + log.info("数据库升级完成"); + } + + /** + * 加载演示数据的内部方法 + */ + private void doLoadDemoData() { + log.info("开始加载演示数据..."); + + try { + // 创建系统管理员账号 + createAdminUserIfNotExists(); + + // 创建5个演示充电站 + createDemoStationsIfNotExists(); + + log.info("演示数据加载完成"); + } catch (Exception e) { + log.error("加载演示数据失败", e); + throw new RuntimeException("加载演示数据失败", e); + } + } + + private String loadResourceFile(String path) throws IOException { + ClassPathResource resource = new ClassPathResource(path); + byte[] binaryData = FileCopyUtils.copyToByteArray(resource.getInputStream()); + return new String(binaryData, StandardCharsets.UTF_8); + } + + private void createAdminUserIfNotExists() { + try { + // 检查是否已存在同名用户(大小写不敏感) + int userCount = userMapper.countByUserName("sanbing"); + + if (userCount == 0) { + // 使用固定的UUID作为管理员用户ID + UUID adminUserId = UUID.fromString("00000000-0000-0000-0000-000000000001"); + ObjectNode additionalInfo = JacksonUtil.newObjectNode(); + + // 创建UserCredentials对象 + UserCredentials credentials = new UserCredentials(); + // 使用BCrypt加密密码 + String encodedPassword = passwordEncoder.encode("sanbing@123456"); + credentials.setPassword(encodedPassword); + credentials.setEnabled(true); + credentials.setFailedLoginAttempts(0); + + User adminUser = User.builder() + .id(adminUserId) + .createdTime(LocalDateTime.now()) + .updatedTime(LocalDateTime.now()) + .additionalInfo(additionalInfo) + .status(UserStatusEnum.ENABLE) + .userName("sanbing") + .userCredentials(credentials) + .authority(AuthorityEnum.SYS_ADMIN) // 设置为系统管理员权限 + .version(1) + .build(); + + userMapper.insert(adminUser); + log.info("创建系统管理员账号: {}, 权限: {}", adminUser.getUserName(), adminUser.getAuthority()); + } else { + log.info("系统管理员账号已存在,跳过创建"); + } + } catch (Exception e) { + log.error("创建系统管理员账号失败", e); + throw e; + } + } + + /** + * 创建5个演示充电站,每个站配置不同数量的充电桩和枪 + */ + private void createDemoStationsIfNotExists() { + String[][] stationData = { + {"07d80c81-fe99-4a1f-a6aa-dc4d798b5626", "三丙家专属充电站", "S20241001001", "120.1079330444336", "30.267013549804688", "浙江省", "杭州市", "西湖区", "西溪路552-1号"}, + {"17d80c81-fe99-4a1f-a6aa-dc4d798b5627", "西湖区政府充电站", "S20241001002", "120.1279330444336", "30.277013549804688", "浙江省", "杭州市", "西湖区", "文三路168号"}, + {"27d80c81-fe99-4a1f-a6aa-dc4d798b5628", "杭州大厦充电站", "S20241001003", "120.1679330444336", "30.257013549804688", "浙江省", "杭州市", "下城区", "延安路385号"}, + {"37d80c81-fe99-4a1f-a6aa-dc4d798b5629", "钱江新城充电站", "S20241001004", "120.1879330444336", "30.247013549804688", "浙江省", "杭州市", "江干区", "富春路701号"}, + {"47d80c81-fe99-4a1f-a6aa-dc4d798b562a", "滨江高新充电站", "S20241001005", "120.1979330444336", "30.207013549804688", "浙江省", "杭州市", "滨江区", "江南大道588号"} + }; + + for (int i = 0; i < stationData.length; i++) { + Station station = createStationIfNotExists(stationData[i]); + + // 为每个充电站创建充电桩和充电枪 + // 站1: 6桩10枪, 站2: 6桩10枪, 站3: 6桩10枪, 站4: 6桩10枪, 站5: 6桩10枪 = 30桩50枪 + createDemoPilesAndGunsForStation(station, i + 1); + } + } + + private Station createStationIfNotExists(String[] data) { + try { + UUID stationId = UUID.fromString(data[0]); + Station existingStation = stationMapper.selectById(stationId); + + if (existingStation == null) { + ObjectNode additionalInfo = JacksonUtil.newObjectNode(); + + Station station = Station.builder() + .id(stationId) + .createdTime(LocalDateTime.now()) + .updatedTime(LocalDateTime.now()) + .additionalInfo(additionalInfo) + .stationName(data[1]) + .stationCode(data[2]) + .longitude(Float.parseFloat(data[3])) + .latitude(Float.parseFloat(data[4])) + .province(data[5]) + .city(data[6]) + .county(data[7]) + .address(data[8]) + .version(1) + .build(); + + stationMapper.insert(station); + log.info("创建演示电站: {}", station.getStationName()); + return station; + } else { + log.info("演示电站已存在,跳过创建: {}", data[1]); + return existingStation; + } + } catch (Exception e) { + log.error("创建演示电站失败: {}", data[1], e); + throw e; + } + } + + /** + * 为每个充电站创建充电桩和充电枪 + * 总计: 5站 x 6桩 = 30桩, 每桩1-2枪 = 50枪 + */ + private void createDemoPilesAndGunsForStation(Station station, int stationIndex) { + // 每个充电站创建6个充电桩 + for (int pileIndex = 1; pileIndex <= 6; pileIndex++) { + // 计算全局桩号:(站序号-1) * 6 + 桩序号,从20231212000001开始 + int globalPileNumber = (stationIndex - 1) * 6 + pileIndex; + Pile pile = createDemoPileForStation(station, stationIndex, pileIndex, globalPileNumber); + + // 为充电桩创建充电枪 + // 前4个充电桩每个2枪,后2个充电桩每个1枪,这样每站正好10枪 + int gunsPerPile = (pileIndex <= 4) ? 2 : 1; + createDemoGunsForPile(pile, station, stationIndex, pileIndex, gunsPerPile); + } + } + + private Pile createDemoPileForStation(Station station, int stationIndex, int pileIndex, int globalPileNumber) { + // 生成唯一的UUID + String uuidString = String.format("%08d-0000-4000-8000-%012d", + stationIndex * 1000 + pileIndex, stationIndex * 1000000L + pileIndex); + UUID pileId = UUID.fromString(uuidString); + + Pile existingPile = pileMapper.selectById(pileId); + if (existingPile != null) { + log.info("充电桩已存在,跳过创建: {}", existingPile.getPileName()); + return existingPile; + } + + ObjectNode additionalInfo = JacksonUtil.newObjectNode(); + + // 充电桩品牌和型号多样化 + String[] brands = {"星星", "特来电", "云快充", "国家电网", "南方电网", "蔚来"}; + String[] models = {"10A", "20A", "30A", "40A", "60A", "120A"}; + String brand = brands[((stationIndex - 1) * 6 + pileIndex - 1) % brands.length]; + String model = models[((stationIndex - 1) * 6 + pileIndex - 1) % models.length]; + + // 交流桩和直流桩混合 + PileTypeEnum pileType = (pileIndex % 3 == 0) ? PileTypeEnum.DC : PileTypeEnum.AC; + + // 生成桩编号:从20231212000001开始递增 + String pileCode = String.format("20231212%06d", globalPileNumber); + + Pile pile = Pile.builder() + .id(pileId) + .createdTime(LocalDateTime.now()) + .updatedTime(LocalDateTime.now()) + .additionalInfo(additionalInfo) + .pileName(String.format("%s-%d号充电桩", station.getStationName(), pileIndex)) + .pileCode(pileCode) + .protocol("yunkuaichongV150") + .stationId(station.getId()) + .brand(brand) + .model(model) + .manufacturer(brand) + .type(pileType) + .version(1) + .build(); + + pileMapper.insert(pile); + log.info("创建演示充电桩: {}", pile.getPileName()); + + // 为新创建的充电桩插入演示属性(模拟在线/离线状态) + loadDemoPileAttributes(pileId, stationIndex, pileIndex); + + return pile; + } + + private void createDemoGunsForPile(Pile pile, Station station, int stationIndex, int pileIndex, int gunCount) { + for (int gunIndex = 1; gunIndex <= gunCount; gunIndex++) { + // 生成唯一的UUID + String uuidString = String.format("%08d-1111-4000-8000-%012d", + stationIndex * 10000 + pileIndex * 10 + gunIndex, + stationIndex * 10000000L + pileIndex * 100000L + gunIndex); + UUID gunId = UUID.fromString(uuidString); + + Gun existingGun = gunMapper.selectById(gunId); + if (existingGun != null) { + log.info("充电枪已存在,跳过创建: {}", existingGun.getGunName()); + continue; + } + + ObjectNode additionalInfo = JacksonUtil.newObjectNode(); + + Gun gun = Gun.builder() + .id(gunId) + .createdTime(LocalDateTime.now()) + .updatedTime(LocalDateTime.now()) + .additionalInfo(additionalInfo) + .gunNo(String.format("%02d", gunIndex)) + .gunName(String.format("%s-%d号枪", pile.getPileName(), gunIndex)) + .gunCode(String.format("%s-%02d", pile.getPileCode(), gunIndex)) + .stationId(station.getId()) + .pileId(pile.getId()) + .version(1) + .build(); + + gunMapper.insert(gun); + log.info("创建演示充电枪: {}", gun.getGunName()); + + // 为新创建的充电枪插入演示属性(模拟不同运行状态) + loadDemoGunAttributes(gunId, stationIndex, pileIndex, gunIndex); + } + } + + + /** + * 为充电桩加载演示属性,模拟在线/离线状态 + */ + private void loadDemoPileAttributes(UUID pileId, int stationIndex, int pileIndex) { + long currentTime = System.currentTimeMillis(); + + // 模拟80%在线率,20%离线率 + boolean isOnline = ((stationIndex + pileIndex) % 5) != 0; // 80%在线 + String status = isOnline ? "ONLINE" : "OFFLINE"; + + // 插入状态属性 + AttributeKvEntry statusAttr = new BaseAttributeKvEntry( + new StringDataEntry(AttrKeyEnum.STATUS.getCode(), status), + currentTime + ); + attributeService.save(pileId, statusAttr); + + if (isOnline) { + // 在线桩设置连接时间 + AttributeKvEntry connectedAtAttr = new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.CONNECTED_AT.getCode(), currentTime - (3600000L * (pileIndex % 12))), + currentTime + ); + attributeService.save(pileId, connectedAtAttr); + } else { + // 离线桩设置断开时间 + AttributeKvEntry disconnectedAtAttr = new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.DISCONNECTED_AT.getCode(), currentTime - (1800000L * (pileIndex % 6))), + currentTime + ); + attributeService.save(pileId, disconnectedAtAttr); + } + + log.info("为充电桩 {} 设置演示状态属性: {}", pileId, status); + } + + /** + * 为充电枪加载演示属性,模拟多种运行状态 + */ + private void loadDemoGunAttributes(UUID gunId, int stationIndex, int pileIndex, int gunIndex) { + long currentTime = System.currentTimeMillis(); + + // 模拟九种充电枪运行状态的分布 + String[] gunStatuses = { + "IDLE", // 空闲 - 30% + "IDLE", + "IDLE", + "CHARGING", // 充电中 - 25% + "CHARGING", + "INSERTED", // 已插枪 - 15% + "CHARGE_COMPLETE", // 充电完成 - 10% + "FAULT", // 故障 - 8% + "RESERVED", // 预约 - 5% + "DISCHARGING", // 放电中 - 3% + "DISCHARGE_READY", // 放电准备 - 2% + "DISCHARGE_COMPLETE" // 放电完成 - 2% + }; + + // 基于索引选择状态,确保有良好的分布 + int statusIndex = ((stationIndex - 1) * 20 + (pileIndex - 1) * 3 + (gunIndex - 1)) % gunStatuses.length; + String gunStatus = gunStatuses[statusIndex]; + + // 插入枪运行状态属性 + AttributeKvEntry statusAttr = new BaseAttributeKvEntry( + new StringDataEntry(AttrKeyEnum.GUN_RUN_STATUS.getCode(), gunStatus), + currentTime + ); + attributeService.save(gunId, statusAttr); + + // 根据状态设置额外属性 + if ("CHARGING".equals(gunStatus) || "DISCHARGING".equals(gunStatus)) { + // 充电中或放电中的枪设置功率 + double power = 10.0 + (statusIndex % 8) * 5.0; // 10-45kW + AttributeKvEntry powerAttr = new BaseAttributeKvEntry( + new DoubleDataEntry("chargingPower", power), + currentTime + ); + attributeService.save(gunId, powerAttr); + } + + if ("FAULT".equals(gunStatus)) { + // 故障枪设置故障代码 + String[] faultCodes = {"E001", "E002", "E003", "E004", "E005"}; + String faultCode = faultCodes[statusIndex % faultCodes.length]; + AttributeKvEntry faultAttr = new BaseAttributeKvEntry( + new StringDataEntry("faultCode", faultCode), + currentTime + ); + attributeService.save(gunId, faultAttr); + } + + log.info("为充电枪 {} 设置演示状态属性: {}", gunId, gunStatus); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/initializing/StatusCleanupInitializingBean.java b/jcpp-app/src/main/java/sanbing/jcpp/app/initializing/StatusCleanupInitializingBean.java new file mode 100644 index 0000000..45b4c73 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/initializing/StatusCleanupInitializingBean.java @@ -0,0 +1,243 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.initializing; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; +import sanbing.jcpp.app.dal.entity.Attribute; +import sanbing.jcpp.app.dal.mapper.AttributeMapper; +import sanbing.jcpp.app.data.PileSession; +import sanbing.jcpp.app.data.kv.*; +import sanbing.jcpp.app.data.page.PageDataIterable; +import sanbing.jcpp.app.service.AttributeService; +import sanbing.jcpp.app.service.PileService; +import sanbing.jcpp.app.service.cache.session.PileSessionCacheKey; +import sanbing.jcpp.infrastructure.cache.CacheValueWrapper; +import sanbing.jcpp.infrastructure.cache.TransactionalCache; + +import java.util.UUID; + +/** + * 状态清洗组件 + * 在Spring容器初始化时执行充电桩状态的全量清洗 + * 如果失败会阻止应用启动,确保数据状态一致性 + * + * @author baigod + */ +@Slf4j +@Component +@RequiredArgsConstructor +@Order(10) // 在数据库初始化之后执行 +public class StatusCleanupInitializingBean implements InitializingBean { + + private final PileService pileService; + private final TransactionalCache pileSessionCache; + private final AttributeMapper attributeMapper; + private final AttributeService attributeService; + + @Value("${service.protocol.sessions.default-inactivity-timeout-in-sec:600}") + private int inactivityTimeoutInSec; + + // 分页大小,控制每次处理的充电桩数量 + private static final int FETCH_PAGE_SIZE = 1000; + + @Override + public void afterPropertiesSet() throws Exception { + log.info("开始执行状态清洗..."); + + try { + performStatusCleanup(); + log.info("状态清洗执行完成"); + } catch (Exception e) { + log.error("状态清洗执行失败,应用启动终止", e); + // 抛出异常阻止Spring容器启动,确保数据状态一致性 + throw new RuntimeException("系统状态清理失败,应用无法在数据状态不一致的情况下启动", e); + } + } + + /** + * 执行充电桩状态清洗任务。 + * + * 该方法的主要功能是检查所有充电桩的状态,并根据预定义的逻辑进行必要的更新,以确保数据库和缓存的状态一致性。 + * + * 核心逻辑: + * 1. 数据库状态为准,缓存为辅助判断。 + * 2. 有会话连接 = 在线,无会话连接 = 可能离线。 + * 3. 确保数据库和缓存的最终一致性。 + * + * 处理场景: + * - 场景1:有会话连接,但数据库状态为OFFLINE -> 更新为ONLINE。 + * - 场景2:无会话连接,但数据库状态为ONLINE -> 检查最后活跃时间,超时则设为OFFLINE。 + * - 场景3:无会话连接,数据库状态也为OFFLINE -> 跳过。 + * - 场景4:有会话连接,数据库状态也为ONLINE -> 跳过。 + * + * 异常处理: + * - 网络分区:依赖最后活跃时间判断。 + * - 系统重启:会话丢失,通过重连恢复状态。 + * - 缓存异常:以数据库状态为准。 + * - 数据库异常:记录错误,继续处理其他设备。 + * + * @throws RuntimeException 如果在执行状态清洗过程中发生不可恢复的异常。 + */ + private void performStatusCleanup() { + log.info("开始执行充电桩状态清洗..."); + + long startTime = System.currentTimeMillis(); + int processedCount = 0; // 已处理的充电桩数量 + int updatedCount = 0; // 状态已更新的充电桩数量 + int onlineCount = 0; // 最终在线的充电桩数量 + int offlineCount = 0; // 最终离线的充电桩数量 + + // 计算不活跃阈值时间,用于判断是否超时 + long currentTime = System.currentTimeMillis(); + long timeoutThreshold = currentTime - (inactivityTimeoutInSec * 1000L); + + try { + // 使用分页查询所有充电桩,避免一次性加载过多数据导致内存溢出 + PageDataIterable pileIterable = new PageDataIterable<>( + pileService::findPilesWithPagination, + FETCH_PAGE_SIZE + ); + + for (var pile : pileIterable) { + processedCount++; + String pileCode = pile.getPileCode(); + + try { + // 获取当前数据库中的状态 + String currentDbStatus = pileService.findPileStatus(pile.getId()); + boolean isCurrentlyOnline = PileStatusEnum.ONLINE.name().equals(currentDbStatus); + + // 检查是否有活跃的会话连接 + boolean hasActiveSession = checkActiveSession(pileCode); + + // 根据会话状态、数据库状态和超时时间决定目标状态 + String targetStatus = determineTargetStatus(hasActiveSession, isCurrentlyOnline, pile.getId(), timeoutThreshold); + + // 如果需要更新状态,则执行更新操作 + if (!targetStatus.equals(currentDbStatus)) { + updatePileStatusWithTimestamp(pile.getId(), targetStatus, currentTime); + log.info("更新充电桩状态: pileCode={}, 从 {} 更新为 {}, 会话状态={}", + pileCode, currentDbStatus, targetStatus, hasActiveSession ? "有" : "无"); + updatedCount++; + } + + // 统计最终状态 + if (PileStatusEnum.ONLINE.name().equals(targetStatus)) { + onlineCount++; + } else { + offlineCount++; + } + + } catch (Exception e) { + // 捕获单个充电桩处理过程中的异常,记录日志并继续处理其他充电桩 + log.error("处理充电桩状态清洗失败: pileCode={}", pileCode, e); + } + } + + } catch (Exception e) { + // 捕获全局异常,记录日志并抛出运行时异常 + log.error("执行状态清洗过程中发生异常", e); + throw new RuntimeException("状态清洗执行失败", e); + } + + long endTime = System.currentTimeMillis(); + // 记录状态清洗的汇总信息,包括处理数量、更新数量、在线数量、离线数量和耗时 + log.info("充电桩状态清洗完成: 处理数量={}, 更新数量={}, 在线数量={}, 离线数量={}, 耗时={}ms", + processedCount, updatedCount, onlineCount, offlineCount, endTime - startTime); + } + + /** + * 检查充电桩是否有活跃的会话连接 + */ + private boolean checkActiveSession(String pileCode) { + try { + CacheValueWrapper sessionWrapper = pileSessionCache.get(new PileSessionCacheKey(pileCode)); + return sessionWrapper != null && sessionWrapper.get() != null; + } catch (Exception e) { + log.warn("检查充电桩会话失败: pileCode={}", pileCode, e); + return false; + } + } + + /** + * 根据会话状态、数据库状态和超时时间决定目标状态 + */ + private String determineTargetStatus(boolean hasActiveSession, boolean isCurrentlyOnline, UUID pileId, long timeoutThreshold) { + // 有活跃会话,应该在线 + if (hasActiveSession) { + return PileStatusEnum.ONLINE.name(); + } + + // 无活跃会话,需要检查最后活跃时间 + if (isCurrentlyOnline) { + // 当前显示在线但无会话,检查最后活跃时间 + Attribute lastActiveAttr = attributeMapper.findByEntityAndKey(pileId, AttrKeyEnum.LAST_ACTIVE_TIME.getCode()); + if (lastActiveAttr != null && lastActiveAttr.getLongV() != null) { + long lastActiveTime = lastActiveAttr.getLongV(); + if (lastActiveTime < timeoutThreshold) { + // 超时了,应该设为离线 + log.debug("充电桩超时未活跃,设为离线: pileId={}, lastActiveTime={}, threshold={}", + pileId, lastActiveTime, timeoutThreshold); + return PileStatusEnum.OFFLINE.name(); + } + } else { + // 没有最后活跃时间记录,但当前显示在线且无会话,保守地设为离线 + log.debug("充电桩无最后活跃时间记录但当前在线且无会话,设为离线: pileId={}", pileId); + return PileStatusEnum.OFFLINE.name(); + } + } + + // 其他情况保持当前状态 + return isCurrentlyOnline ? PileStatusEnum.ONLINE.name() : PileStatusEnum.OFFLINE.name(); + } + + /** + * 更新充电桩状态,包括时间戳 + */ + private void updatePileStatusWithTimestamp(UUID pileId, String status, long currentTime) { + try { + // 更新状态属性 + AttributeKvEntry statusAttr = new BaseAttributeKvEntry( + new StringDataEntry(AttrKeyEnum.STATUS.getCode(), status), + currentTime + ); + attributeService.save(pileId, statusAttr); + + // 根据状态更新相应的时间戳 + if (PileStatusEnum.ONLINE.name().equals(status)) { + // 设为在线时更新连接时间和最后活跃时间 + updatePileAttribute(pileId, AttrKeyEnum.CONNECTED_AT, currentTime); + updatePileAttribute(pileId, AttrKeyEnum.LAST_ACTIVE_TIME, currentTime); + } else if (PileStatusEnum.OFFLINE.name().equals(status)) { + // 设为离线时更新断开连接时间 + updatePileAttribute(pileId, AttrKeyEnum.DISCONNECTED_AT, currentTime); + } + + } catch (Exception e) { + log.error("更新充电桩状态失败: pileId={}, status={}", pileId, status, e); + throw e; + } + } + + /** + * 更新充电桩特定属性 + */ + private void updatePileAttribute(UUID pileId, AttrKeyEnum key, long value) { + long currentTime = System.currentTimeMillis(); + AttributeKvEntry attr = new BaseAttributeKvEntry( + new LongDataEntry(key.getCode(), value), + currentTime + ); + attributeService.save(pileId, attr); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/AttributeService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/AttributeService.java new file mode 100644 index 0000000..88c8b64 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/AttributeService.java @@ -0,0 +1,34 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service; + +import com.google.common.util.concurrent.ListenableFuture; +import sanbing.jcpp.app.data.kv.AttributeKvEntry; +import sanbing.jcpp.app.data.kv.AttributesSaveResult; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface AttributeService { + + ListenableFuture> find(UUID entityId, String attrKey); + + ListenableFuture> find(UUID entityId, Collection attrKeys); + + ListenableFuture> findAll(UUID entityId); + + ListenableFuture save(UUID entityId, List attributes); + + ListenableFuture save(UUID entityId, AttributeKvEntry attribute); + + ListenableFuture> removeAll(UUID entityId, List attrKeys); + + int removeAllByEntityId(UUID entityId); + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/DashboardService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/DashboardService.java new file mode 100644 index 0000000..f9c76b1 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/DashboardService.java @@ -0,0 +1,22 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service; + +import sanbing.jcpp.app.adapter.response.DashboardStats; + +/** + * 仪表盘服务接口 + * + * @author 九筒 + */ +public interface DashboardService { + + /** + * 获取仪表盘统计数据 + */ + DashboardStats getDashboardStats(); +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/DownlinkCallService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/DownlinkCallService.java index 86a79b7..b3e78ee 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/DownlinkCallService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/DownlinkCallService.java @@ -22,7 +22,7 @@ import sanbing.jcpp.protocol.adapter.DownlinkController; import java.util.UUID; /** - * @author baigod + * @author 九筒 */ @Slf4j public abstract class DownlinkCallService { diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/GunService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/GunService.java new file mode 100644 index 0000000..215859c --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/GunService.java @@ -0,0 +1,79 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service; + +import sanbing.jcpp.app.adapter.request.GunCreateRequest; +import sanbing.jcpp.app.adapter.request.GunQueryRequest; +import sanbing.jcpp.app.adapter.request.GunUpdateRequest; +import sanbing.jcpp.app.adapter.response.GunWithStatusResponse; +import sanbing.jcpp.app.adapter.response.PageResponse; +import sanbing.jcpp.app.dal.entity.Gun; +import sanbing.jcpp.proto.gen.ProtocolProto.GunRunStatus; + +import java.util.UUID; + +public interface GunService { + + /** + * 创建充电枪 + */ + Gun createGun(GunCreateRequest request); + + /** + * 根据ID查询充电枪 + */ + Gun findById(UUID id); + + /** + * 更新充电枪 + */ + Gun updateGun(UUID id, GunUpdateRequest request); + + /** + * 删除充电枪 + */ + void deleteGun(UUID id); + + /** + * 分页查询充电枪及状态信息 + */ + PageResponse queryGunsWithStatus(GunQueryRequest request); + + /** + * 根据充电桩编码和充电枪编码查询充电枪 + */ + Gun findByPileCodeAndGunCode(String pileCode, String gunCode); + + /** + * 查询充电枪状态 + * + * @param gunId 充电枪ID + * @return 状态字符串,如果不存在返回null + */ + String findGunStatus(UUID gunId); + + /** + * 保存充电枪状态变更时序数据 - 高性能版本 + * + * @param gunId 充电枪ID + * @param status 状态 + * @param ts 时间戳,如果为null则使用当前时间 + */ + void saveGunStatusChange(UUID gunId, String status, Long ts); + + /** + * 处理充电枪状态上报 + * + * @param pileCode 充电桩编码 + * @param gunCode 充电枪编码 + * @param protoStatus Proto状态 + * @param ts 时间戳 + * @return 是否需要更新充电桩状态 + */ + boolean handleGunRunStatus(String pileCode, String gunCode, GunRunStatus protoStatus, long ts); + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java index 97a0a22..09c7e18 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileProtocolService.java @@ -7,17 +7,13 @@ package sanbing.jcpp.app.service; import sanbing.jcpp.infrastructure.queue.Callback; -import sanbing.jcpp.proto.gen.ProtocolProto; -import sanbing.jcpp.proto.gen.ProtocolProto.OfflineCardBalanceUpdateRequest; -import sanbing.jcpp.proto.gen.ProtocolProto.OfflineCardSyncRequest; -import sanbing.jcpp.proto.gen.ProtocolProto.SetPricingRequest; -import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; +import sanbing.jcpp.proto.gen.ProtocolProto.*; import java.math.BigDecimal; import java.time.LocalDateTime; /** - * @author baigod + * @author 九筒 */ public interface PileProtocolService { /** @@ -30,6 +26,11 @@ public interface PileProtocolService { */ void heartBeat(UplinkQueueMessage uplinkQueueMessage, Callback callback); + /** + * 处理会话关闭事件 + */ + void onSessionCloseEvent(UplinkQueueMessage uplinkQueueMessage, Callback callback); + /** * 校验计费模型 */ @@ -58,8 +59,6 @@ public interface PileProtocolService { /** * 远程启动反馈 * - * @param uplinkQueueMessage - * @param callback */ void onRemoteStartChargingResponse(UplinkQueueMessage uplinkQueueMessage, Callback callback); @@ -77,8 +76,8 @@ public interface PileProtocolService { * 启动充电(支持卡号和并充序号) * 当 parallelNo 不为空时,自动使用并充启机命令 */ - void startCharge(String pileCode, String gunCode, BigDecimal limitYuan, String orderNo, - String logicalCardNo, String physicalCardNo, String parallelNo); + void startCharge(String pileCode, String gunCode, BigDecimal limitYuan, String orderNo, + String logicalCardNo, String physicalCardNo, String parallelNo); /** * 停止充电 @@ -121,9 +120,9 @@ public interface PileProtocolService { void postBmsAbort(UplinkQueueMessage uplinkQueueMessage, Callback callback); /** - * 远程更新 + * 远程更新 */ - void otaRequest(ProtocolProto.OtaRequest request); + void otaRequest(OtaRequest request); /** * 远程更新应答 @@ -176,4 +175,9 @@ public interface PileProtocolService { */ void onTimeSyncResponse(UplinkQueueMessage uplinkQueueMessage, Callback callback); + /** + * 充电过程BMS需求与充电机输出 + */ + void postBmsDemandChargerOutput(UplinkQueueMessage uplinkQueueMessage, Callback callback); + } \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileService.java new file mode 100644 index 0000000..5e704d4 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileService.java @@ -0,0 +1,118 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service; + +import com.google.common.util.concurrent.ListenableFuture; +import sanbing.jcpp.app.adapter.request.PileCreateRequest; +import sanbing.jcpp.app.adapter.request.PileQueryRequest; +import sanbing.jcpp.app.adapter.request.PileUpdateRequest; +import sanbing.jcpp.app.adapter.response.PageResponse; +import sanbing.jcpp.app.adapter.response.PileOptionResponse; +import sanbing.jcpp.app.adapter.response.PileWithStatusResponse; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; +import sanbing.jcpp.app.dal.entity.Pile; +import sanbing.jcpp.app.data.kv.AttributesSaveResult; +import sanbing.jcpp.app.exception.JCPPException; + +import java.util.List; +import java.util.UUID; + +public interface PileService { + + /** + * 创建充电桩 + */ + Pile createPile(PileCreateRequest request); + + /** + * 根据ID查询充电桩 + */ + Pile findById(UUID id); + + /** + * 更新充电桩 + */ + Pile updatePile(UUID id, PileUpdateRequest request) throws JCPPException; + + /** + * 删除充电桩 + */ + void deletePile(UUID id) throws JCPPException; + + /** + * 分页查询充电桩及状态信息 + */ + PageResponse queryPilesWithStatus(PileQueryRequest request); + + /** + * 获取充电桩选项列表 + */ + List getPileOptions(); + + /** + * 更新充电桩状态 + * + * @param pileId 充电桩ID + * @param status 新状态 + */ + void updatePileStatus(UUID pileId, PileStatusEnum status); + + /** + * 根据充电桩编码更新状态 + * + * @param pileCode 充电桩编码 + * @param status 新状态 + */ + void updatePileStatusByCode(String pileCode, PileStatusEnum status); + + /** + * 查询所有充电桩 + */ + List findAll(); + + /** + * 分页查询充电桩(用于状态清洗等批处理场景) + * + * @param offset 偏移量 + * @param limit 限制数量 + * @return 充电桩列表 + */ + List findPilesWithPagination(int offset, int limit); + + /** + * 查询充电桩状态 + * + * @param pileId 充电桩ID + * @return 状态字符串,如果不存在返回null + */ + String findPileStatus(UUID pileId); + + /** + * 处理充电桩登录后的状态管理(优化版) + * 执行:更新STATUS为ONLINE → 更新CONNECTED_AT → 更新LAST_ACTIVE_TIME + * + * @param pileId 充电桩ID + * @return 异步操作结果 + */ + ListenableFuture handlePileLogin(UUID pileId); + + /** + * 处理充电桩心跳时的状态管理(优化版) + * 执行:更新STATUS为ONLINE → 更新LAST_ACTIVE_TIME + * + * @param pileId 充电桩ID + * @return 异步操作结果 + */ + ListenableFuture handlePileHeartbeat(UUID pileId); + + /** + * 处理充电桩会话关闭时的状态管理 + * + * @param pileCode 充电桩编码 + */ + void handlePileSessionClose(String pileCode); +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileSessionService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileSessionService.java new file mode 100644 index 0000000..b34e8f7 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/PileSessionService.java @@ -0,0 +1,88 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service; + +import sanbing.jcpp.app.dal.entity.Pile; +import sanbing.jcpp.app.data.PileSession; +import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; + +import java.util.List; +import java.util.Optional; + +/** + * PileSession管理服务 + * 负责充电桩会话的生命周期管理 + *

+ * 设计理念: + * - 基于事件驱动的会话管理(登录、心跳、会话关闭) + * - 依赖缓存TTL自动过期机制,无需定时清理 + * - 缓存中无会话 = 充电桩离线 + * + * @author 九筒 + */ +public interface PileSessionService { + + /** + * 创建或更新PileSession + * 智能判断:如果会话存在则更新活跃时间,不存在则创建新会话 + * 适用于登录、心跳等所有场景,最大程度减少Redis交互次数 + * + * @param uplinkQueueMessage 上行消息 + * @param pile 充电桩实体 + * @param remoteAddress 远程地址 + * @param nodeId 节点ID + * @param nodeIp 节点IP + * @param restPort REST端口 + * @param grpcPort GRPC端口 + * @return 创建或更新的PileSession + */ + PileSession createOrUpdateSession(UplinkQueueMessage uplinkQueueMessage, + Pile pile, + String remoteAddress, + String nodeId, + String nodeIp, + int restPort, + int grpcPort); + + /** + * 获取PileSession + * + * @param pileCode 充电桩编码 + * @return PileSession的可选包装,如果不存在则返回空 + */ + Optional getSession(String pileCode); + + + + /** + * 检查是否存在活跃的会话 + * + * @param pileCode 充电桩编码 + * @return 是否存在活跃会话 + */ + boolean hasActiveSession(String pileCode); + + /** + * 移除会话 + * 适用于登出、会话超时等场景 + * + * @param pileCode 充电桩编码 + */ + void removeSession(String pileCode); + + + /** + * 批量检查会话状态 + * 用于系统启动时的状态清洗 + * + * @param pileCodes 充电桩编码列表 + * @return 存在活跃会话的充电桩编码列表 + */ + List checkActiveSessions(java.util.List pileCodes); + + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/ProtocolService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/ProtocolService.java new file mode 100644 index 0000000..96a50de --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/ProtocolService.java @@ -0,0 +1,27 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service; + +import sanbing.jcpp.app.adapter.response.ProtocolOption; + +import java.util.List; + +/** + * 协议服务接口 + * + * @author 九筒 + * @since 2024-12-22 + */ +public interface ProtocolService { + + /** + * 获取所有支持的协议选项列表 + * @return 协议选项列表 + */ + List getSupportedProtocols(); + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/StationService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/StationService.java new file mode 100644 index 0000000..d0f54e8 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/StationService.java @@ -0,0 +1,61 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service; + +import sanbing.jcpp.app.adapter.request.StationCreateRequest; +import sanbing.jcpp.app.adapter.request.StationQueryRequest; +import sanbing.jcpp.app.adapter.request.StationUpdateRequest; +import sanbing.jcpp.app.adapter.response.PageResponse; +import sanbing.jcpp.app.adapter.response.StationOption; +import sanbing.jcpp.app.dal.entity.Station; +import sanbing.jcpp.app.exception.JCPPException; + +import java.util.List; +import java.util.UUID; + +/** + * 充电站服务接口 + * + * @author 九筒 + */ +public interface StationService { + + /** + * 分页查询充电站 + */ + PageResponse getStations(StationQueryRequest request); + + /** + * 根据ID获取充电站 + */ + Station getStationById(UUID id); + + /** + * 创建充电站 + */ + Station createStation(StationCreateRequest request); + + /** + * 更新充电站 + */ + Station updateStation(UUID id, StationUpdateRequest request) throws JCPPException; + + /** + * 删除充电站 + */ + void deleteStation(UUID id) throws JCPPException; + + /** + * 获取充电站选项列表(用于下拉选择) + */ + List getStationOptions(); + + /** + * 搜索充电站选项列表(支持关键字搜索和分页) + */ + List searchStationOptions(String keyword, int page, int size); +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/UserService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/UserService.java new file mode 100644 index 0000000..dd1d980 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/UserService.java @@ -0,0 +1,72 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service; + +import org.springframework.security.core.AuthenticationException; +import sanbing.jcpp.app.dal.entity.User; +import sanbing.jcpp.app.service.security.model.UserCredentials; + +import java.util.UUID; + +/** + * 用户服务接口 + * + * @author 九筒 + */ +public interface UserService { + + /** + * 根据ID查询用户 + * + * @param id 用户ID + * @return 用户实体,如果不存在返回null + */ + User findById(UUID id); + + /** + * 根据用户名查询用户 + * + * @param username 用户名 + * @return 用户实体,如果不存在返回null + */ + User findUserByUsername(String username); + + /** + * 增加用户登录失败次数 + * + * @param userId 用户ID + * @return 更新后的失败次数 + */ + int increaseFailedLoginAttempts(UUID userId); + + /** + * 设置用户凭证启用状态 + * + * @param userId 用户ID + * @param enabled 是否启用,true-启用,false-禁用 + */ + void setUserCredentialsEnabled(UUID userId, boolean enabled); + + /** + * 重置用户登录失败次数 + * + * @param userId 用户ID + */ + void resetFailedLoginAttempts(UUID userId); + + /** + * 验证用户凭证 + * + * @param userId 用户ID + * @param userCredentials 用户凭证 + * @param username 用户名 + * @param password 密码 + * @throws AuthenticationException 验证失败时抛出 + */ + void validateUserCredentials(UUID userId, UserCredentials userCredentials, String username, String password) throws AuthenticationException; + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/CacheExecutorService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/CacheExecutorService.java new file mode 100644 index 0000000..c605387 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/CacheExecutorService.java @@ -0,0 +1,24 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.cache; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import sanbing.jcpp.infrastructure.util.async.AbstractListeningExecutor; + +@Component +public class CacheExecutorService extends AbstractListeningExecutor { + + @Value("${cache.maximumPoolSize}") + private int poolSize; + + @Override + protected int getThreadPollSize() { + return poolSize; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCacheEvictEvent.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCacheEvictEvent.java new file mode 100644 index 0000000..e355c7b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCacheEvictEvent.java @@ -0,0 +1,21 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.cache.attribute; + +import java.util.UUID; + +/** + * 属性缓存驱逐事件 + * + * @author baigod + */ +public record AttributeCacheEvictEvent(UUID entityId, String attrKey) { + + public AttributeCacheEvictEvent(UUID entityId) { + this(entityId, null); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCacheKey.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCacheKey.java new file mode 100644 index 0000000..6e1fd2e --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCacheKey.java @@ -0,0 +1,31 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.cache.attribute; + +import lombok.Builder; +import sanbing.jcpp.infrastructure.cache.VersionedCacheKey; + +import java.io.Serial; +import java.util.UUID; + +@Builder +public record AttributeCacheKey(UUID entityId, String attrKey) implements VersionedCacheKey { + + @Serial + private static final long serialVersionUID = 1L; + + @Override + public String toString() { + return entityId + ":" + attrKey; + } + + @Override + public boolean isVersioned() { + return false; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCaffeineCache.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCaffeineCache.java new file mode 100644 index 0000000..5e8e2f6 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeCaffeineCache.java @@ -0,0 +1,24 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.cache.attribute; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.data.kv.AttributeKvEntry; +import sanbing.jcpp.infrastructure.cache.CacheConstants; +import sanbing.jcpp.infrastructure.cache.VersionedCaffeineCache; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("AttributeCache") +public class AttributeCaffeineCache extends VersionedCaffeineCache { + + public AttributeCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.ATTRIBUTES_CACHE); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeRedisCache.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeRedisCache.java new file mode 100644 index 0000000..9ca7138 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/attribute/AttributeRedisCache.java @@ -0,0 +1,43 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.cache.attribute; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.data.kv.AttributeKvEntry; +import sanbing.jcpp.app.data.kv.BaseAttributeKvEntry; +import sanbing.jcpp.infrastructure.cache.*; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("AttributeCache") +public class AttributeRedisCache extends VersionedRedisCache { + + public AttributeRedisCache(JCPPRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, LettuceConnectionFactory connectionFactory) { + super(CacheConstants.ATTRIBUTES_CACHE, cacheSpecsMap, connectionFactory, configuration, new JCPPRedisSerializer<>() { + + @Override + public byte[] serialize(AttributeKvEntry attribute) throws SerializationException { + // 使用自定义序列化方法避免Optional类型问题 + if (attribute instanceof BaseAttributeKvEntry attributeKvEntry) { + return attributeKvEntry.toJsonBytes(); + } else { + // 兜底方案,如果不是BaseAttributeKvEntry类型,仍然使用JacksonUtil + return JacksonUtil.writeValueAsBytes(attribute); + } + } + + @Override + public AttributeKvEntry deserialize(AttributeCacheKey key, byte[] bytes) throws SerializationException { + // 使用自定义反序列化方法避免Optional类型问题 + return BaseAttributeKvEntry.fromJsonBytes(bytes); + } + }); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheEvictEvent.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheEvictEvent.java new file mode 100644 index 0000000..44b6372 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheEvictEvent.java @@ -0,0 +1,19 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.cache.gun; + +import lombok.Data; + +import java.util.UUID; + +@Data +public class GunCacheEvictEvent { + + private UUID gunId; + private String pileCode; + private String gunCode; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheKey.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheKey.java new file mode 100644 index 0000000..188cf9a --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCacheKey.java @@ -0,0 +1,44 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.cache.gun; + +import lombok.Builder; +import sanbing.jcpp.infrastructure.cache.VersionedCacheKey; + +import java.io.Serial; +import java.util.UUID; + +@Builder +public record GunCacheKey(UUID gunId, String pileCode, String gunCode) implements VersionedCacheKey { + + @Serial + private static final long serialVersionUID = 1L; + + public GunCacheKey(UUID gunId) { + this(gunId, null, null); + } + + public GunCacheKey(String pileCode, String gunCode) { + this(null, pileCode, gunCode); + } + + @Override + public String toString() { + if (gunId != null) { + return gunId.toString(); + } else if (pileCode != null && gunCode != null) { + return pileCode + ":" + gunCode; + } else { + throw new IllegalStateException("GunCacheKey 必须包含有效的 gunId 或者 pileCode+gunCode 组合"); + } + } + + @Override + public boolean isVersioned() { + return gunId == null; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCaffeineCache.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCaffeineCache.java new file mode 100644 index 0000000..d0b8339 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunCaffeineCache.java @@ -0,0 +1,23 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.cache.gun; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.dal.entity.Gun; +import sanbing.jcpp.infrastructure.cache.CacheConstants; +import sanbing.jcpp.infrastructure.cache.VersionedCaffeineCache; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("GunCache") +public class GunCaffeineCache extends VersionedCaffeineCache { + + public GunCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.GUN_CACHE); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunRedisCache.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunRedisCache.java new file mode 100644 index 0000000..608f750 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/gun/GunRedisCache.java @@ -0,0 +1,35 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.cache.gun; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.dal.entity.Gun; +import sanbing.jcpp.infrastructure.cache.*; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("GunCache") +public class GunRedisCache extends VersionedRedisCache { + + public GunRedisCache(JCPPRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, LettuceConnectionFactory connectionFactory) { + super(CacheConstants.GUN_CACHE, cacheSpecsMap, connectionFactory, configuration, new JCPPRedisSerializer<>() { + + @Override + public byte[] serialize(Gun gun) throws SerializationException { + return JacksonUtil.writeValueAsBytes(gun); + } + + @Override + public Gun deserialize(GunCacheKey key, byte[] bytes) throws SerializationException { + return JacksonUtil.fromBytes(bytes, Gun.class); + } + }); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheKey.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheKey.java index 2d2364b..b2ee4be 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheKey.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/pile/PileCacheKey.java @@ -7,26 +7,17 @@ package sanbing.jcpp.app.service.cache.pile; import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import sanbing.jcpp.infrastructure.cache.VersionedCacheKey; import java.io.Serial; import java.util.Optional; import java.util.UUID; -@Getter -@EqualsAndHashCode -@RequiredArgsConstructor @Builder -public class PileCacheKey implements VersionedCacheKey { +public record PileCacheKey(UUID pileId, String pileCode) implements VersionedCacheKey { @Serial - private static final long serialVersionUID = 6366389552842340207L; - - private final UUID pileId; - private final String pileCode; + private static final long serialVersionUID = 1L; public PileCacheKey(UUID pileId) { this(pileId, null); @@ -43,7 +34,7 @@ public class PileCacheKey implements VersionedCacheKey { @Override public boolean isVersioned() { - return pileId != null; + return pileId == null; } } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCacheKey.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCacheKey.java index 089d074..cc7e593 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCacheKey.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCacheKey.java @@ -7,24 +7,14 @@ package sanbing.jcpp.app.service.cache.session; import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; import java.io.Serializable; /** - * @author baigod + * @author 九筒 */ -@Getter -@EqualsAndHashCode @Builder -public class PileSessionCacheKey implements Serializable { - - private final String pileCode; - - public PileSessionCacheKey(String pileCode) { - this.pileCode = pileCode; - } +public record PileSessionCacheKey(String pileCode) implements Serializable { @Override public String toString() { diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCaffeineCache.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCaffeineCache.java index 21876d8..0da756b 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCaffeineCache.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionCaffeineCache.java @@ -14,7 +14,7 @@ import sanbing.jcpp.infrastructure.cache.CacheConstants; import sanbing.jcpp.infrastructure.cache.CaffeineTransactionalCache; /** - * @author baigod + * @author 九筒 */ @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) @Service("PileSessionCache") diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionRedisCache.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionRedisCache.java index 3c14ef4..f05336c 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionRedisCache.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/cache/session/PileSessionRedisCache.java @@ -15,7 +15,7 @@ import sanbing.jcpp.infrastructure.cache.*; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; /** - * @author baigod + * @author 九筒 */ @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("PileSessionCache") diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/config/DownlinkRestTemplateConfiguration.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/config/DownlinkRestTemplateConfiguration.java index 1d35c3f..020cf04 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/config/DownlinkRestTemplateConfiguration.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/config/DownlinkRestTemplateConfiguration.java @@ -17,7 +17,7 @@ import java.time.temporal.ChronoUnit; import java.util.Collections; /** - * @author baigod + * @author 九筒 */ @Configuration public class DownlinkRestTemplateConfiguration { diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/grpc/DownlinkGrpcClient.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/grpc/DownlinkGrpcClient.java index 4e816d5..c8f321e 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/grpc/DownlinkGrpcClient.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/grpc/DownlinkGrpcClient.java @@ -16,6 +16,8 @@ import io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoopGroup; import io.grpc.netty.shaded.io.netty.channel.socket.nio.NioSocketChannel; import io.grpc.stub.StreamObserver; import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -28,8 +30,6 @@ import sanbing.jcpp.proto.gen.ProtocolInterfaceGrpc; import sanbing.jcpp.proto.gen.ProtocolInterfaceGrpc.ProtocolInterfaceStub; import sanbing.jcpp.proto.gen.ProtocolProto.*; -import javax.annotation.PreDestroy; -import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -41,7 +41,7 @@ import java.util.concurrent.locks.ReentrantLock; import static sanbing.jcpp.infrastructure.proto.ProtoConverter.toTracerProto; /** - * @author baigod + * @author 九筒 */ @Component @Slf4j @@ -109,7 +109,7 @@ public class DownlinkGrpcClient { msgHandleExecutorMap.computeIfAbsent(key, hostAndPort -> Executors.newFixedThreadPool(1, JCPPThreadFactory.forName("downlink-handle-threads-" + hostAndPort))) .execute(new TracerRunnable(() -> { - while (Boolean.TRUE.equals(initializedMap.computeIfAbsent(key, k -> Boolean.FALSE))) { + while (initializedMap.computeIfAbsent(key, k -> Boolean.FALSE)) { try { handleMsgs(key, queue); } catch (Exception e) { diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/CachedAttributeService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/CachedAttributeService.java new file mode 100644 index 0000000..63513e8 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/CachedAttributeService.java @@ -0,0 +1,232 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.impl; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.dal.repository.attribute.AttributeRepository; +import sanbing.jcpp.app.dal.repository.impl.RepositoryExecutorService; +import sanbing.jcpp.app.data.kv.AttributeKvEntry; +import sanbing.jcpp.app.data.kv.AttributesSaveResult; +import sanbing.jcpp.app.data.kv.BaseAttributeKvEntry; +import sanbing.jcpp.app.service.AttributeService; +import sanbing.jcpp.app.service.cache.CacheExecutorService; +import sanbing.jcpp.app.service.cache.attribute.AttributeCacheKey; +import sanbing.jcpp.infrastructure.cache.CacheValueWrapper; +import sanbing.jcpp.infrastructure.cache.VersionedCache; +import sanbing.jcpp.infrastructure.stats.DefaultCounter; +import sanbing.jcpp.infrastructure.stats.StatsFactory; +import sanbing.jcpp.infrastructure.util.JCPPPair; +import sanbing.jcpp.infrastructure.util.validation.Validator; + +import java.util.*; + +import static sanbing.jcpp.app.dal.repository.attribute.KvValidator.validate; +import static sanbing.jcpp.app.dal.repository.attribute.KvValidator.validateId; + + +@Service +@Primary +@Slf4j +public class CachedAttributeService implements AttributeService { + private static final String STATS_NAME = "attributes.cache"; + public static final String LOCAL_CACHE_TYPE = "caffeine"; + + private final AttributeRepository attributeRepository; + private final RepositoryExecutorService repositoryExecutorService; + private final CacheExecutorService cacheExecutorService; + private final DefaultCounter hitCounter; + private final DefaultCounter missCounter; + private final VersionedCache cache; + private ListeningExecutorService cacheExecutor; + + @Value("${cache.type:caffeine}") + private String cacheType; + @Value("${sql.attributes.value_no_xss_validation:false}") + private boolean valueNoXssValidation; + + public CachedAttributeService(AttributeRepository attributeRepository, + RepositoryExecutorService repositoryExecutorService, + StatsFactory statsFactory, + CacheExecutorService cacheExecutorService, + VersionedCache cache) { + this.attributeRepository = attributeRepository; + this.repositoryExecutorService = repositoryExecutorService; + this.cacheExecutorService = cacheExecutorService; + this.cache = cache; + + this.hitCounter = statsFactory.createDefaultCounter(STATS_NAME, "result", "hit"); + this.missCounter = statsFactory.createDefaultCounter(STATS_NAME, "result", "miss"); + } + + @PostConstruct + public void init() { + this.cacheExecutor = getExecutor(cacheType, cacheExecutorService); + } + + /** + * Will return: + * - for the local cache type (cache.type="coffeine"): directExecutor (run callback immediately in the same thread) + * - for the remote cache: dedicated thread pool for the cache IO calls to unblock any caller thread + */ + ListeningExecutorService getExecutor(String cacheType, CacheExecutorService cacheExecutorService) { + if (StringUtils.isEmpty(cacheType) || LOCAL_CACHE_TYPE.equals(cacheType)) { + log.info("Going to use directExecutor for the local cache type {}", cacheType); + return MoreExecutors.newDirectExecutorService(); + } + log.info("Going to use cacheExecutorService for the remote cache type {}", cacheType); + return cacheExecutorService.executor(); + } + + @Override + public ListenableFuture> find(UUID entityId, String attrKey) { + validateId(entityId); + Validator.validateString(attrKey, k -> "Incorrect attribute key " + k); + + return cacheExecutor.submit(() -> { + AttributeCacheKey attributeCacheKey = new AttributeCacheKey( entityId, attrKey); + CacheValueWrapper cachedAttributeValue = cache.get(attributeCacheKey); + if (cachedAttributeValue != null) { + hitCounter.increment(); + AttributeKvEntry cachedAttributeKvEntry = cachedAttributeValue.get(); + return Optional.ofNullable(cachedAttributeKvEntry); + } else { + missCounter.increment(); + Optional result = attributeRepository.find(entityId, attrKey); + cache.put(attributeCacheKey, result.orElse(null)); + return result; + } + }); + } + + @Override + public ListenableFuture> find(UUID entityId, final Collection attrKeys) { + validateId(entityId); + final var attrKeySet = new LinkedHashSet<>(attrKeys); // deduplicate the attributes + attrKeySet.forEach(attrKey -> Validator.validateString(attrKey, k -> "Incorrect attribute key " + k)); + + //CacheExecutor for Redis or DirectExecutor for local Caffeine + return Futures.transformAsync(cacheExecutor.submit(() -> findCachedAttributes(entityId, attrKeySet)), + wrappedCachedAttributes -> { + + List cachedAttributes = wrappedCachedAttributes.values().stream() + .map(CacheValueWrapper::get) + .filter(Objects::nonNull) + .toList(); + if (wrappedCachedAttributes.size() == attrKeySet.size()) { + log.trace("[{}] Found all attributes from cache: {}", entityId, attrKeySet); + return Futures.immediateFuture(cachedAttributes); + } + + Set notFoundAttrKeys = new HashSet<>(attrKeySet); + notFoundAttrKeys.removeAll(wrappedCachedAttributes.keySet()); + + // DB call should run in DB executor, not in cache-related executor + return repositoryExecutorService.submit(() -> { + log.trace("[{}] Lookup attributes from db: {}", entityId, notFoundAttrKeys); + List result = attributeRepository.find(entityId, notFoundAttrKeys); + for (AttributeKvEntry foundInDbAttribute : result) { + put(entityId, foundInDbAttribute); + notFoundAttrKeys.remove(foundInDbAttribute.getKey()); + } + for (String key : notFoundAttrKeys) { + cache.put(new AttributeCacheKey(entityId, key), null); + } + List mergedAttributes = new ArrayList<>(cachedAttributes); + mergedAttributes.addAll(result); + log.trace("[{}] Commit cache transaction: {}", entityId, notFoundAttrKeys); + return mergedAttributes; + }); + + }, MoreExecutors.directExecutor()); // cacheExecutor analyse and returns results or submit to DB executor + } + + private Map> findCachedAttributes(UUID entityId, Collection attrKeys) { + Map> cachedAttributes = new HashMap<>(); + for (String attrKey : attrKeys) { + var cachedAttributeValue = cache.get(new AttributeCacheKey( entityId, attrKey)); + if (cachedAttributeValue != null) { + hitCounter.increment(); + cachedAttributes.put(attrKey, cachedAttributeValue); + } else { + missCounter.increment(); + } + } + return cachedAttributes; + } + + @Override + public ListenableFuture> findAll(UUID entityId) { + validateId(entityId); + // We can`t watch on cache because the keys are unknown. + return repositoryExecutorService.submit(() -> attributeRepository.findAll( entityId)); + } + + @Override + public int removeAllByEntityId(UUID entityId) { + List result = attributeRepository.removeAllByEntityId(entityId); + result.forEach(key -> { + cache.evict(new AttributeCacheKey(entityId, key)); + }); + return result.size(); + } + + @Override + public ListenableFuture save(UUID entityId, AttributeKvEntry attribute) { + validateId(entityId); + validate(attribute, valueNoXssValidation); + return doSave(entityId, List.of(attribute)); + } + + @Override + public ListenableFuture save(UUID entityId, List attributes) { + validateId(entityId); + validate(attributes, valueNoXssValidation); + return doSave( entityId, attributes); + } + + private ListenableFuture doSave(UUID entityId, List attributes) { + List> futures = new ArrayList<>(attributes.size()); + for (var attribute : attributes) { + ListenableFuture future = Futures.transform(attributeRepository.save(entityId, attribute), version -> { + BaseAttributeKvEntry attributeKvEntry = new BaseAttributeKvEntry(((BaseAttributeKvEntry) attribute).getKv(), attribute.getLastUpdateTs(), version); + put(entityId, attributeKvEntry); + return version; + }, cacheExecutor); + futures.add(future); + } + return Futures.transform(Futures.allAsList(futures), AttributesSaveResult::of, MoreExecutors.directExecutor()); + } + + private void put(UUID entityId, AttributeKvEntry attribute) { + String key = attribute.getKey(); + log.trace("[{}][{}] Before cache put: {}", entityId, key, attribute); + cache.put(new AttributeCacheKey( entityId, key), attribute); + log.trace("[{}][{}] after cache put.", entityId, key); + } + + @Override + public ListenableFuture> removeAll(UUID entityId, List attrKeys) { + validateId(entityId); + List>> futures = attributeRepository.removeAllWithVersions( entityId, attrKeys); + return Futures.allAsList(futures.stream().map(future -> Futures.transform(future, keyVersionPair -> { + String key = keyVersionPair.getFirst(); + Integer version = keyVersionPair.getSecond(); + cache.evict(new AttributeCacheKey( entityId, key), version); + return key; + }, cacheExecutor)).toList()); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultDashboardService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultDashboardService.java new file mode 100644 index 0000000..fe7b911 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultDashboardService.java @@ -0,0 +1,115 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.adapter.response.DashboardStats; +import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; +import sanbing.jcpp.app.dal.mapper.GunMapper; +import sanbing.jcpp.app.dal.mapper.PileMapper; +import sanbing.jcpp.app.dal.mapper.StationMapper; +import sanbing.jcpp.app.data.kv.AttrKeyEnum; +import sanbing.jcpp.app.service.DashboardService; + + +/** + * 仪表盘服务实现 + * + * @author 九筒 + */ +@Service +@RequiredArgsConstructor +public class DefaultDashboardService implements DashboardService { + + private final StationMapper stationMapper; + private final PileMapper pileMapper; + private final GunMapper gunMapper; + + @Override + public DashboardStats getDashboardStats() { + // 获取总览统计 + DashboardStats.Overview overview = buildOverview(); + + // 获取充电桩状态分布 + DashboardStats.PileStatusDistribution pileStatusDistribution = buildPileStatusDistribution(); + + // 获取充电枪状态分布 + DashboardStats.GunStatusDistribution gunStatusDistribution = buildGunStatusDistribution(); + + return DashboardStats.builder() + .overview(overview) + .pileStatusDistribution(pileStatusDistribution) + .gunStatusDistribution(gunStatusDistribution) + .build(); + } + + private DashboardStats.Overview buildOverview() { + // 统计充电站数量 + Long totalStations = stationMapper.selectCount(null); + + // 统计充电桩数量 + Long totalPiles = pileMapper.selectCount(null); + + // 统计充电枪数量 + Long totalGuns = gunMapper.selectCount(null); + + return DashboardStats.Overview.builder() + .totalStations(totalStations) + .totalPiles(totalPiles) + .totalGuns(totalGuns) + .build(); + } + + private DashboardStats.PileStatusDistribution buildPileStatusDistribution() { + // 统计充电桩总数量 + Long totalPiles = pileMapper.selectCount(null); + + // 从数据库查询真实的在线/离线状态分布,使用枚举值作为参数 + String statusKey = AttrKeyEnum.STATUS.getCode(); + Long onlinePiles = pileMapper.countOnlinePiles(statusKey, PileStatusEnum.ONLINE.getValue()); + Long offlinePiles = pileMapper.countOfflinePiles(statusKey, PileStatusEnum.OFFLINE.getValue()); + + return DashboardStats.PileStatusDistribution.builder() + .totalPiles(totalPiles) + .onlinePiles(onlinePiles) + .offlinePiles(offlinePiles) + .build(); + } + + private DashboardStats.GunStatusDistribution buildGunStatusDistribution() { + // 统计充电枪总数量 + Long totalGuns = gunMapper.selectCount(null); + + // 从数据库查询真实的运行状态分布,按照GunRunStatusEnum枚举统计,使用枚举值作为参数 + String statusKey = AttrKeyEnum.GUN_RUN_STATUS.getCode(); + Long idleGuns = gunMapper.countIdleGuns(statusKey, GunRunStatusEnum.IDLE.getValue()); + Long insertedGuns = gunMapper.countInsertedGuns(statusKey, GunRunStatusEnum.INSERTED.getValue()); + Long chargingGuns = gunMapper.countChargingGuns(statusKey, GunRunStatusEnum.CHARGING.getValue()); + Long chargeCompleteGuns = gunMapper.countChargeCompleteGuns(statusKey, GunRunStatusEnum.CHARGE_COMPLETE.getValue()); + Long dischargeReadyGuns = gunMapper.countDischargeReadyGuns(statusKey, GunRunStatusEnum.DISCHARGE_READY.getValue()); + Long dischargingGuns = gunMapper.countDischargingGuns(statusKey, GunRunStatusEnum.DISCHARGING.getValue()); + Long dischargeCompleteGuns = gunMapper.countDischargeCompleteGuns(statusKey, GunRunStatusEnum.DISCHARGE_COMPLETE.getValue()); + Long reservedGuns = gunMapper.countReservedGuns(statusKey, GunRunStatusEnum.RESERVED.getValue()); + Long faultGuns = gunMapper.countFaultGuns(statusKey, GunRunStatusEnum.FAULT.getValue()); + + return DashboardStats.GunStatusDistribution.builder() + .totalGuns(totalGuns) + .idleGuns(idleGuns) + .insertedGuns(insertedGuns) + .chargingGuns(chargingGuns) + .chargeCompleteGuns(chargeCompleteGuns) + .dischargeReadyGuns(dischargeReadyGuns) + .dischargingGuns(dischargingGuns) + .dischargeCompleteGuns(dischargeCompleteGuns) + .reservedGuns(reservedGuns) + .faultGuns(faultGuns) + .build(); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultGunService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultGunService.java new file mode 100644 index 0000000..b2fd7c8 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultGunService.java @@ -0,0 +1,234 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.adapter.request.GunCreateRequest; +import sanbing.jcpp.app.adapter.request.GunQueryRequest; +import sanbing.jcpp.app.adapter.request.GunUpdateRequest; +import sanbing.jcpp.app.adapter.response.GunWithStatusResponse; +import sanbing.jcpp.app.adapter.response.PageResponse; +import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum; +import sanbing.jcpp.app.dal.entity.Gun; +import sanbing.jcpp.app.dal.mapper.GunMapper; +import sanbing.jcpp.app.dal.repository.GunRepository; +import sanbing.jcpp.app.data.kv.AttrKeyEnum; +import sanbing.jcpp.app.data.kv.AttributeKvEntry; +import sanbing.jcpp.app.data.kv.BaseAttributeKvEntry; +import sanbing.jcpp.app.data.kv.StringDataEntry; +import sanbing.jcpp.app.service.AttributeService; +import sanbing.jcpp.app.service.GunService; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; +import sanbing.jcpp.proto.gen.ProtocolProto.GunRunStatus; + +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Slf4j +public class DefaultGunService implements GunService { + + private final GunMapper gunMapper; + private final GunRepository gunRepository; + private final AttributeService attributeService; + + @Override + public Gun createGun(GunCreateRequest request) { + // 检查充电枪编码是否已存在 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Gun::getGunCode, request.getGunCode()); + if (gunMapper.selectCount(wrapper) > 0) { + throw new RuntimeException("充电枪编码已存在"); + } + + Gun gun = Gun.builder() + .id(UUID.randomUUID()) + .createdTime(LocalDateTime.now()) + .gunName(request.getGunName()) + .gunNo(request.getGunNo()) + .gunCode(request.getGunCode()) + .stationId(request.getStationId()) + .pileId(request.getPileId()) + .additionalInfo(JacksonUtil.newObjectNode()) + .version(0) + .build(); + + gunMapper.insert(gun); + return gun; + } + + @Override + public Gun findById(UUID id) { + return gunRepository.findById(id); + } + + @Override + public Gun updateGun(UUID id, GunUpdateRequest request) { + Gun existingGun = findById(id); + if (existingGun == null) { + throw new RuntimeException("充电枪不存在,更新失败"); + } + + Gun updatedGun = Gun.builder() + .id(existingGun.getId()) + .createdTime(existingGun.getCreatedTime()) + .updatedTime(LocalDateTime.now()) // 更新时设置更新时间 + .gunName(request.getGunName()) + .gunNo(existingGun.getGunNo()) // 编号不允许修改 + .gunCode(existingGun.getGunCode()) // 编码不允许修改 + .stationId(existingGun.getStationId()) // 所属充电站不允许修改 + .pileId(existingGun.getPileId()) // 所属充电桩不允许修改 + .additionalInfo(existingGun.getAdditionalInfo()) + .version(existingGun.getVersion()) + .build(); + + gunMapper.updateById(updatedGun); + return updatedGun; + } + + @Override + public void deleteGun(UUID id) { + Gun gun = findById(id); + if (gun == null) { + throw new RuntimeException("充电枪不存在,删除失败"); + } + + int affectedRows = gunMapper.deleteById(id); + if (affectedRows == 0) { + throw new RuntimeException("删除充电枪失败,可能已被其他操作删除"); + } + } + @Override + public PageResponse queryGunsWithStatus(GunQueryRequest request) { + Page page = new Page<>(request.getPage(), request.getSize()); + + // 使用MyBatis XML配置查询,避免魔法值错误 + IPage result = gunMapper.selectGunWithStatusPage(page, request); + + return PageResponse.builder() + .records(result.getRecords()) + .total(result.getTotal()) + .totalPages((int) result.getPages()) + .page(request.getPage()) + .size(request.getSize()) + .build(); + } + + @Override + public Gun findByPileCodeAndGunCode(String pileCode, String gunCode) { + return gunRepository.findByPileCodeAndGunCode(pileCode, gunCode); + } + + @Override + public String findGunStatus(UUID gunId) { + ListenableFuture> attribute = attributeService.find(gunId, AttrKeyEnum.STATUS.getCode()); + + try { + Optional result = attribute.get(); + if (result.isPresent()) { + AttributeKvEntry entry = result.get(); + Optional strValue = entry.getStrValue(); + return strValue.orElse(null); + } + return null; + } catch (Exception e) { + log.error("获取充枪状态失败: gunId={}", gunId, e); + return null; + } + } + + @Override + public void saveGunStatusChange(UUID gunId, String status, Long ts) { + try { + long currentTime = ts != null ? ts : System.currentTimeMillis(); + AttributeKvEntry gunStatusAttr = new BaseAttributeKvEntry( + new StringDataEntry(AttrKeyEnum.GUN_RUN_STATUS.getCode(), status), + currentTime + ); + + attributeService.save(gunId, gunStatusAttr); + + log.info("充电枪状态已保存: gunId={}, status={}, ts={}", gunId, status, ts); + } catch (Exception e) { + log.error("保存充电枪状态失败: gunId={}, status={}", gunId, status, e); + } + } + + @Override + public boolean handleGunRunStatus(String pileCode, String gunCode, GunRunStatus protoStatus, long ts) { + log.info("处理充电枪状态上报: 桩编码={}, 枪编码={}, 状态={}", pileCode, gunCode, protoStatus); + + // 将Proto状态转换为数据库枚举 + GunRunStatusEnum dbStatus = convertProtoStatusToDbStatus(protoStatus); + + if (dbStatus != null) { + // 获取充电枪信息(使用缓存) + Gun gun = findByPileCodeAndGunCode(pileCode, gunCode); + if (gun != null) { + // 检查状态是否真的发生了变化,避免重复保存 + String currentStatus = findGunStatus(gun.getId()); + if (dbStatus.name().equals(currentStatus)) { + log.debug("充电枪状态未发生变化,跳过更新: 桩编码={}, 枪编码={}, 状态={}", pileCode, gunCode, dbStatus); + return false; + } + + // 保存充电枪状态到属性表 + saveGunStatusChange(gun.getId(), dbStatus.name(), ts); + + log.info("充电枪状态更新成功: 桩编码={}, 枪编码={}, 原状态={}, 新状态={}", + pileCode, gunCode, currentStatus, dbStatus); + + // 根据充电枪状态判断是否需要更新充电桩状态 + return shouldUpdatePileStatus(dbStatus); + } else { + log.warn("未找到充电枪: 桩编码={}, 枪编码={}", pileCode, gunCode); + } + } else { + log.warn("未知的充电枪状态: {}, 跳过更新", protoStatus); + } + + return false; + } + + /** + * 将Proto状态转换为数据库枚举状态 + */ + private GunRunStatusEnum convertProtoStatusToDbStatus(GunRunStatus protoStatus) { + return switch (protoStatus) { + case IDLE -> GunRunStatusEnum.IDLE; + case INSERTED -> GunRunStatusEnum.INSERTED; + case CHARGING -> GunRunStatusEnum.CHARGING; + case CHARGE_COMPLETE -> GunRunStatusEnum.CHARGE_COMPLETE; + case DISCHARGE_READY -> GunRunStatusEnum.DISCHARGE_READY; + case DISCHARGING -> GunRunStatusEnum.DISCHARGING; + case DISCHARGE_COMPLETE -> GunRunStatusEnum.DISCHARGE_COMPLETE; + case RESERVED -> GunRunStatusEnum.RESERVED; + case FAULT -> GunRunStatusEnum.FAULT; + default -> null; // 未知状态不更新 + }; + } + + /** + * 根据充电枪状态判断是否需要更新充电桩状态 + */ + private boolean shouldUpdatePileStatus(GunRunStatusEnum gunStatus) { + return switch (gunStatus) { + case CHARGING, DISCHARGING -> true; // 充电或放电时可能需要更新桩状态为在线 + case FAULT -> true; // 故障时可能需要更新桩状态 + default -> false; // 其他状态不需要更新桩状态 + }; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java index 03495c4..0ace369 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileProtocolService.java @@ -8,21 +8,23 @@ package sanbing.jcpp.app.service.impl; import cn.hutool.core.date.DateUtil; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.ListenableFuture; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import sanbing.jcpp.app.dal.config.ibatis.enums.GunRunStatusEnum; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; +import sanbing.jcpp.app.dal.entity.Gun; import sanbing.jcpp.app.dal.entity.Pile; -import sanbing.jcpp.app.data.PileSession; -import sanbing.jcpp.app.repository.PileRepository; -import sanbing.jcpp.app.service.DownlinkCallService; -import sanbing.jcpp.app.service.PileProtocolService; -import sanbing.jcpp.app.service.cache.session.PileSessionCacheKey; -import sanbing.jcpp.infrastructure.cache.TransactionalCache; +import sanbing.jcpp.app.dal.repository.PileRepository; +import sanbing.jcpp.app.data.kv.*; +import sanbing.jcpp.app.service.*; import sanbing.jcpp.infrastructure.proto.ProtoConverter; import sanbing.jcpp.infrastructure.proto.model.PricingModel; import sanbing.jcpp.infrastructure.proto.model.PricingModel.FlagPrice; import sanbing.jcpp.infrastructure.proto.model.PricingModel.Period; import sanbing.jcpp.infrastructure.queue.Callback; +import sanbing.jcpp.infrastructure.util.async.JCPPAsynchron; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; import sanbing.jcpp.proto.gen.ProtocolProto.*; import sanbing.jcpp.protocol.domain.DownlinkCmdEnum; @@ -33,11 +35,11 @@ import java.time.LocalTime; import java.util.*; import static sanbing.jcpp.proto.gen.ProtocolProto.PricingModelFlag.*; -import static sanbing.jcpp.proto.gen.ProtocolProto.PricingModelRule.SPLIT_TIME; +import static sanbing.jcpp.proto.gen.ProtocolProto.PricingModelRule.PEAK_VALLEY_PRICING; import static sanbing.jcpp.proto.gen.ProtocolProto.PricingModelType.CHARGE; /** - * @author baigod + * @author 九筒 */ @Service @Slf4j @@ -47,59 +49,125 @@ public class DefaultPileProtocolService implements PileProtocolService { PileRepository pileRepository; @Resource - TransactionalCache pileSessionCache; + DownlinkCallService downlinkCallService; @Resource - DownlinkCallService downlinkCallService; + GunService gunService; + + @Resource + PileService pileService; + + @Resource + AttributeService attributeService; + + @Resource + PileSessionService pileSessionService; @Override public void pileLogin(UplinkQueueMessage uplinkQueueMessage, Callback callback) { log.debug("接收到桩登录事件 {}", uplinkQueueMessage); LoginRequest loginRequest = uplinkQueueMessage.getLoginRequest(); - - Pile pile = pileRepository.findPileByCode(loginRequest.getPileCode()); - String pileCode = loginRequest.getPileCode(); - log.debug("查询到充电桩信息 {}", pile); + Pile pile = pileRepository.findPileByCode(pileCode); + log.debug("查询到充电桩信息: pileCode={}, exists={}", pileCode, pile != null); // 构造下行回复 - DownlinkRequestMessage.Builder downlinkMessageBuilder = createDownlinkMessageBuilder(uplinkQueueMessage, loginRequest.getPileCode()); + DownlinkRequestMessage.Builder downlinkMessageBuilder = createDownlinkMessageBuilder( + uplinkQueueMessage, pileCode); downlinkMessageBuilder.setDownlinkCmd(DownlinkCmdEnum.LOGIN_ACK.name()); - if (pile != null) { + // 处理登录成功的情况 + handleSuccessfulLogin(uplinkQueueMessage, loginRequest, pile, downlinkMessageBuilder, pileCode); + } else { + // 处理登录失败的情况(充电桩不存在) + handleFailedLogin(uplinkQueueMessage, loginRequest, downlinkMessageBuilder, pileCode); + } - PileSession pileSession = createSession(uplinkQueueMessage, pile, + callback.onSuccess(); + } + + /** + * 处理登录成功的情况 + */ + private void handleSuccessfulLogin(UplinkQueueMessage uplinkQueueMessage, + LoginRequest loginRequest, + Pile pile, + DownlinkRequestMessage.Builder downlinkMessageBuilder, + String pileCode) { + try { + // 1. 保存pileSession + pileSessionService.createOrUpdateSession( + uplinkQueueMessage, pile, loginRequest.getRemoteAddress(), loginRequest.getNodeId(), loginRequest.getNodeHostAddress(), loginRequest.getNodeRestPort(), loginRequest.getNodeGrpcPort()); - // 保存到缓存 - pileSessionCache.put(new PileSessionCacheKey(pile.getPileCode()), pileSession); + // 2. 处理登录状态管理(异步) + ListenableFuture future = pileService.handlePileLogin(pile.getId()); - downlinkMessageBuilder.setLoginResponse(LoginResponse.newBuilder() - .setSuccess(true) - .setPileCode(loginRequest.getPileCode()) - .build()); + // 3. 异步处理完成后发送应答 + JCPPAsynchron.withCallback(future, + result -> { + sendLoginSuccessResponse(downlinkMessageBuilder, loginRequest, pileCode); + log.info("充电桩登录成功,状态更新完成: pileCode={}", pileCode); + }, + throwable -> { + log.error("充电桩登录状态更新失败: pileCode={}", pileCode, throwable); + sendLoginFailureResponse(downlinkMessageBuilder, uplinkQueueMessage, loginRequest, pileCode); + } + ); - downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, pileCode); - } else { - - downlinkMessageBuilder.setLoginResponse(LoginResponse.newBuilder() - .setSuccess(false) - .setPileCode(loginRequest.getPileCode()) - .build()); - - - downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, uplinkQueueMessage, loginRequest); + } catch (Exception e) { + log.error("处理充电桩登录时发生异常: pileCode={}", pileCode, e); + sendLoginFailureResponse(downlinkMessageBuilder, uplinkQueueMessage, loginRequest, pileCode); } + } + /** + * 处理登录失败的情况 + */ + private void handleFailedLogin(UplinkQueueMessage uplinkQueueMessage, + LoginRequest loginRequest, + DownlinkRequestMessage.Builder downlinkMessageBuilder, + String pileCode) { + log.warn("充电桩登录失败,充电桩不存在: pileCode={}", pileCode); + sendLoginFailureResponse(downlinkMessageBuilder, uplinkQueueMessage, loginRequest, pileCode); + } - callback.onSuccess(); + /** + * 发送登录成功应答 + */ + private void sendLoginSuccessResponse(DownlinkRequestMessage.Builder downlinkMessageBuilder, + LoginRequest loginRequest, + String pileCode) { + downlinkMessageBuilder.setLoginResponse(LoginResponse.newBuilder() + .setSuccess(true) + .setPileCode(loginRequest.getPileCode()) + .build()); + + log.info("业务[充电桩登录成功应答] 发送下行消息到充电桩: {}", pileCode); + downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, pileCode); + } + + /** + * 发送登录失败应答 + */ + private void sendLoginFailureResponse(DownlinkRequestMessage.Builder downlinkMessageBuilder, + UplinkQueueMessage uplinkQueueMessage, + LoginRequest loginRequest, + String pileCode) { + downlinkMessageBuilder.setLoginResponse(LoginResponse.newBuilder() + .setSuccess(false) + .setPileCode(loginRequest.getPileCode()) + .build()); + + log.info("业务[充电桩登录失败应答] 发送下行消息到充电桩: {}", pileCode); + downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, uplinkQueueMessage, loginRequest); } @Override @@ -111,34 +179,54 @@ public class DefaultPileProtocolService implements PileProtocolService { Pile pile = pileRepository.findPileByCode(heartBeatRequest.getPileCode()); if (pile != null) { - // 重新保存到缓存 - createSession(uplinkQueueMessage, pile, - heartBeatRequest.getRemoteAddress(), - heartBeatRequest.getNodeId(), - heartBeatRequest.getNodeHostAddress(), - heartBeatRequest.getNodeRestPort(), - heartBeatRequest.getNodeGrpcPort()); + // 1. 处理心跳状态管理(异步) + ListenableFuture future = pileService.handlePileHeartbeat(pile.getId()); + + // 2. 异步处理完成后保存pileSession + JCPPAsynchron.withCallback(future, + result -> { + // 保存pileSession + pileSessionService.createOrUpdateSession( + uplinkQueueMessage, pile, + heartBeatRequest.getRemoteAddress(), + heartBeatRequest.getNodeId(), + heartBeatRequest.getNodeHostAddress(), + heartBeatRequest.getNodeRestPort(), + heartBeatRequest.getNodeGrpcPort()); + + log.debug("充电桩心跳处理完成,状态更新成功: pileCode={}", heartBeatRequest.getPileCode()); + }, + throwable -> { + log.error("充电桩心跳状态更新失败: pileCode={}", heartBeatRequest.getPileCode(), throwable); + } + ); } callback.onSuccess(); } - private PileSession createSession(UplinkQueueMessage uplinkQueueMessage, - Pile pile, - String remoteAddress, - String nodeId, - String nodeIp, - int restPort, - int grpcPort) { - PileSession pileSession = new PileSession(pile.getId(), pile.getPileCode(), uplinkQueueMessage.getProtocolName()); - pileSession.setProtocolSessionId(new UUID(uplinkQueueMessage.getSessionIdMSB(), uplinkQueueMessage.getSessionIdLSB())); - pileSession.setRemoteAddress(remoteAddress); - pileSession.setNodeId(nodeId); - pileSession.setNodeIp(nodeIp); - pileSession.setNodeRestPort(restPort); - pileSession.setNodeGrpcPort(grpcPort); + @Override + public void onSessionCloseEvent(UplinkQueueMessage uplinkQueueMessage, Callback callback) { + log.info("接收到会话关闭事件 {}", uplinkQueueMessage); - return pileSession; + SessionCloseEventProto sessionCloseEvent = uplinkQueueMessage.getSessionCloseEventProto(); + String pileCode = sessionCloseEvent.getPileCode(); + + try { + // 通过 PileService 处理会话关闭状态管理 + pileService.handlePileSessionClose(pileCode); + + // 使用PileSessionService清除会话缓存 + pileSessionService.removeSession(pileCode); + + log.info("会话关闭事件处理完成: 桩编码={}, 关闭原因={}, 状态已更新为OFFLINE", + pileCode, sessionCloseEvent.getReason()); + + } catch (Exception e) { + log.error("处理会话关闭事件失败: 桩编码={}", pileCode, e); + } + + callback.onSuccess(); } @Override @@ -159,6 +247,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setPricingId(pricingId) .build()); + log.info("业务[计费模型验证应答] 发送下行消息到充电桩: {}, 计费ID: {}", pileCode, pricingId); downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, pileCode); callback.onSuccess(); @@ -190,7 +279,7 @@ public class DefaultPileProtocolService implements PileProtocolService { model.setSequenceNumber(1); model.setPileCode(pileCode); model.setType(CHARGE); - model.setRule(SPLIT_TIME); + model.setRule(PEAK_VALLEY_PRICING); model.setStandardElec(new BigDecimal("0.75")); model.setStandardServ(new BigDecimal("0.45")); model.setFlagPriceList(flagPriceMap); @@ -205,6 +294,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setPricingModel(ProtoConverter.toPricingModel(model)) .build()); + log.info("业务[计费模型查询应答] 发送下行消息到充电桩: {}", pileCode); downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, pileCode); callback.onSuccess(); @@ -212,9 +302,32 @@ public class DefaultPileProtocolService implements PileProtocolService { @Override public void postGunRunStatus(UplinkQueueMessage uplinkQueueMessage, Callback callback) { - log.info("接收到充电桩上报的电桩状态 {}", uplinkQueueMessage); + log.info("接收到充电桩上报的充电枪状态 {}", uplinkQueueMessage); - // TODO 处理相关业务逻辑 + try { + GunRunStatusProto gunRunStatusProto = uplinkQueueMessage.getGunRunStatusProto(); + String pileCode = gunRunStatusProto.getPileCode(); + String gunCode = gunRunStatusProto.getGunCode(); + long ts = uplinkQueueMessage.getTs(); + GunRunStatus protoStatus = gunRunStatusProto.getGunRunStatus(); + + // 委托给 GunService 处理充电枪状态逻辑 + boolean needUpdatePileStatus = gunService.handleGunRunStatus(pileCode, gunCode, protoStatus, ts); + + // 如果需要,根据充电枪状态更新充电桩状态 + if (needUpdatePileStatus) { + // 转换Proto状态为枚举状态来更新桩状态 + GunRunStatusEnum dbStatus = convertProtoStatusToDbStatus(protoStatus); + if (dbStatus != null) { + updatePileStatusBasedOnGunStatus(pileCode, dbStatus); + } + } + + } catch (Exception e) { + log.error("处理充电枪状态上报失败", e); + callback.onFailure(e); + return; + } callback.onSuccess(); } @@ -275,6 +388,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setAdditionalInfo(additionalInfo.toString()) .build()); + log.info("业务[交易记录上报应答] 发送下行消息到充电桩: {}", pileCode); downlinkCallService.sendDownlinkMessage(downlinkMessageBuilder, pileCode); callback.onSuccess(); @@ -318,6 +432,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setDownlinkCmd(downlinkCmd.name()) .setRemoteStartChargingRequest(requestBuilder.build()); + log.info("业务[远程启动充电] 发送下行消息到充电桩: {}, 充电枪: {}, 订单号: {}", pileCode, gunCode, orderNo); downlinkCallService.sendDownlinkMessage(downlinkRequestMessageBuilder, pileCode); } @@ -339,6 +454,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setGunCode(gunCode) .build()); + log.info("业务[远程停止充电] 发送下行消息到充电桩: {}, 充电枪: {}", pileCode, gunCode); downlinkCallService.sendDownlinkMessage(downlinkRequestMessageBuilder, pileCode); } @@ -361,6 +477,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setType(type) .build()); + log.info("业务[重启充电桩] 发送下行消息到充电桩: {}, 重启类型: {}", pileCode, type); downlinkCallService.sendDownlinkMessage(downlinkRequestMessageBuilder, pileCode); } @@ -378,6 +495,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setDownlinkCmd(DownlinkCmdEnum.SET_PRICING.name()) .setSetPricingRequest(setPricingRequest); + log.info("业务[设置计费模型] 发送下行消息到充电桩: {}", pileCode); downlinkCallService.sendDownlinkMessage(downlinkRequestMessageBuilder, pileCode); } @@ -437,6 +555,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setRequestIdLSB(requestId.getLeastSignificantBits()) .setDownlinkCmd(DownlinkCmdEnum.OTA_REQUEST.name()) .setOtaRequest(request); + log.info("业务[OTA升级请求] 发送下行消息到充电桩: {}, 文件路径: {}", request.getPileCode(), request.getFilePath()); downlinkCallService.sendDownlinkMessage(downlinkRequestMessageBuilder,request.getPileCode()); } @@ -490,6 +609,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setPileCode(pileCode) .setTime(DateUtil.formatLocalDateTime(time)) .build()); + log.info("业务[时间同步] 发送下行消息到充电桩: {}, 同步时间: {}", pileCode, DateUtil.formatLocalDateTime(time)); downlinkCallService.sendDownlinkMessage(downlinkRequestMessageBuilder, pileCode); } @@ -519,8 +639,26 @@ public class DefaultPileProtocolService implements PileProtocolService { log.info("地锁状态信息: 桩编码: {}, 枪号: {}, 车位锁状态: {}, 车位状态: {}, 地锁电量: {}%, 报警状态: {}", pileCode, gunCode, lockStatus, parkStatus, lockBattery, alarmStatus); - // TODO 处理相关业务逻辑,比如保存地锁状态信息到数据库 + try { + // 获取时间戳 + long ts = uplinkQueueMessage.getTs(); + // 获取充电枪信息 + Gun gun = gunService.findByPileCodeAndGunCode(pileCode, gunCode); + if (gun != null) { + // 保存地锁状态到属性表 + saveLockStatusToAttributes(gun.getId(), lockStatus, parkStatus, lockBattery, alarmStatus, ts); + + log.info("地锁和车位状态已保存: 桩编码={}, 枪编码={}, 地锁状态={}, 车位状态={}", + pileCode, gunCode, lockStatus, parkStatus); + } else { + log.warn("未找到充电枪,无法保存地锁状态: 桩编码={}, 枪编码={}", pileCode, gunCode); + } + } catch (Exception e) { + log.error("保存地锁状态失败: 桩编码={}, 枪编码={}", pileCode, gunCode, e); + callback.onFailure(e); + return; + } callback.onSuccess(); } @@ -548,6 +686,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setRequestIdLSB(requestId.getLeastSignificantBits()) .setDownlinkCmd(DownlinkCmdEnum.OFFLINE_CARD_BALANCE_UPDATE_REQUEST.name()) .setOfflineCardBalanceUpdateRequest(request); + log.info("业务[离线卡余额更新] 发送下行消息到充电桩: {}, 卡号: {}", request.getPileCode(), request.getCardNo()); downlinkCallService.sendDownlinkMessage(downlinkRequestMessageBuilder,request.getPileCode()); } @@ -564,6 +703,7 @@ public class DefaultPileProtocolService implements PileProtocolService { .setRequestIdLSB(requestId.getLeastSignificantBits()) .setDownlinkCmd(DownlinkCmdEnum.OFFLINE_CARD_SYNC_REQUEST.name()) .setOfflineCardSyncRequest(request); + log.info("业务[离线卡同步] 发送下行消息到充电桩: {}", request.getPileCode()); downlinkCallService.sendDownlinkMessage(downlinkRequestMessageBuilder,request.getPileCode()); } @@ -600,7 +740,106 @@ public class DefaultPileProtocolService implements PileProtocolService { return builder; } + @Override + public void postBmsDemandChargerOutput(UplinkQueueMessage uplinkQueueMessage, Callback callback) { + log.info("接收到充电过程BMS需求与充电机输出信息:{}", uplinkQueueMessage); + BmsDemandChargerOutputProto bmsDemandChargerOutputProto = uplinkQueueMessage.getBmsDemandChargerOutputProto(); + String pileCode = bmsDemandChargerOutputProto.getPileCode(); + String gunCode = bmsDemandChargerOutputProto.getGunCode(); + String tradeNo = bmsDemandChargerOutputProto.getTradeNo(); + String additionalInfo = bmsDemandChargerOutputProto.getAdditionalInfo(); + log.info("充电过程BMS需求与充电机输出信息: 桩编码: {}, 枪号: {}, 交易流水号: {}, 附加信息: {}", + pileCode, gunCode, tradeNo, additionalInfo); + // TODO 处理相关业务逻辑 + callback.onSuccess(); + } + /** + * 将Proto状态转换为数据库枚举状态 + */ + private GunRunStatusEnum convertProtoStatusToDbStatus(GunRunStatus protoStatus) { + switch (protoStatus) { + case IDLE: + return GunRunStatusEnum.IDLE; + case INSERTED: + return GunRunStatusEnum.INSERTED; + case CHARGING: + return GunRunStatusEnum.CHARGING; + case CHARGE_COMPLETE: + return GunRunStatusEnum.CHARGE_COMPLETE; + case DISCHARGE_READY: + return GunRunStatusEnum.DISCHARGE_READY; + case DISCHARGING: + return GunRunStatusEnum.DISCHARGING; + case DISCHARGE_COMPLETE: + return GunRunStatusEnum.DISCHARGE_COMPLETE; + case RESERVED: + return GunRunStatusEnum.RESERVED; + case FAULT: + return GunRunStatusEnum.FAULT; + case UNKNOWN: + default: + return null; // 未知状态不更新 + } + } + /** + * 充电枪状态上报处理 + *

+ * 重构说明: + * - 充电桩状态简化为ONLINE/OFFLINE,不再受枪状态影响 + * - 充电枪的工作状态独立维护,不影响充电桩的在线状态 + * - 只要设备能正常通信,充电桩就保持ONLINE状态 + *

+ * 处理逻辑: + * 1. 枪状态变化不影响桩的在线状态 + * 2. 仅在设备能上报枪状态时,确保桩为ONLINE状态 + * 3. 枪的故障状态仅记录在枪级别,不影响桩状态 + */ + private void updatePileStatusBasedOnGunStatus(String pileCode, GunRunStatusEnum gunStatus) { + // 枪状态上报说明设备在线,确保充电桩状态为ONLINE + Pile pile = pileRepository.findPileByCode(pileCode); + if (pile != null) { + String currentStatusStr = pileService.findPileStatus(pile.getId()); + // 如果当前不是ONLINE状态,更新为ONLINE + if (!"ONLINE".equals(currentStatusStr)) { + pileService.updatePileStatusByCode(pileCode, PileStatusEnum.ONLINE); + log.info("枪状态上报,确保充电桩在线: 桩编码={}, 枪状态={}, 更新桩状态=ONLINE", + pileCode, gunStatus); + } + } + + // 注意:枪的具体状态通过GunService单独管理,与桩状态解耦 + log.debug("充电枪状态上报: 桩编码={}, 枪状态={}", pileCode, gunStatus); + } + + /** + * 保存地锁状态到属性表 + */ + private void saveLockStatusToAttributes(UUID gunId, int lockStatus, int parkStatus, int lockBattery, int alarmStatus, long ts) { + try { + + // 保存地锁状态 + AttributeKvEntry lockStatusAttr = new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.LOCK_STATUS.getCode(), (long) lockStatus), + ts + ); + attributeService.save(gunId, lockStatusAttr); + + // 保存车位状态 + AttributeKvEntry parkStatusAttr = new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.PARK_STATUS.getCode(), (long) parkStatus), + ts + ); + attributeService.save(gunId, parkStatusAttr); + + // 地锁电量和报警状态暂不保存,只记录日志 + log.debug("地锁电量: {}%, 报警状态: {}", lockBattery, alarmStatus); + + } catch (Exception e) { + log.error("保存地锁状态到属性表失败: gunId={}", gunId, e); + throw e; + } + } } \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileService.java new file mode 100644 index 0000000..f5101c5 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileService.java @@ -0,0 +1,324 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.adapter.request.PileCreateRequest; +import sanbing.jcpp.app.adapter.request.PileQueryRequest; +import sanbing.jcpp.app.adapter.request.PileUpdateRequest; +import sanbing.jcpp.app.adapter.response.PageResponse; +import sanbing.jcpp.app.adapter.response.PileOptionResponse; +import sanbing.jcpp.app.adapter.response.PileWithStatusResponse; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; +import sanbing.jcpp.app.dal.entity.Pile; +import sanbing.jcpp.app.dal.mapper.GunMapper; +import sanbing.jcpp.app.dal.mapper.PileMapper; +import sanbing.jcpp.app.dal.repository.PileRepository; +import sanbing.jcpp.app.data.kv.*; +import sanbing.jcpp.app.exception.JCPPErrorCode; +import sanbing.jcpp.app.exception.JCPPException; +import sanbing.jcpp.app.service.AttributeService; +import sanbing.jcpp.app.service.PileService; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Slf4j +public class DefaultPileService implements PileService { + + private final PileMapper pileMapper; + private final PileRepository pileRepository; + private final AttributeService attributeService; + private final GunMapper gunMapper; + + + @Override + public Pile createPile(PileCreateRequest request) { + // 检查充电桩编码是否已存在 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Pile::getPileCode, request.getPileCode()); + if (pileMapper.selectCount(wrapper) > 0) { + throw new RuntimeException("充电桩编码已存在"); + } + + Pile pile = Pile.builder() + .id(UUID.randomUUID()) + .createdTime(LocalDateTime.now()) + .pileName(request.getPileName()) + .pileCode(request.getPileCode()) + .protocol(request.getProtocol()) + .stationId(request.getStationId()) + .brand(request.getBrand()) + .model(request.getModel()) + .manufacturer(request.getManufacturer()) + .type(request.getType()) + .additionalInfo(JacksonUtil.newObjectNode()) + .version(0) + .build(); + + pileMapper.insert(pile); + return pile; + } + + @Override + public Pile findById(UUID id) { + return pileMapper.selectById(id); + } + + @Override + public Pile updatePile(UUID id, PileUpdateRequest request) throws JCPPException { + Pile existingPile = findById(id); + if (existingPile == null) { + throw new JCPPException("充电桩不存在", JCPPErrorCode.ITEM_NOT_FOUND); + } + + Pile updatedPile = Pile.builder() + .id(existingPile.getId()) + .createdTime(existingPile.getCreatedTime()) + .updatedTime(LocalDateTime.now()) // 更新时设置更新时间 + .pileName(request.getPileName()) + .pileCode(existingPile.getPileCode()) // 编码不允许修改 + .protocol(request.getProtocol()) + .stationId(existingPile.getStationId()) // 所属充电站不允许修改 + .brand(request.getBrand()) + .model(request.getModel()) + .manufacturer(request.getManufacturer()) + .type(request.getType()) + .additionalInfo(existingPile.getAdditionalInfo()) + .version(existingPile.getVersion()) + .build(); + + pileMapper.updateById(updatedPile); + return updatedPile; + } + + @Override + public void deletePile(UUID id) throws JCPPException { + // 检查充电桩是否存在 + Pile pile = findById(id); + if (pile == null) { + throw new JCPPException("充电桩不存在", JCPPErrorCode.ITEM_NOT_FOUND); + } + + // 检查充电桩下是否存在充电枪 + long gunCount = gunMapper.countByPileId(id); + if (gunCount > 0) { + throw new JCPPException( + String.format("无法删除充电桩[%s],该充电桩下还有 %d 支充电枪,请先删除所有充电枪", + pile.getPileName(), gunCount), + JCPPErrorCode.VERSION_CONFLICT); + } + + // 执行删除 + int affectedRows = pileMapper.deleteById(id); + if (affectedRows == 0) { + throw new JCPPException("删除充电桩失败,可能已被其他操作删除", JCPPErrorCode.VERSION_CONFLICT); + } + } + + + @Override + public PageResponse queryPilesWithStatus(PileQueryRequest request) { + // 添加详细的分页调试日志 + Page page = new Page<>(request.getPage(), request.getSize()); + + // 使用AttrKeyEnum消除魔法值,提高代码可维护性 + IPage result = pileMapper.selectPileWithStatusPage( + page, + request, + AttrKeyEnum.STATUS, + AttrKeyEnum.CONNECTED_AT, + AttrKeyEnum.DISCONNECTED_AT, + AttrKeyEnum.LAST_ACTIVE_TIME + ); + + return PageResponse.builder() + .records(result.getRecords()) + .total(result.getTotal()) + .totalPages((int) result.getPages()) + .page(request.getPage()) + .size(request.getSize()) + .build(); + } + + @Override + public void updatePileStatus(UUID pileId, PileStatusEnum status) { + try { + // 获取现有充电桩信息 + Pile existingPile = findById(pileId); + if (existingPile == null) { + log.warn("充电桩不存在,无法更新状态: ID={}, 状态={}", pileId, status); + return; + } + + // 检查状态是否真的发生了变化,避免重复保存 + String currentStatus = findPileStatus(pileId); + if (status.name().equals(currentStatus)) { + log.debug("充电桩状态未发生变化,跳过更新: ID={}, 状态={}", pileId, status); + return; + } + + long currentTime = System.currentTimeMillis(); + + // 更新状态属性 + AttributeKvEntry statusAttr = new BaseAttributeKvEntry( + new StringDataEntry(AttrKeyEnum.STATUS.getCode(), status.name()), + currentTime + ); + attributeService.save(pileId, statusAttr); + + log.info("充电桩状态更新成功: ID={}, 桩编码={}, 原状态={}, 新状态={}", + pileId, existingPile.getPileCode(), currentStatus, status); + } catch (Exception e) { + log.error("更新充电桩状态失败: ID={}, 状态={}", pileId, status, e); + throw new RuntimeException("更新充电桩状态失败", e); + } + } + + @Override + public void updatePileStatusByCode(String pileCode, PileStatusEnum status) { + try { + // 根据编码查找充电桩 + Pile pile = pileRepository.findPileByCode(pileCode); + if (pile == null) { + log.warn("根据编码未找到充电桩: pileCode={}", pileCode); + return; + } + + // 调用基于ID的更新方法 + updatePileStatus(pile.getId(), status); + } catch (Exception e) { + log.error("根据编码更新充电桩状态失败: pileCode={}, 状态={}", pileCode, status, e); + throw new RuntimeException("根据编码更新充电桩状态失败", e); + } + } + + @Override + public List findAll() { + return pileMapper.selectList(null); + } + + @Override + public List findPilesWithPagination(int offset, int limit) { + return pileMapper.selectPage(new Page<>(offset / limit + 1, limit), null).getRecords(); + } + + @Override + public String findPileStatus(UUID pileId) { + // 直接从数据库查询,避免异步复杂性 + ListenableFuture> attribute = attributeService.find(pileId, AttrKeyEnum.STATUS.getCode()); + try { + Optional result = attribute.get(); + if (result.isPresent()) { + AttributeKvEntry entry = result.get(); + Optional strValue = entry.getStrValue(); + return strValue.orElse(null); + } + return null; + } catch (Exception e) { + log.error("获取充电桩状态失败: pileId={}", pileId, e); + return null; + } + } + + @Override + public ListenableFuture handlePileLogin(UUID pileId) { + long currentTime = System.currentTimeMillis(); + List attributesToUpdate = new ArrayList<>(); + + // 1. 更新STATUS为ONLINE + attributesToUpdate.add(new BaseAttributeKvEntry( + new StringDataEntry(AttrKeyEnum.STATUS.getCode(), PileStatusEnum.ONLINE.name()), + currentTime)); + + // 2. 更新CONNECTED_AT为当前系统时间 + attributesToUpdate.add(new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.CONNECTED_AT.getCode(), currentTime), + currentTime)); + + // 3. 更新LAST_ACTIVE_TIME为当前系统时间 + attributesToUpdate.add(new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.LAST_ACTIVE_TIME.getCode(), currentTime), + currentTime)); + + // 批量保存属性 + return attributeService.save(pileId, attributesToUpdate); + } + + @Override + public ListenableFuture handlePileHeartbeat(UUID pileId) { + long currentTime = System.currentTimeMillis(); + List attributesToUpdate = new ArrayList<>(); + + // 1. 更新STATUS为ONLINE + attributesToUpdate.add(new BaseAttributeKvEntry( + new StringDataEntry(AttrKeyEnum.STATUS.getCode(), PileStatusEnum.ONLINE.name()), + currentTime)); + + // 2. 更新LAST_ACTIVE_TIME为当前系统时间 + attributesToUpdate.add(new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.LAST_ACTIVE_TIME.getCode(), currentTime), + currentTime)); + + // 批量保存属性 + return attributeService.save(pileId, attributesToUpdate); + } + + @Override + public void handlePileSessionClose(String pileCode) { + Pile pile = pileRepository.findPileByCode(pileCode); + if (pile == null) { + log.warn("充电桩会话关闭处理失败,未找到充电桩: pileCode={}", pileCode); + return; + } + + long currentTime = System.currentTimeMillis(); + List attributesToUpdate = new ArrayList<>(); + + // 1. 更新STATUS为OFFLINE + attributesToUpdate.add(new BaseAttributeKvEntry( + new StringDataEntry(AttrKeyEnum.STATUS.getCode(), PileStatusEnum.OFFLINE.name()), + currentTime)); + + // 2. 更新DISCONNECTED_AT为当前系统时间 + attributesToUpdate.add(new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.DISCONNECTED_AT.getCode(), currentTime), + currentTime)); + + // 批量保存属性 + attributeService.save(pile.getId(), attributesToUpdate); + + log.info("充电桩会话关闭,设置为离线状态: 桩编码={}", pileCode); + } + + @Override + public List getPileOptions() { + List piles = pileMapper.selectList(null); + return piles.stream() + .map(pile -> PileOptionResponse.builder() + .id(pile.getId()) + .label(pile.getPileName() + " (" + pile.getPileCode() + ")") + .pileName(pile.getPileName()) + .pileCode(pile.getPileCode()) + .stationId(pile.getStationId()) + .build()) + .collect(Collectors.toList()); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileSessionService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileSessionService.java new file mode 100644 index 0000000..cd14dfb --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultPileSessionService.java @@ -0,0 +1,369 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.impl; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.dal.config.ibatis.enums.PileStatusEnum; +import sanbing.jcpp.app.dal.entity.Pile; +import sanbing.jcpp.app.data.PileSession; +import sanbing.jcpp.app.data.kv.*; +import sanbing.jcpp.app.data.page.PageDataIterable; +import sanbing.jcpp.app.service.AttributeService; +import sanbing.jcpp.app.service.PileService; +import sanbing.jcpp.app.service.PileSessionService; +import sanbing.jcpp.app.service.cache.session.PileSessionCacheKey; +import sanbing.jcpp.infrastructure.cache.CacheValueWrapper; +import sanbing.jcpp.infrastructure.cache.TransactionalCache; +import sanbing.jcpp.infrastructure.queue.common.TopicPartitionInfo; +import sanbing.jcpp.infrastructure.queue.discovery.PartitionProvider; +import sanbing.jcpp.infrastructure.queue.discovery.ServiceType; +import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * PileSession管理服务默认实现 + * + * @author 九筒 + */ +@Service +@Slf4j +public class DefaultPileSessionService implements PileSessionService { + + @Resource + private TransactionalCache pileSessionCache; + + @Resource + private PileService pileService; + + @Resource + private AttributeService attributeService; + + + @Resource + private PartitionProvider partitionProvider; + + @Value("${service.protocol.sessions.default-inactivity-timeout-in-sec:600}") + private int inactivityTimeoutInSec; + + private static final int FETCH_PAGE_SIZE = 1000; + + @Override + public PileSession createOrUpdateSession(UplinkQueueMessage uplinkQueueMessage, + Pile pile, + String remoteAddress, + String nodeId, + String nodeIp, + int restPort, + int grpcPort) { + + String pileCode = pile.getPileCode(); + PileSessionCacheKey cacheKey = new PileSessionCacheKey(pileCode); + + // 直接创建或更新会话,一步缓存操作 + PileSession pileSession = createSession(uplinkQueueMessage, pile, remoteAddress, + nodeId, nodeIp, restPort, grpcPort); + + pileSessionCache.put(cacheKey, pileSession); + + log.debug("保存PileSession: pileCode={}, sessionId={}", + pileCode, pileSession.getProtocolSessionId()); + + return pileSession; + } + + @Override + public Optional getSession(String pileCode) { + try { + CacheValueWrapper wrapper = pileSessionCache.get(new PileSessionCacheKey(pileCode)); + if (wrapper != null && wrapper.get() != null) { + return Optional.of(wrapper.get()); + } + } catch (Exception e) { + log.warn("获取PileSession失败: pileCode={}", pileCode, e); + } + return Optional.empty(); + } + + + @Override + public boolean hasActiveSession(String pileCode) { + return getSession(pileCode).isPresent(); + } + + + @Override + public void removeSession(String pileCode) { + try { + PileSessionCacheKey cacheKey = new PileSessionCacheKey(pileCode); + pileSessionCache.evict(cacheKey); + log.info("PileSession已移除: pileCode={}", pileCode); + } catch (Exception e) { + log.error("移除PileSession失败: pileCode={}", pileCode, e); + } + } + + + @Override + public List checkActiveSessions(List pileCodes) { + if (pileCodes.isEmpty()) { + return new ArrayList<>(); + } + + List activePileCodes = new ArrayList<>(); + + for (String pileCode : pileCodes) { + if (hasActiveSession(pileCode)) { + activePileCodes.add(pileCode); + } + } + + log.debug("批量检查会话状态完成: 检查数量={}, 活跃数量={}", + pileCodes.size(), activePileCodes.size()); + + return activePileCodes; + } + + /** + * 执行会话健康检查和状态清洗 + * 基于 service.protocol.sessions 配置进行兜底检查 + * 使用分区逻辑避免多实例冲突 + * 维护充电桩的 status、connectedAt、disconnectedAt、lastActiveTime 属性 + */ + @Scheduled(fixedDelayString = "#{${service.protocol.sessions.default-state-check-interval-in-sec:60} * 1000}") + public void performSessionHealthCheck() { + try { + log.debug("开始执行会话健康检查和状态清洗"); + + long startTime = System.currentTimeMillis(); + int processedCount = 0; + int updatedCount = 0; + int onlineCount = 0; + int offlineCount = 0; + + // 计算超时阈值 + long currentTime = System.currentTimeMillis(); + long timeoutThreshold = currentTime - (inactivityTimeoutInSec * 1000L); + + // 分页处理所有充电桩 + PageDataIterable pileIterable = new PageDataIterable<>( + pileService::findPilesWithPagination, + FETCH_PAGE_SIZE + ); + + for (Pile pile : pileIterable) { + processedCount++; + String pileCode = pile.getPileCode(); + + // 使用分区逻辑判断是否由本实例处理 + if (!isMyPartition(pileCode)) { + continue; + } + + try { + PileHealthCheckResult result = checkAndUpdatePileStatus(pile, timeoutThreshold, currentTime); + if (result.statusUpdated) { + updatedCount++; + } + + // 统计最终状态 + if (PileStatusEnum.ONLINE.name().equals(result.finalStatus)) { + onlineCount++; + } else { + offlineCount++; + } + + } catch (Exception e) { + log.error("处理充电桩健康检查失败: pileCode={}", pileCode, e); + } + } + + long endTime = System.currentTimeMillis(); + log.info("会话健康检查完成: 处理数量={}, 更新数量={}, 在线数量={}, 离线数量={}, 耗时={}ms", + processedCount, updatedCount, onlineCount, offlineCount, endTime - startTime); + + } catch (Exception e) { + log.error("会话健康检查执行失败", e); + } + } + + /** + * 健康检查结果 + */ + private record PileHealthCheckResult(boolean statusUpdated, String finalStatus) { + } + + /** + * 判断充电桩是否由当前实例处理(分区逻辑) + */ + private boolean isMyPartition(String pileCode) { + try { + TopicPartitionInfo partitionInfo = partitionProvider.resolve(ServiceType.APP, "pile-session", pileCode); + return partitionInfo.isMyPartition(); + } catch (Exception e) { + log.warn("分区判断失败: pileCode={}, 默认由当前实例处理", pileCode, e); + return true; // 出错时默认处理,避免遗漏 + } + } + + /** + * 检查并更新充电桩状态(优化版本) + */ + private PileHealthCheckResult checkAndUpdatePileStatus(Pile pile, long timeoutThreshold, long currentTime) { + String pileCode = pile.getPileCode(); + UUID pileId = pile.getId(); + + // 检查是否有活跃会话 + boolean hasActiveSession = hasActiveSession(pileCode); + + // 获取当前状态和最后活跃时间(通过AttributeService一次性获取) + try { + List attributes = attributeService.find(pileId, + List.of(AttrKeyEnum.STATUS.getCode(), AttrKeyEnum.LAST_ACTIVE_TIME.getCode())).get(); + + String currentStatus = null; + Long lastActiveTime = null; + + for (AttributeKvEntry attr : attributes) { + if (AttrKeyEnum.STATUS.getCode().equals(attr.getKey())) { + currentStatus = attr.getStrValue().orElse(PileStatusEnum.OFFLINE.name()); + } else if (AttrKeyEnum.LAST_ACTIVE_TIME.getCode().equals(attr.getKey())) { + lastActiveTime = attr.getLongValue().orElse(null); + } + } + + // 如果当前状态为空,默认为离线 + if (currentStatus == null) { + currentStatus = PileStatusEnum.OFFLINE.name(); + } + + // 决定目标状态 + String targetStatus = determineTargetStatusOptimized(hasActiveSession, currentStatus, lastActiveTime, timeoutThreshold); + + // 更新状态(如果需要) + boolean statusUpdated = false; + if (!targetStatus.equals(currentStatus)) { + updatePileStatusOptimized(pileId, targetStatus, currentTime); + log.info("健康检查更新充电桩状态: pileCode={}, 从 {} 更新为 {}, 会话状态={}", + pileCode, currentStatus, targetStatus, hasActiveSession ? "有" : "无"); + statusUpdated = true; + } + + return new PileHealthCheckResult(statusUpdated, targetStatus); + + } catch (Exception e) { + log.error("检查充电桩状态失败: pileCode={}", pileCode, e); + // 异常情况下,返回当前可能的状态 + String fallbackStatus = hasActiveSession ? PileStatusEnum.ONLINE.name() : PileStatusEnum.OFFLINE.name(); + return new PileHealthCheckResult(false, fallbackStatus); + } + } + + /** + * 优化的状态判断逻辑 + */ + private String determineTargetStatusOptimized(boolean hasActiveSession, String currentStatus, Long lastActiveTime, long timeoutThreshold) { + // 有活跃会话,应该在线 + if (hasActiveSession) { + return PileStatusEnum.ONLINE.name(); + } + + // 无活跃会话,需要检查最后活跃时间 + boolean isCurrentlyOnline = PileStatusEnum.ONLINE.name().equals(currentStatus); + if (isCurrentlyOnline) { + // 当前显示在线但无会话,检查最后活跃时间 + if (lastActiveTime != null && lastActiveTime < timeoutThreshold) { + return PileStatusEnum.OFFLINE.name(); + } else if (lastActiveTime == null) { + // 没有最后活跃时间记录,设为离线 + return PileStatusEnum.OFFLINE.name(); + } + } + + // 其他情况保持当前状态 + return currentStatus; + } + + /** + * 优化的状态更新方法(批量更新相关属性) + */ + private void updatePileStatusOptimized(UUID pileId, String status, long currentTime) { + try { + List attributesToUpdate = new ArrayList<>(); + + // 更新状态属性 + attributesToUpdate.add(new BaseAttributeKvEntry( + new StringDataEntry(AttrKeyEnum.STATUS.getCode(), status), + currentTime)); + + // 根据状态更新相应的时间戳 + if (PileStatusEnum.ONLINE.name().equals(status)) { + // 设为在线时更新连接时间和最后活跃时间 + attributesToUpdate.add(new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.CONNECTED_AT.getCode(), currentTime), + currentTime)); + attributesToUpdate.add(new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.LAST_ACTIVE_TIME.getCode(), currentTime), + currentTime)); + + // 删除断开连接时间(如果存在) + attributeService.removeAll(pileId, List.of(AttrKeyEnum.DISCONNECTED_AT.getCode())); + + } else if (PileStatusEnum.OFFLINE.name().equals(status)) { + // 设为离线时更新断开连接时间 + attributesToUpdate.add(new BaseAttributeKvEntry( + new LongDataEntry(AttrKeyEnum.DISCONNECTED_AT.getCode(), currentTime), + currentTime)); + } + + // 批量保存属性 + attributeService.save(pileId, attributesToUpdate); + + } catch (Exception e) { + log.error("更新充电桩状态失败: pileId={}, status={}", pileId, status, e); + throw e; + } + } + + + /** + * 创建PileSession实例 + * 从原来的DefaultPileProtocolService中提取的逻辑 + */ + private PileSession createSession(UplinkQueueMessage uplinkQueueMessage, + Pile pile, + String remoteAddress, + String nodeId, + String nodeIp, + int restPort, + int grpcPort) { + + PileSession pileSession = new PileSession(pile.getId(), pile.getPileCode(), + uplinkQueueMessage.getProtocolName()); + + // 设置协议会话ID + pileSession.setProtocolSessionId(new UUID(uplinkQueueMessage.getSessionIdMSB(), + uplinkQueueMessage.getSessionIdLSB())); + + // 设置网络信息 + pileSession.setRemoteAddress(remoteAddress); + pileSession.setNodeId(nodeId); + pileSession.setNodeIp(nodeIp); + pileSession.setNodeRestPort(restPort); + pileSession.setNodeGrpcPort(grpcPort); + + + return pileSession; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultProtocolService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultProtocolService.java new file mode 100644 index 0000000..930273e --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultProtocolService.java @@ -0,0 +1,36 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.impl; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.adapter.response.ProtocolOption; +import sanbing.jcpp.app.service.ProtocolService; +import sanbing.jcpp.protocol.enums.SupportedProtocols; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 协议服务实现 + * + * @author 九筒 + * @since 2024-12-22 + */ +@Slf4j +@Service +public class DefaultProtocolService implements ProtocolService { + + @Override + public List getSupportedProtocols() { + // 直接从SupportedProtocols获取,无需缓存 + return SupportedProtocols.getAllProtocols() + .stream() + .map(ProtocolOption::fromProtocolInfo) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultStationService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultStationService.java new file mode 100644 index 0000000..bc9746d --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultStationService.java @@ -0,0 +1,193 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import sanbing.jcpp.app.adapter.request.StationCreateRequest; +import sanbing.jcpp.app.adapter.request.StationQueryRequest; +import sanbing.jcpp.app.adapter.request.StationUpdateRequest; +import sanbing.jcpp.app.adapter.response.PageResponse; +import sanbing.jcpp.app.adapter.response.StationOption; +import sanbing.jcpp.app.dal.entity.Station; +import sanbing.jcpp.app.dal.mapper.PileMapper; +import sanbing.jcpp.app.dal.mapper.StationMapper; +import sanbing.jcpp.app.exception.JCPPErrorCode; +import sanbing.jcpp.app.exception.JCPPException; +import sanbing.jcpp.app.service.StationService; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * 充电站服务实现 + * + * @author 九筒 + */ +@Service +@RequiredArgsConstructor +public class DefaultStationService implements StationService { + + private final StationMapper stationMapper; + private final PileMapper pileMapper; + + @Override + public PageResponse getStations(StationQueryRequest request) { + QueryWrapper wrapper = new QueryWrapper<>(); + + // 添加搜索条件 + if (StringUtils.hasText(request.getStationName())) { + wrapper.like("station_name", request.getStationName()); + } + if (StringUtils.hasText(request.getStationCode())) { + wrapper.like("station_code", request.getStationCode()); + } + if (StringUtils.hasText(request.getProvince())) { + wrapper.eq("province", request.getProvince()); + } + if (StringUtils.hasText(request.getCity())) { + wrapper.eq("city", request.getCity()); + } + + + // 添加排序 + if (StringUtils.hasText(request.getSortField())) { + boolean isAsc = "asc".equalsIgnoreCase(request.getSortOrder()); + wrapper.orderBy(true, isAsc, request.getSortField()); + } else { + wrapper.orderByDesc("created_time"); + } + + // 分页查询 + Page page = new Page<>(request.getPage(), request.getSize()); + IPage result = stationMapper.selectPage(page, wrapper); + + return PageResponse.of(result.getRecords(), result.getTotal(), request.getPage(), request.getSize()); + } + + @Override + public Station getStationById(UUID id) { + return stationMapper.selectById(id); + } + + @Override + public Station createStation(StationCreateRequest request) { + Station station = Station.builder() + .id(UUID.randomUUID()) + .createdTime(LocalDateTime.now()) + .stationName(request.getStationName()) + .stationCode(request.getStationCode()) + .longitude(request.getLongitude()) + .latitude(request.getLatitude()) + .province(request.getProvince()) + .city(request.getCity()) + .county(request.getCounty()) + .address(request.getAddress()) + .additionalInfo(JacksonUtil.newObjectNode()) + .version(1) + .build(); + + stationMapper.insert(station); + return station; + } + + @Override + public Station updateStation(UUID id, StationUpdateRequest request) throws JCPPException { + Station station = stationMapper.selectById(id); + if (station == null) { + throw new JCPPException("充电站不存在", JCPPErrorCode.ITEM_NOT_FOUND); + } + + station.setUpdatedTime(LocalDateTime.now()); // 更新时设置更新时间 + station.setStationName(request.getStationName()); + station.setLongitude(request.getLongitude()); + station.setLatitude(request.getLatitude()); + station.setProvince(request.getProvince()); + station.setCity(request.getCity()); + station.setCounty(request.getCounty()); + station.setAddress(request.getAddress()); + + stationMapper.updateById(station); + return station; + } + + @Override + public void deleteStation(UUID id) throws JCPPException { + // 检查充电站是否存在 + Station station = stationMapper.selectById(id); + if (station == null) { + throw new JCPPException("充电站不存在", JCPPErrorCode.ITEM_NOT_FOUND); + } + + // 检查充电站下是否存在充电桩 + long pileCount = pileMapper.countByStationId(id); + if (pileCount > 0) { + throw new JCPPException( + String.format("无法删除充电站[%s],该充电站下还有 %d 个充电桩,请先删除所有充电桩", + station.getStationName(), pileCount), + JCPPErrorCode.VERSION_CONFLICT); + } + + // 执行删除 + int affectedRows = stationMapper.deleteById(id); + if (affectedRows == 0) { + throw new JCPPException("删除充电站失败,可能已被其他操作删除", JCPPErrorCode.VERSION_CONFLICT); + } + } + + @Override + public List getStationOptions() { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.select("id", "station_name", "station_code") + .orderByAsc("station_name"); + + List stations = stationMapper.selectList(wrapper); + + return stations.stream() + .map(station -> StationOption.of( + station.getId(), + station.getStationName(), + station.getStationCode() + )) + .collect(Collectors.toList()); + } + + @Override + public List searchStationOptions(String keyword, int page, int size) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.select("id", "station_name", "station_code"); + + // 如果有关键字,按站名或编码模糊搜索 + if (StringUtils.hasText(keyword)) { + wrapper.and(w -> w.like("station_name", keyword) + .or() + .like("station_code", keyword)); + } + + // 按创建时间倒序排序 + wrapper.orderByDesc("created_time"); + + // 分页查询 + Page pageQuery = new Page<>(page, size); + IPage result = stationMapper.selectPage(pageQuery, wrapper); + + return result.getRecords().stream() + .map(station -> StationOption.of( + station.getId(), + station.getStationName(), + station.getStationCode() + )) + .collect(Collectors.toList()); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultUserService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultUserService.java new file mode 100644 index 0000000..01261c1 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/DefaultUserService.java @@ -0,0 +1,251 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import sanbing.jcpp.app.dal.entity.User; +import sanbing.jcpp.app.dal.mapper.UserMapper; +import sanbing.jcpp.app.service.UserService; +import sanbing.jcpp.app.service.security.config.SecurityProperties; +import sanbing.jcpp.app.service.security.model.UserCredentials; + +import java.util.UUID; + +/** + * 用户服务实现 + * + * @author 九筒 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class DefaultUserService implements UserService { + + private final UserMapper userMapper; + private final BCryptPasswordEncoder encoder; + private final SecurityProperties securityProperties; + + @Override + public User findById(UUID id) { + if (id == null) { + log.warn("findById called with null id"); + return null; + } + + try { + return userMapper.selectById(id); + } catch (Exception e) { + log.error("Error finding user by id: {}", id, e); + throw new RuntimeException("查询用户失败", e); + } + } + + @Override + public User findUserByUsername(String username) { + if (username == null || username.trim().isEmpty()) { + log.warn("findUserByUsername called with null or empty username"); + return null; + } + + try { + return userMapper.findByUserName(username); + } catch (Exception e) { + log.error("Error finding user by username: {}", username, e); + throw new RuntimeException("根据用户名查询用户失败", e); + } + } + + @Override + public int increaseFailedLoginAttempts(UUID userId) { + if (userId == null) { + log.warn("increaseFailedLoginAttempts called with null userId"); + throw new IllegalArgumentException("用户ID不能为空"); + } + + try { + // 1. 查询当前用户 + User user = userMapper.selectById(userId); + if (user == null) { + log.warn("User not found with id: {}", userId); + throw new RuntimeException("用户不存在"); + } + + // 2. 获取当前的userCredentials + UserCredentials credentials = user.getUserCredentials(); + if (credentials == null) { + // 如果userCredentials为空,创建新的UserCredentials对象 + credentials = new UserCredentials(); + } + + // 3. 获取当前失败次数并累加 + int currentAttempts = credentials.getFailedLoginAttempts() != null ? credentials.getFailedLoginAttempts() : 0; + int newAttempts = currentAttempts + 1; + + // 4. 更新失败次数 + credentials.setFailedLoginAttempts(newAttempts); + + // 5. 更新用户实体 + user.setUserCredentials(credentials); + + // 6. 保存到数据库 + int updateResult = userMapper.updateById(user); + if (updateResult == 0) { + log.error("Failed to update user credentials for userId: {}", userId); + throw new RuntimeException("更新用户登录失败次数失败"); + } + + log.debug("Increased failed login attempts for user {} from {} to {}", userId, currentAttempts, newAttempts); + return newAttempts; + + } catch (Exception e) { + log.error("Error increasing failed login attempts for userId: {}", userId, e); + throw e; + } + } + + @Override + public void setUserCredentialsEnabled(UUID userId, boolean enabled) { + if (userId == null) { + log.warn("setUserCredentialsEnabled called with null userId"); + throw new IllegalArgumentException("用户ID不能为空"); + } + + try { + // 1. 查询当前用户 + User user = userMapper.selectById(userId); + if (user == null) { + log.warn("User not found with id: {}", userId); + throw new RuntimeException("用户不存在"); + } + + // 2. 获取当前的userCredentials + UserCredentials credentials = user.getUserCredentials(); + if (credentials == null) { + // 如果userCredentials为空,创建新的UserCredentials对象 + credentials = new UserCredentials(); + } + + // 3. 获取当前状态 + boolean currentEnabled = credentials.isEnabled(); + + // 4. 如果状态没有变化,直接返回 + if (currentEnabled == enabled) { + log.debug("User {} credentials enabled status already is {}, no update needed", userId, enabled); + return; + } + + // 5. 更新启用状态 + credentials.setEnabled(enabled); + + // 6. 更新用户实体 + user.setUserCredentials(credentials); + + // 7. 保存到数据库 + int updateResult = userMapper.updateById(user); + if (updateResult == 0) { + log.error("Failed to update user credentials enabled status for userId: {}", userId); + throw new RuntimeException("更新用户凭证启用状态失败"); + } + + log.info("Updated user {} credentials enabled status from {} to {}", userId, currentEnabled, enabled); + + } catch (Exception e) { + log.error("Error setting user credentials enabled status for userId: {}, enabled: {}", userId, enabled, e); + throw e; + } + } + + @Override + public void resetFailedLoginAttempts(UUID userId) { + if (userId == null) { + log.warn("resetFailedLoginAttempts called with null userId"); + throw new IllegalArgumentException("用户ID不能为空"); + } + + try { + // 1. 查询当前用户 + User user = userMapper.selectById(userId); + if (user == null) { + log.warn("User not found with id: {}", userId); + throw new RuntimeException("用户不存在"); + } + + // 2. 获取当前的userCredentials + UserCredentials credentials = user.getUserCredentials(); + if (credentials == null) { + // 如果userCredentials为空,说明本来就没有失败记录,直接返回 + log.debug("User {} has no credentials, no failed attempts to reset", userId); + return; + } + + // 3. 获取当前失败次数 + Integer currentAttempts = credentials.getFailedLoginAttempts(); + + // 4. 如果失败次数已经是0或null,直接返回 + if (currentAttempts == null || currentAttempts == 0) { + log.debug("User {} failed login attempts already is 0, no reset needed", userId); + return; + } + + // 5. 重置失败次数为0 + credentials.setFailedLoginAttempts(0); + + // 6. 更新用户实体 + user.setUserCredentials(credentials); + + // 7. 保存到数据库 + int updateResult = userMapper.updateById(user); + if (updateResult == 0) { + log.error("Failed to reset failed login attempts for userId: {}", userId); + throw new RuntimeException("重置用户登录失败次数失败"); + } + + log.info("Reset failed login attempts for user {} from {} to 0", userId, currentAttempts); + + } catch (Exception e) { + log.error("Error resetting failed login attempts for userId: {}", userId, e); + throw e; + } + } + + @Override + public void validateUserCredentials(UUID userId, UserCredentials userCredentials, String username, String password) throws AuthenticationException { + if (!encoder.matches(password, userCredentials.getPassword())) { + int failedLoginAttempts = increaseFailedLoginAttempts(userId); + SecurityProperties.SecuritySettingsProperties settings = securityProperties.getSettings(); + if (settings.getMaxFailedLoginAttempts() != null && settings.getMaxFailedLoginAttempts() > 0) { + if (failedLoginAttempts > settings.getMaxFailedLoginAttempts() && userCredentials.isEnabled()) { + lockAccount(userId, username, settings.getUserLockoutNotificationEmail(), settings.getMaxFailedLoginAttempts()); + throw new LockedException("Authentication Failed. Username was locked due to security policy."); + } + } + throw new BadCredentialsException("Authentication Failed. Username or Password not valid."); + } + + if (!userCredentials.isEnabled()) { + throw new DisabledException("User is not active"); + } + + resetFailedLoginAttempts(userId); + } + + /** + * 锁定账户 + */ + private void lockAccount(UUID userId, String username, String userLockoutNotificationEmail, Integer maxFailedLoginAttempts) { + setUserCredentialsEnabled(userId, false); + log.warn("User {} locked due to {} failed login attempts", username, maxFailedLoginAttempts); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/GrpcDownlinkCallService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/GrpcDownlinkCallService.java index 5aea74a..715b18e 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/GrpcDownlinkCallService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/GrpcDownlinkCallService.java @@ -19,7 +19,7 @@ import sanbing.jcpp.proto.gen.ProtocolProto.RequestMsg; import static sanbing.jcpp.infrastructure.proto.ProtoConverter.toTracerProto; /** - * @author baigod + * @author 九筒 */ @Service @Slf4j diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/RestDownlinkCallService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/RestDownlinkCallService.java index 9df7b3d..0343001 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/RestDownlinkCallService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/impl/RestDownlinkCallService.java @@ -23,7 +23,7 @@ import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRequestMessage; import static sanbing.jcpp.infrastructure.util.trace.TracerContextUtil.*; /** - * @author baigod + * @author 九筒 */ @Service @Slf4j @@ -59,6 +59,6 @@ public class RestDownlinkCallService extends DownlinkCallService { String url = String.format("http://%s:%d/api/onDownlink", nodeIp, port); ResponseEntity response = downlinkRestTemplate.postForEntity(url, entity, ResponseEntity.class); - log.debug("下行消息发送成功 {}", response); + log.debug("下行消息发送完成 {}", response); } } \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/AbstractConsumerService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/AbstractConsumerService.java index 94106e8..f6fb7aa 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/AbstractConsumerService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/AbstractConsumerService.java @@ -9,8 +9,6 @@ package sanbing.jcpp.app.service.queue; import jakarta.annotation.PreDestroy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.ApplicationEventPublisher; -import sanbing.jcpp.infrastructure.queue.discovery.PartitionProvider; import sanbing.jcpp.infrastructure.queue.discovery.event.JCPPApplicationEventListener; import sanbing.jcpp.infrastructure.queue.discovery.event.PartitionChangeEvent; import sanbing.jcpp.infrastructure.util.annotation.AfterStartUp; @@ -25,9 +23,6 @@ import java.util.concurrent.ScheduledExecutorService; @RequiredArgsConstructor public abstract class AbstractConsumerService extends JCPPApplicationEventListener { - protected final PartitionProvider partitionProvider; - protected final ApplicationEventPublisher eventPublisher; - protected ExecutorService consumersExecutor; protected ExecutorService mgmtExecutor; protected ScheduledExecutorService scheduler; diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/AppConsumerStats.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/AppConsumerStats.java index a4fe41d..358a1d1 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/AppConsumerStats.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/AppConsumerStats.java @@ -7,6 +7,7 @@ package sanbing.jcpp.app.service.queue; import io.micrometer.core.instrument.Timer; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.stats.StatsCounter; import sanbing.jcpp.infrastructure.stats.StatsFactory; @@ -14,11 +15,13 @@ import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import java.time.Duration; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.concurrent.TimeUnit; @Slf4j public class AppConsumerStats { + // StatsCounter相关常量 + public static final String STATS_KEY_APP_CONSUMER = "appConsumer"; public static final String TOTAL_MSGS = "totalMsgs"; public static final String LOGIN_EVENTS = "loginEvents"; public static final String HEARTBEAT_EVENTS = "heartBeatEvents"; @@ -26,26 +29,81 @@ public class AppConsumerStats { public static final String CHARGING_PROGRESS_EVENTS = "chargingProgressEvents"; public static final String TRANSACTION_RECORD_EVENTS = "transactionRecordEvents"; + // Timer相关常量 + public static final String STATS_KEY_APP_CONSUMER_MSGS = "appConsumerMsgs"; + public static final String TIMER_TAG_MSG_TYPE = "msgType"; + + /** + * 消费者处理阶段枚举,对应ProtocolUplinkConsumerService#processMsgs中的不同条件分支 + */ + @Getter + public enum AppConsumerPhaseEnum { + LOGIN_REQUEST("loginRequest", "登录请求"), + HEART_BEAT_REQUEST("heartBeatRequest", "心跳请求"), + SESSION_CLOSE_EVENT("sessionCloseEvent", "会话关闭事件"), + VERIFY_PRICING_REQUEST("verifyPricingRequest", "验证定价请求"), + QUERY_PRICING_REQUEST("queryPricingRequest", "查询定价请求"), + GUN_RUN_STATUS("gunRunStatus", "充电枪运行状态"), + CHARGING_PROGRESS("chargingProgress", "充电进度"), + SET_PRICING_RESPONSE("setPricingResponse", "设置定价响应"), + REMOTE_START_CHARGING_RESPONSE("remoteStartChargingResponse", "远程启动充电响应"), + REMOTE_STOP_CHARGING_RESPONSE("remoteStopChargingResponse", "远程停止充电响应"), + TRANSACTION_RECORD_REQUEST("transactionRecordRequest", "交易记录请求"), + BMS_CHARGING_ERROR("bmsChargingError", "BMS充电错误"), + BMS_PARAM_CONFIG_REPORT("bmsParamConfigReport", "BMS参数配置报告"), + BMS_CHARGING_INFO("bmsChargingInfo", "BMS充电信息"), + BMS_ABORT("bmsAbort", "BMS中止"), + RESTART_PILE_RESPONSE("restartPileResponse", "重启充电桩响应"), + BMS_HANDSHAKE("bmsHandshake", "BMS握手"), + OTA_RESPONSE("otaResponse", "OTA响应"), + GROUND_LOCK_STATUS("groundLockStatus", "地锁状态"), + OFFLINE_CARD_BALANCE_UPDATE_RESPONSE("offlineCardBalanceUpdateResponse", "离线卡余额更新响应"), + OFFLINE_CARD_SYNC_RESPONSE("offlineCardSyncResponse", "离线卡同步响应"), + TIME_SYNC_RESPONSE("timeSyncResponse", "时间同步响应"), + UNKNOWN("unknown", "未知类型"); + + private final String timerKey; + private final String description; + + AppConsumerPhaseEnum(String timerKey, String description) { + this.timerKey = timerKey; + this.description = description; + } + + } + private final StatsCounter totalCounter; private final StatsCounter loginCounter; private final StatsCounter heartBeatCounter; private final StatsCounter gunRunStatusCounter; private final StatsCounter chargingProgressCounter; private final StatsCounter transactionRecordCounter; + private final Timer appConsumerTimer; + private final Map appConsumerTimerMap = new HashMap<>(); + + // Timer统计配置参数 + private final int timerTopN; + private final List counters = new ArrayList<>(); - public AppConsumerStats(StatsFactory statsFactory) { - String statsKey = "appConsumer"; + public AppConsumerStats(StatsFactory statsFactory, int timerTopN) { + this.timerTopN = timerTopN; + this.totalCounter = register(statsFactory.createStatsCounter(STATS_KEY_APP_CONSUMER, TOTAL_MSGS)); + this.loginCounter = register(statsFactory.createStatsCounter(STATS_KEY_APP_CONSUMER, LOGIN_EVENTS)); + this.heartBeatCounter = register(statsFactory.createStatsCounter(STATS_KEY_APP_CONSUMER, HEARTBEAT_EVENTS)); + this.gunRunStatusCounter = register(statsFactory.createStatsCounter(STATS_KEY_APP_CONSUMER, GUN_RUN_STATUS_EVENTS)); + this.chargingProgressCounter = register(statsFactory.createStatsCounter(STATS_KEY_APP_CONSUMER, CHARGING_PROGRESS_EVENTS)); + this.transactionRecordCounter = register(statsFactory.createStatsCounter(STATS_KEY_APP_CONSUMER, TRANSACTION_RECORD_EVENTS)); - this.totalCounter = register(statsFactory.createStatsCounter(statsKey, TOTAL_MSGS)); - this.loginCounter = register(statsFactory.createStatsCounter(statsKey, LOGIN_EVENTS)); - this.heartBeatCounter = register(statsFactory.createStatsCounter(statsKey, HEARTBEAT_EVENTS)); - this.gunRunStatusCounter = register(statsFactory.createStatsCounter(statsKey, GUN_RUN_STATUS_EVENTS)); - this.chargingProgressCounter = register(statsFactory.createStatsCounter(statsKey, CHARGING_PROGRESS_EVENTS)); - this.transactionRecordCounter = register(statsFactory.createStatsCounter(statsKey, TRANSACTION_RECORD_EVENTS)); - this.appConsumerTimer = statsFactory.createTimer(statsKey); + // 初始化通用消费计时器 + this.appConsumerTimer = statsFactory.createTimer(STATS_KEY_APP_CONSUMER); + + // 初始化各消息类型的计时器映射(排除CONSUME类型) + for (AppConsumerPhaseEnum phase : AppConsumerPhaseEnum.values()) { + this.appConsumerTimerMap.put(phase.getTimerKey(), statsFactory.createTimer(STATS_KEY_APP_CONSUMER_MSGS, TIMER_TAG_MSG_TYPE, phase.getTimerKey())); + } } private StatsCounter register(StatsCounter counter) { @@ -70,6 +128,77 @@ public class AppConsumerStats { appConsumerTimer.record(Duration.ofMillis(System.currentTimeMillis() - TracerContextUtil.getCurrentTracer().getTracerTs())); } + /** + * 根据消息类型记录处理耗时 + * + * @param msg 上行队列消息 + */ + public void msgTimer(UplinkQueueMessage msg) { + AppConsumerPhaseEnum phase = getPhaseByMessage(msg); + + Timer timer = appConsumerTimerMap.get(phase.getTimerKey()); + + if (timer != null) { + timer.record(Duration.ofMillis(System.currentTimeMillis() - TracerContextUtil.getCurrentTracer().getTracerTs())); + } + } + + /** + * 根据消息内容判断消息类型阶段 + * + * @param msg 上行队列消息 + * @return 对应的消息处理阶段枚举 + */ + private AppConsumerPhaseEnum getPhaseByMessage(UplinkQueueMessage msg) { + if (msg.hasLoginRequest()) { + return AppConsumerPhaseEnum.LOGIN_REQUEST; + } else if (msg.hasHeartBeatRequest()) { + return AppConsumerPhaseEnum.HEART_BEAT_REQUEST; + } else if (msg.hasSessionCloseEventProto()) { + return AppConsumerPhaseEnum.SESSION_CLOSE_EVENT; + } else if (msg.hasVerifyPricingRequest()) { + return AppConsumerPhaseEnum.VERIFY_PRICING_REQUEST; + } else if (msg.hasQueryPricingRequest()) { + return AppConsumerPhaseEnum.QUERY_PRICING_REQUEST; + } else if (msg.hasGunRunStatusProto()) { + return AppConsumerPhaseEnum.GUN_RUN_STATUS; + } else if (msg.hasChargingProgressProto()) { + return AppConsumerPhaseEnum.CHARGING_PROGRESS; + } else if (msg.hasSetPricingResponse()) { + return AppConsumerPhaseEnum.SET_PRICING_RESPONSE; + } else if (msg.hasRemoteStartChargingResponse()) { + return AppConsumerPhaseEnum.REMOTE_START_CHARGING_RESPONSE; + } else if (msg.hasRemoteStopChargingResponse()) { + return AppConsumerPhaseEnum.REMOTE_STOP_CHARGING_RESPONSE; + } else if (msg.hasTransactionRecordRequest()) { + return AppConsumerPhaseEnum.TRANSACTION_RECORD_REQUEST; + } else if (msg.hasBmsChargingErrorProto()) { + return AppConsumerPhaseEnum.BMS_CHARGING_ERROR; + } else if (msg.hasBmsParamConfigReportProto()) { + return AppConsumerPhaseEnum.BMS_PARAM_CONFIG_REPORT; + } else if (msg.hasBmsChargingInfoProto()) { + return AppConsumerPhaseEnum.BMS_CHARGING_INFO; + } else if (msg.hasBmsAbortProto()) { + return AppConsumerPhaseEnum.BMS_ABORT; + } else if (msg.hasRestartPileResponse()) { + return AppConsumerPhaseEnum.RESTART_PILE_RESPONSE; + } else if (msg.hasBmsHandshakeProto()) { + return AppConsumerPhaseEnum.BMS_HANDSHAKE; + } else if (msg.hasOtaResponse()) { + return AppConsumerPhaseEnum.OTA_RESPONSE; + } else if (msg.hasGroundLockStatusProto()) { + return AppConsumerPhaseEnum.GROUND_LOCK_STATUS; + } else if (msg.hasOfflineCardBalanceUpdateResponse()) { + return AppConsumerPhaseEnum.OFFLINE_CARD_BALANCE_UPDATE_RESPONSE; + } else if (msg.hasOfflineCardSyncResponse()) { + return AppConsumerPhaseEnum.OFFLINE_CARD_SYNC_RESPONSE; + } else if (msg.hasTimeSyncResponse()) { + return AppConsumerPhaseEnum.TIME_SYNC_RESPONSE; + } else { + return AppConsumerPhaseEnum.UNKNOWN; + } + } + public void printStats() { int total = totalCounter.get(); if (total > 0) { @@ -78,6 +207,45 @@ public class AppConsumerStats { stats.append(counter.getName()).append(" = [").append(counter.get()).append("] "); }); log.info("App Queue Consumer Stats: {}", stats); + + printTimerStats(); + } + } + + private void printTimerStats() { + if (appConsumerTimerMap.isEmpty() || timerTopN <= 0) { + return; + } + + // 获取耗时前N的消息类型 + List> topNTimers = appConsumerTimerMap.entrySet().stream() + .filter(entry -> entry.getValue().count() > 0) + .sorted(Map.Entry.comparingByValue( + Comparator.comparingDouble(timer -> timer.mean(TimeUnit.MILLISECONDS)) + ).reversed()) + .limit(timerTopN) + .toList(); + + if (!topNTimers.isEmpty()) { + StringBuilder timerStats = new StringBuilder(String.format("App Consumer Timer Stats (Top %d by avg time): ", timerTopN)); + + for (int i = 0; i < topNTimers.size(); i++) { + Map.Entry entry = topNTimers.get(i); + Timer timer = entry.getValue(); + + timerStats.append(String.format("%s[count=%d, avg=%.2fms, max=%.2fms]", + entry.getKey(), + timer.count(), + timer.mean(TimeUnit.MILLISECONDS), + timer.max(TimeUnit.MILLISECONDS) + )); + + if (i < topNTimers.size() - 1) { + timerStats.append(", "); + } + } + + log.info("{}", timerStats); } } diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/QueueEvent.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/QueueEvent.java index fc98bbf..ae3ffb0 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/QueueEvent.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/QueueEvent.java @@ -6,9 +6,7 @@ */ package sanbing.jcpp.app.service.queue; -import java.io.Serializable; - -public enum QueueEvent implements Serializable { +public enum QueueEvent { PARTITION_CHANGE, CONFIG_UPDATE, DELETE diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/consumer/ProtocolUplinkConsumerService.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/consumer/ProtocolUplinkConsumerService.java index 41b8244..763e5f9 100644 --- a/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/consumer/ProtocolUplinkConsumerService.java +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/queue/consumer/ProtocolUplinkConsumerService.java @@ -14,7 +14,6 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import sanbing.jcpp.app.service.PileProtocolService; @@ -46,12 +45,12 @@ import static sanbing.jcpp.infrastructure.queue.common.QueueConstants.*; /** - * @author baigod + * @author 九筒 */ @Service @AppComponent @Slf4j -public class ProtocolUplinkConsumerService extends AbstractConsumerService implements ApplicationListener { +public class ProtocolUplinkConsumerService extends AbstractConsumerService { @Value("${queue.app.poll-interval}") private int pollInterval; @@ -61,6 +60,8 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService imple private boolean consumerPerPartition; @Value("${queue.app.stats.enabled}") private boolean statsEnabled; + @Value("${queue.app.stats.timer-top-n}") + private int timerTopN; private final PileProtocolService pileProtocolService; @@ -68,22 +69,26 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService imple private AppQueueConsumerManager, AppQueueConfig> appConsumer; - private final AppConsumerStats stats; + private final StatsFactory statsFactory; + + private AppConsumerStats stats; public ProtocolUplinkConsumerService(PartitionProvider partitionProvider, ApplicationEventPublisher eventPublisher, PileProtocolService pileProtocolService, AppQueueFactory appQueueFactory, StatsFactory statsFactory) { - super(partitionProvider, eventPublisher); + super(); this.pileProtocolService = pileProtocolService; this.appQueueFactory = appQueueFactory; - this.stats = new AppConsumerStats(statsFactory); + this.statsFactory = statsFactory; } @PostConstruct public void init() { super.init("jcpp-app"); + + this.stats = new AppConsumerStats(statsFactory, timerTopN); log.info("Initializing Protocol Uplink Messages Queue Subscriptions."); @@ -143,9 +148,9 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService imple Callback callback = new PackCallback<>(id, ctx); - try { - UplinkQueueMessage uplinkQueueMsg = msg.getValue(); + UplinkQueueMessage uplinkQueueMsg = msg.getValue(); + try { if (statsEnabled) { stats.log(uplinkQueueMsg); } @@ -160,6 +165,10 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService imple pileProtocolService.heartBeat(uplinkQueueMsg, callback); + } else if (uplinkQueueMsg.hasSessionCloseEventProto()) { + + pileProtocolService.onSessionCloseEvent(uplinkQueueMsg, callback); + } else if (uplinkQueueMsg.hasVerifyPricingRequest()) { pileProtocolService.verifyPricing(uplinkQueueMsg, callback); @@ -236,7 +245,11 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService imple pileProtocolService.onTimeSyncResponse(uplinkQueueMsg, callback); - } else { + } else if (uplinkQueueMsg.hasBmsDemandChargerOutputProto()) { + + pileProtocolService.postBmsDemandChargerOutput(uplinkQueueMsg, callback); + + }else { callback.onSuccess(); } @@ -246,6 +259,13 @@ public class ProtocolUplinkConsumerService extends AbstractConsumerService imple log.warn("[{}] Failed to process message: {}", id, msg, e); callback.onFailure(e); + + } finally { + + if (statsEnabled) { + + stats.msgTimer(uplinkQueueMsg); + } } })) ); diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/SecurityConfiguration.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/SecurityConfiguration.java new file mode 100644 index 0000000..9937bfc --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/SecurityConfiguration.java @@ -0,0 +1,182 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security; + +import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.header.writers.StaticHeadersWriter; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import sanbing.jcpp.app.adapter.config.MvcCorsProperties; +import sanbing.jcpp.app.exception.JCPPErrorResponseHandler; +import sanbing.jcpp.app.service.security.auth.AuthExceptionHandler; +import sanbing.jcpp.app.service.security.auth.jwt.*; +import sanbing.jcpp.app.service.security.auth.jwt.extractor.TokenExtractor; +import sanbing.jcpp.app.service.security.auth.rest.RestAuthenticationProvider; +import sanbing.jcpp.app.service.security.auth.rest.RestLoginProcessingFilter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +@Order(SecurityProperties.BASIC_AUTH_ORDER) +public class SecurityConfiguration { + + public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; + public static final String JWT_TOKEN_HEADER_PARAM_V2 = "Authorization"; + public static final String JWT_TOKEN_QUERY_PARAM = "token"; + + public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; + public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; + protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[]{"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/api/license/**","/test/**"}; + public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; + public static final String WS_ENTRY_POINT = "/api/ws/**"; + + @Resource + private JCPPErrorResponseHandler restAccessDeniedHandler; + + + @Resource + @Qualifier("defaultAuthenticationSuccessHandler") + private AuthenticationSuccessHandler successHandler; + + @Resource + @Qualifier("defaultAuthenticationFailureHandler") + private AuthenticationFailureHandler failureHandler; + + @Resource + private RestAuthenticationProvider restAuthenticationProvider; + @Resource + private JwtAuthenticationProvider jwtAuthenticationProvider; + @Resource + private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider; + + @Resource + @Qualifier("jwtHeaderTokenExtractor") + private TokenExtractor jwtHeaderTokenExtractor; + + @Resource + private AuthenticationManager authenticationManager; + + @Resource + private AuthExceptionHandler authExceptionHandler; + + @Bean + protected RestLoginProcessingFilter buildRestLoginProcessingFilter() { + RestLoginProcessingFilter filter = new RestLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler); + filter.setAuthenticationManager(this.authenticationManager); + return filter; + } + + protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() { + List pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)); + pathsToSkip.addAll(Arrays.asList(WS_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT)); + SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT); + JwtTokenAuthenticationProcessingFilter filter + = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher); + filter.setAuthenticationManager(this.authenticationManager); + return filter; + } + + @Bean + protected RefreshTokenProcessingFilter buildRefreshTokenProcessingFilter() throws Exception { + RefreshTokenProcessingFilter filter = new RefreshTokenProcessingFilter(TOKEN_REFRESH_ENTRY_POINT, successHandler, failureHandler); + filter.setAuthenticationManager(this.authenticationManager); + return filter; + } + + @Bean + public AuthenticationManager authenticationManager() { + return new ProviderManager(List.of( + restAuthenticationProvider, + jwtAuthenticationProvider, + refreshTokenAuthenticationProvider + )); + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + @Order(0) + SecurityFilterChain resources(HttpSecurity http) throws Exception { + http + .securityMatchers(matchers -> matchers + .requestMatchers("/*.js", "/*.css", "/*.ico", "/assets/**", "/static/**")) + .headers(header -> header + .defaultsDisabled() + .addHeaderWriter(new StaticHeadersWriter(HttpHeaders.CACHE_CONTROL, "max-age=0, public"))) + .authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll()) + .requestCache(RequestCacheConfigurer::disable) + .securityContext(AbstractHttpConfigurer::disable) + .sessionManagement(AbstractHttpConfigurer::disable); + return http.build(); + } + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.headers(headers -> headers + .cacheControl(config -> {}) + .frameOptions(config -> {}).disable()) + .cors(cors -> {}) + .csrf(AbstractHttpConfigurer::disable) + .exceptionHandling(config -> {}) + .sessionManagement(config -> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(config -> config + .requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points (webjars included) + .requestMatchers( + FORM_BASED_LOGIN_ENTRY_POINT, // Login end-point + TOKEN_REFRESH_ENTRY_POINT, // Token refresh end-point + WS_ENTRY_POINT).permitAll() // Protected WebSocket API End-points + .requestMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points + .anyRequest().permitAll()) + .exceptionHandling(config -> config.accessDeniedHandler(restAccessDeniedHandler)) + .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(authExceptionHandler, buildRestLoginProcessingFilter().getClass()); + + return http.build(); + } + + + @Bean + @ConditionalOnMissingBean(CorsFilter.class) + public CorsFilter corsFilter(MvcCorsProperties mvcCorsProperties) { + if (mvcCorsProperties.getMappings().isEmpty()) { + return new CorsFilter(new UrlBasedCorsConfigurationSource()); + } else { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.setCorsConfigurations(mvcCorsProperties.getMappings()); + return new CorsFilter(source); + } + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/AbstractJwtAuthenticationToken.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/AbstractJwtAuthenticationToken.java new file mode 100644 index 0000000..c73775d --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/AbstractJwtAuthenticationToken.java @@ -0,0 +1,57 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import sanbing.jcpp.app.service.security.model.SecurityUser; +import sanbing.jcpp.app.service.security.model.token.RawAccessJwtToken; + +public abstract class AbstractJwtAuthenticationToken extends AbstractAuthenticationToken { + + private static final long serialVersionUID = -6212297506742428406L; + + private RawAccessJwtToken rawAccessToken; + private SecurityUser securityUser; + + public AbstractJwtAuthenticationToken(RawAccessJwtToken unsafeToken) { + super(null); + this.rawAccessToken = unsafeToken; + this.setAuthenticated(false); + } + + public AbstractJwtAuthenticationToken(SecurityUser securityUser) { + super(securityUser.getAuthorities()); + this.eraseCredentials(); + this.securityUser = securityUser; + super.setAuthenticated(true); + } + + @Override + public void setAuthenticated(boolean authenticated) { + if (authenticated) { + throw new IllegalArgumentException( + "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); + } + super.setAuthenticated(false); + } + + @Override + public Object getCredentials() { + return rawAccessToken; + } + + @Override + public Object getPrincipal() { + return this.securityUser; + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + this.rawAccessToken = null; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/AuthExceptionHandler.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/AuthExceptionHandler.java new file mode 100644 index 0000000..5a72fba --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/AuthExceptionHandler.java @@ -0,0 +1,37 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import sanbing.jcpp.app.exception.JCPPErrorResponseHandler; + +@Component +@RequiredArgsConstructor +@Slf4j +public class AuthExceptionHandler extends OncePerRequestFilter { + + private final JCPPErrorResponseHandler errorResponseHandler; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { + try { + filterChain.doFilter(request, response); + } catch (AuthenticationException e) { + throw e; + } catch (Exception e) { + errorResponseHandler.handle(e, response); + } + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/JwtAuthenticationToken.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/JwtAuthenticationToken.java new file mode 100644 index 0000000..81e1f8b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/JwtAuthenticationToken.java @@ -0,0 +1,24 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth; + + +import sanbing.jcpp.app.service.security.model.SecurityUser; +import sanbing.jcpp.app.service.security.model.token.RawAccessJwtToken; + +public class JwtAuthenticationToken extends AbstractJwtAuthenticationToken { + + private static final long serialVersionUID = -8487219769037942225L; + + public JwtAuthenticationToken(RawAccessJwtToken unsafeToken) { + super(unsafeToken); + } + + public JwtAuthenticationToken(SecurityUser securityUser) { + super(securityUser); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/RefreshAuthenticationToken.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/RefreshAuthenticationToken.java new file mode 100644 index 0000000..60779db --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/RefreshAuthenticationToken.java @@ -0,0 +1,24 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth; + + +import sanbing.jcpp.app.service.security.model.SecurityUser; +import sanbing.jcpp.app.service.security.model.token.RawAccessJwtToken; + +public class RefreshAuthenticationToken extends AbstractJwtAuthenticationToken { + + private static final long serialVersionUID = -1311042791508924523L; + + public RefreshAuthenticationToken(RawAccessJwtToken unsafeToken) { + super(unsafeToken); + } + + public RefreshAuthenticationToken(SecurityUser securityUser) { + super(securityUser); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/JwtAuthenticationProvider.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/JwtAuthenticationProvider.java new file mode 100644 index 0000000..c44c1d3 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/JwtAuthenticationProvider.java @@ -0,0 +1,46 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.jwt; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Component; +import sanbing.jcpp.app.service.security.auth.JwtAuthenticationToken; +import sanbing.jcpp.app.service.security.model.SecurityUser; +import sanbing.jcpp.app.service.security.model.token.JwtTokenFactory; +import sanbing.jcpp.app.service.security.model.token.RawAccessJwtToken; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationProvider implements AuthenticationProvider { + + private final JwtTokenFactory tokenFactory; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); + SecurityUser securityUser = authenticate(rawAccessToken.token()); + return new JwtAuthenticationToken(securityUser); + } + + public SecurityUser authenticate(String accessToken) throws AuthenticationException { + if (StringUtils.isEmpty(accessToken)) { + throw new BadCredentialsException("Token is invalid"); + } + + return tokenFactory.parseAccessJwtToken(accessToken); + } + + @Override + public boolean supports(Class authentication) { + return (JwtAuthenticationToken.class.isAssignableFrom(authentication)); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java new file mode 100644 index 0000000..c0527e0 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/JwtTokenAuthenticationProcessingFilter.java @@ -0,0 +1,59 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.util.matcher.RequestMatcher; +import sanbing.jcpp.app.service.security.auth.JwtAuthenticationToken; +import sanbing.jcpp.app.service.security.auth.jwt.extractor.TokenExtractor; +import sanbing.jcpp.app.service.security.model.token.RawAccessJwtToken; + +import java.io.IOException; + +public class JwtTokenAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { + private final AuthenticationFailureHandler failureHandler; + private final TokenExtractor tokenExtractor; + + public JwtTokenAuthenticationProcessingFilter(AuthenticationFailureHandler failureHandler, + TokenExtractor tokenExtractor, RequestMatcher matcher) { + super(matcher); + this.failureHandler = failureHandler; + this.tokenExtractor = tokenExtractor; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException, IOException, ServletException { + RawAccessJwtToken token = new RawAccessJwtToken(tokenExtractor.extract(request)); + return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token)); + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, + Authentication authResult) throws IOException, ServletException { + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(authResult); + SecurityContextHolder.setContext(context); + chain.doFilter(request, response); + } + + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, + AuthenticationException failed) throws IOException, ServletException { + SecurityContextHolder.clearContext(); + failureHandler.onAuthenticationFailure(request, response, failed); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java new file mode 100644 index 0000000..3003364 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java @@ -0,0 +1,59 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.jwt; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import sanbing.jcpp.app.dal.entity.User; +import sanbing.jcpp.app.service.UserService; +import sanbing.jcpp.app.service.security.auth.RefreshAuthenticationToken; +import sanbing.jcpp.app.service.security.model.SecurityUser; +import sanbing.jcpp.app.service.security.model.UserPrincipal; +import sanbing.jcpp.app.service.security.model.token.JwtTokenFactory; +import sanbing.jcpp.app.service.security.model.token.RawAccessJwtToken; + +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class RefreshTokenAuthenticationProvider implements AuthenticationProvider { + private final JwtTokenFactory tokenFactory; + private final UserService userService; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Assert.notNull(authentication, "No authentication data provided"); + RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); + SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken.token()); + + SecurityUser securityUser = authenticateByUserId(unsafeUser.getId()); + securityUser.setSessionId(unsafeUser.getSessionId()); + return new RefreshAuthenticationToken(securityUser); + } + + private SecurityUser authenticateByUserId(UUID userId) { + User user = userService.findById(userId); + if (user == null) { + throw new UsernameNotFoundException("User not found by refresh token"); + } + + UserPrincipal userPrincipal = new UserPrincipal(user.getUserName()); + + return new SecurityUser(user, userPrincipal); + } + + + @Override + public boolean supports(Class authentication) { + return (RefreshAuthenticationToken.class.isAssignableFrom(authentication)); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenProcessingFilter.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenProcessingFilter.java new file mode 100644 index 0000000..961fe4e --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenProcessingFilter.java @@ -0,0 +1,82 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import sanbing.jcpp.app.service.security.auth.RefreshAuthenticationToken; +import sanbing.jcpp.app.service.security.exception.AuthMethodNotSupportedException; +import sanbing.jcpp.app.service.security.model.token.RawAccessJwtToken; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +import java.io.IOException; + +@Slf4j +public class RefreshTokenProcessingFilter extends AbstractAuthenticationProcessingFilter { + + private final AuthenticationSuccessHandler successHandler; + private final AuthenticationFailureHandler failureHandler; + + + public RefreshTokenProcessingFilter(String defaultProcessUrl, AuthenticationSuccessHandler successHandler, + AuthenticationFailureHandler failureHandler) { + super(defaultProcessUrl); + this.successHandler = successHandler; + this.failureHandler = failureHandler; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException, IOException, ServletException { + if (!HttpMethod.POST.name().equals(request.getMethod())) { + if(log.isDebugEnabled()) { + log.debug("Authentication method not supported. Request method: {}", request.getMethod()); + } + throw new AuthMethodNotSupportedException("Authentication method not supported"); + } + + RefreshTokenRequest refreshTokenRequest; + try { + refreshTokenRequest = JacksonUtil.fromReader(request.getReader(), RefreshTokenRequest.class); + } catch (Exception e) { + throw new AuthenticationServiceException("Invalid refresh token request payload"); + } + + if (StringUtils.isBlank(refreshTokenRequest.refreshToken())) { + throw new AuthenticationServiceException("Refresh token is not provided"); + } + + RawAccessJwtToken token = new RawAccessJwtToken(refreshTokenRequest.refreshToken()); + + return this.getAuthenticationManager().authenticate(new RefreshAuthenticationToken(token)); + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, + Authentication authResult) throws IOException, ServletException { + successHandler.onAuthenticationSuccess(request, response, authResult); + } + + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, + AuthenticationException failed) throws IOException, ServletException { + SecurityContextHolder.clearContext(); + failureHandler.onAuthenticationFailure(request, response, failed); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenRequest.java new file mode 100644 index 0000000..24e1f17 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/RefreshTokenRequest.java @@ -0,0 +1,18 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.jwt; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public record RefreshTokenRequest(String refreshToken) { + @JsonCreator + public RefreshTokenRequest(@JsonProperty("refreshToken") String refreshToken) { + this.refreshToken = refreshToken; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/SkipPathRequestMatcher.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/SkipPathRequestMatcher.java new file mode 100644 index 0000000..975f81b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/SkipPathRequestMatcher.java @@ -0,0 +1,36 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.jwt; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.stream.Collectors; + +public class SkipPathRequestMatcher implements RequestMatcher { + private final OrRequestMatcher matchers; + private final RequestMatcher processingMatcher; + + public SkipPathRequestMatcher(List pathsToSkip, String processingPath) { + Assert.notNull(pathsToSkip, "List of paths to skip is required."); + List m = pathsToSkip.stream().map(path -> PathPatternRequestMatcher.withDefaults().matcher(path)).collect(Collectors.toList()); + matchers = new OrRequestMatcher(m); + processingMatcher = PathPatternRequestMatcher.withDefaults().matcher(processingPath); + } + + @Override + public boolean matches(HttpServletRequest request) { + if (matchers.matches(request)) { + return false; + } + return processingMatcher.matches(request); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java new file mode 100644 index 0000000..f293c9b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java @@ -0,0 +1,35 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.jwt.extractor; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.stereotype.Component; +import sanbing.jcpp.app.service.security.SecurityConfiguration; + +@Component(value="jwtHeaderTokenExtractor") +public class JwtHeaderTokenExtractor implements TokenExtractor { + public static final String HEADER_PREFIX = "Bearer "; + + @Override + public String extract(HttpServletRequest request) { + String header = request.getHeader(SecurityConfiguration.JWT_TOKEN_HEADER_PARAM); + if (StringUtils.isBlank(header)) { + header = request.getHeader(SecurityConfiguration.JWT_TOKEN_HEADER_PARAM_V2); + if (StringUtils.isBlank(header)) { + throw new AuthenticationServiceException("Authorization header cannot be blank!"); + } + } + + if (header.length() < HEADER_PREFIX.length()) { + throw new AuthenticationServiceException("Invalid authorization header size."); + } + + return header.substring(HEADER_PREFIX.length()); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java new file mode 100644 index 0000000..05a4689 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java @@ -0,0 +1,33 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.jwt.extractor; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.stereotype.Component; +import sanbing.jcpp.app.service.security.SecurityConfiguration; + +@Component(value="jwtQueryTokenExtractor") +public class JwtQueryTokenExtractor implements TokenExtractor { + + @Override + public String extract(HttpServletRequest request) { + String token = null; + if (request.getParameterMap() != null && !request.getParameterMap().isEmpty()) { + String[] tokenParamValue = request.getParameterMap().get(SecurityConfiguration.JWT_TOKEN_QUERY_PARAM); + if (tokenParamValue != null && tokenParamValue.length == 1) { + token = tokenParamValue[0]; + } + } + if (StringUtils.isBlank(token)) { + throw new AuthenticationServiceException("Authorization query parameter cannot be blank!"); + } + + return token; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/TokenExtractor.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/TokenExtractor.java new file mode 100644 index 0000000..8d4fdfc --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/jwt/extractor/TokenExtractor.java @@ -0,0 +1,13 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.jwt.extractor; + +import jakarta.servlet.http.HttpServletRequest; + +public interface TokenExtractor { + String extract(HttpServletRequest request); +} \ No newline at end of file diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/LoginRequest.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/LoginRequest.java new file mode 100644 index 0000000..6052494 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/LoginRequest.java @@ -0,0 +1,19 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.rest; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public record LoginRequest(String username, String password) { + + @JsonCreator + public LoginRequest(@JsonProperty("username") String username, @JsonProperty("password") String password) { + this.username = username; + this.password = password; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationDetails.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationDetails.java new file mode 100644 index 0000000..68263f5 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationDetails.java @@ -0,0 +1,39 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.rest; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.Data; +import ua_parser.Client; +import ua_parser.Parser; + +import java.io.Serializable; + +@Data +public class RestAuthenticationDetails implements Serializable { + + private final String clientAddress; + private final Client userAgent; + + public RestAuthenticationDetails(HttpServletRequest request) { + this.clientAddress = getClientIP(request); + this.userAgent = getUserAgent(request); + } + + private static String getClientIP(HttpServletRequest request) { + String xfHeader = request.getHeader("X-Forwarded-For"); + if (xfHeader == null) { + return request.getRemoteAddr(); + } + return xfHeader.split(",")[0]; + } + + private static Client getUserAgent(HttpServletRequest request) { + Parser uaParser = new Parser(); + return uaParser.parse(request.getHeader("User-Agent")); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationDetailsSource.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationDetailsSource.java new file mode 100644 index 0000000..df20839 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationDetailsSource.java @@ -0,0 +1,18 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.rest; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.security.authentication.AuthenticationDetailsSource; + +public class RestAuthenticationDetailsSource implements + AuthenticationDetailsSource { + + public RestAuthenticationDetails buildDetails(HttpServletRequest context) { + return new RestAuthenticationDetails(context); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationProvider.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationProvider.java new file mode 100644 index 0000000..efd37ba --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAuthenticationProvider.java @@ -0,0 +1,85 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.rest; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import sanbing.jcpp.app.dal.entity.User; +import sanbing.jcpp.app.service.UserService; +import sanbing.jcpp.app.service.security.model.SecurityUser; +import sanbing.jcpp.app.service.security.model.UserCredentials; +import sanbing.jcpp.app.service.security.model.UserPrincipal; + + +@Component +@Slf4j +public class RestAuthenticationProvider implements AuthenticationProvider { + + private final UserService userService; + + @Autowired + public RestAuthenticationProvider(final UserService userService) { + this.userService = userService; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Assert.notNull(authentication, "No authentication data provided"); + + Object principal = authentication.getPrincipal(); + if (!(principal instanceof UserPrincipal userPrincipal)) { + throw new BadCredentialsException("Authentication Failed. Bad user principal."); + } + + String username = userPrincipal.value(); + String password = (String) authentication.getCredentials(); + + SecurityUser securityUser = authenticateByUsernameAndPassword(userPrincipal, username, password); + + return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities()); + } + + private SecurityUser authenticateByUsernameAndPassword(UserPrincipal userPrincipal, String username, String password) { + User user = userService.findUserByUsername(username); + if (user == null) { + throw new UsernameNotFoundException("User not found: " + username); + } + + try { + + UserCredentials userCredentials = user.getUserCredentials(); + if (userCredentials == null) { + throw new UsernameNotFoundException("User credentials not found"); + } + + try { + userService.validateUserCredentials(user.getId(), userCredentials, username, password); + } catch (LockedException e) { + throw e; + } + + if (user.getAuthority() == null) + throw new InsufficientAuthenticationException("User has no authority assigned"); + + return new SecurityUser(user, userCredentials.isEnabled(), userPrincipal); + } catch (Exception e) { + throw e; + } + } + + @Override + public boolean supports(Class authentication) { + return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java new file mode 100644 index 0000000..53fc45c --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAwareAuthenticationFailureHandler.java @@ -0,0 +1,35 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.rest; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; +import sanbing.jcpp.app.exception.JCPPErrorResponseHandler; + +import java.io.IOException; + +@Component(value = "defaultAuthenticationFailureHandler") +public class RestAwareAuthenticationFailureHandler implements AuthenticationFailureHandler { + + private final JCPPErrorResponseHandler errorResponseHandler; + + @Autowired + public RestAwareAuthenticationFailureHandler(JCPPErrorResponseHandler errorResponseHandler) { + this.errorResponseHandler = errorResponseHandler; + } + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException e) throws IOException, ServletException { + errorResponseHandler.handle(e, response); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java new file mode 100644 index 0000000..eb871dd --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java @@ -0,0 +1,56 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.rest; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.WebAttributes; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import sanbing.jcpp.app.service.security.model.JwtPair; +import sanbing.jcpp.app.service.security.model.SecurityUser; +import sanbing.jcpp.app.service.security.model.token.JwtTokenFactory; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +import java.io.IOException; + +@Component(value = "defaultAuthenticationSuccessHandler") +@RequiredArgsConstructor +public class RestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + private final JwtTokenFactory tokenFactory; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + SecurityUser securityUser = (SecurityUser) authentication.getPrincipal(); + + JwtPair tokenPair = tokenFactory.createTokenPair(securityUser); + + response.setStatus(HttpStatus.OK.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + JacksonUtil.writeValue(response.getWriter(), tokenPair); + + clearAuthenticationAttributes(request); + } + + + protected final void clearAuthenticationAttributes(HttpServletRequest request) { + HttpSession session = request.getSession(false); + + if (session == null) { + return; + } + + session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestLoginProcessingFilter.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestLoginProcessingFilter.java new file mode 100644 index 0000000..953365b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/auth/rest/RestLoginProcessingFilter.java @@ -0,0 +1,87 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.auth.rest; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import sanbing.jcpp.app.service.security.exception.AuthMethodNotSupportedException; +import sanbing.jcpp.app.service.security.model.UserPrincipal; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; + +import java.io.IOException; + +@Slf4j +public class RestLoginProcessingFilter extends AbstractAuthenticationProcessingFilter { + + private final AuthenticationDetailsSource authenticationDetailsSource = new RestAuthenticationDetailsSource(); + + private final AuthenticationSuccessHandler successHandler; + private final AuthenticationFailureHandler failureHandler; + + + public RestLoginProcessingFilter(String defaultProcessUrl, AuthenticationSuccessHandler successHandler, + AuthenticationFailureHandler failureHandler) { + super(defaultProcessUrl); + this.successHandler = successHandler; + this.failureHandler = failureHandler; + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException, IOException, ServletException { + if (!HttpMethod.POST.name().equals(request.getMethod())) { + if(log.isDebugEnabled()) { + log.debug("Authentication method not supported. Request method: {}", request.getMethod()); + } + throw new AuthMethodNotSupportedException("Authentication method not supported"); + } + + LoginRequest loginRequest; + try { + loginRequest = JacksonUtil.fromReader(request.getReader(), LoginRequest.class); + } catch (Exception e) { + throw new AuthenticationServiceException("Invalid login request payload"); + } + + if (StringUtils.isBlank(loginRequest.username()) || StringUtils.isEmpty(loginRequest.password())) { + throw new AuthenticationServiceException("Username or Password not provided"); + } + + UserPrincipal principal = new UserPrincipal( loginRequest.username()); + + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, loginRequest.password()); + token.setDetails(authenticationDetailsSource.buildDetails(request)); + return this.getAuthenticationManager().authenticate(token); + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, + Authentication authResult) throws IOException, ServletException { + successHandler.onAuthenticationSuccess(request, response, authResult); + } + + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, + AuthenticationException failed) throws IOException, ServletException { + SecurityContextHolder.clearContext(); + failureHandler.onAuthenticationFailure(request, response, failed); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/config/SecurityProperties.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/config/SecurityProperties.java new file mode 100644 index 0000000..1b8b3e0 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/config/SecurityProperties.java @@ -0,0 +1,141 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.io.Serializable; + +/** + * 统一的安全配置属性 + * 包含安全设置和JWT设置 + * + * @author 九筒 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "security") +public class SecurityProperties { + + /** + * 安全设置 + */ + private SecuritySettingsProperties settings; + + /** + * JWT设置 + */ + private JwtProperties jwt; + + /** + * 安全设置属性 + */ + @Data + public static class SecuritySettingsProperties { + + /** + * 密码策略配置 + */ + private PasswordPolicyProperties passwordPolicy; + + /** + * 最大登录失败次数 + */ + private Integer maxFailedLoginAttempts; + + /** + * 用户锁定通知邮箱 + */ + private String userLockoutNotificationEmail; + + /** + * 移动端密钥长度 + */ + private Integer mobileSecretKeyLength; + + /** + * 用户激活令牌TTL (小时) + */ + private Integer userActivationTokenTtl; + + /** + * 密码重置令牌TTL (小时) + */ + private Integer passwordResetTokenTtl; + } + + /** + * 密码策略属性配置 + */ + @Data + public static class PasswordPolicyProperties implements Serializable { + + /** + * 最小长度 + */ + private Integer minimumLength; + + /** + * 最大长度 + */ + private Integer maximumLength; + + /** + * 最小大写字母数 + */ + private Integer minimumUppercaseLetters; + + /** + * 最小小写字母数 + */ + private Integer minimumLowercaseLetters; + + /** + * 最小数字位数 + */ + private Integer minimumDigits; + + /** + * 最小特殊字符数 + */ + private Integer minimumSpecialCharacters; + + /** + * 允许空格 + */ + private Boolean allowWhitespaces; + } + + /** + * JWT配置属性 + */ + @Data + public static class JwtProperties { + + /** + * JWT令牌过期时间(秒) + */ + private Integer tokenExpirationTime; + + /** + * 刷新令牌过期时间(秒) + */ + private Integer refreshTokenExpTime; + + /** + * 令牌发行者 + */ + private String tokenIssuer; + + /** + * 令牌签名密钥 + */ + private String tokenSigningKey; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/AuthMethodNotSupportedException.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/AuthMethodNotSupportedException.java new file mode 100644 index 0000000..fb921e3 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/AuthMethodNotSupportedException.java @@ -0,0 +1,17 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.exception; + +import org.springframework.security.authentication.AuthenticationServiceException; + +public class AuthMethodNotSupportedException extends AuthenticationServiceException { + private static final long serialVersionUID = 3705043083010304496L; + + public AuthMethodNotSupportedException(String msg) { + super(msg); + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/JwtExpiredTokenException.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/JwtExpiredTokenException.java new file mode 100644 index 0000000..114d0b6 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/JwtExpiredTokenException.java @@ -0,0 +1,28 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.exception; + +import org.springframework.security.core.AuthenticationException; + +public class JwtExpiredTokenException extends AuthenticationException { + private static final long serialVersionUID = -5959543783324224864L; + + private String token; + + public JwtExpiredTokenException(String msg) { + super(msg); + } + + public JwtExpiredTokenException(String token, String msg, Throwable t) { + super(msg, t); + this.token = token; + } + + public String token() { + return this.token; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/UserPasswordExpiredException.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/UserPasswordExpiredException.java new file mode 100644 index 0000000..2c46054 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/UserPasswordExpiredException.java @@ -0,0 +1,24 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.exception; + +import org.springframework.security.authentication.CredentialsExpiredException; + +public class UserPasswordExpiredException extends CredentialsExpiredException { + + private final String resetToken; + + public UserPasswordExpiredException(String msg, String resetToken) { + super(msg); + this.resetToken = resetToken; + } + + public String getResetToken() { + return resetToken; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/UserPasswordNotValidException.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/UserPasswordNotValidException.java new file mode 100644 index 0000000..caf2909 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/exception/UserPasswordNotValidException.java @@ -0,0 +1,17 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.exception; + +import org.springframework.security.authentication.AccountStatusException; + +public class UserPasswordNotValidException extends AccountStatusException { + + public UserPasswordNotValidException(String msg) { + super(msg); + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/JwtPair.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/JwtPair.java new file mode 100644 index 0000000..f9a6484 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/JwtPair.java @@ -0,0 +1,31 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.NoArgsConstructor; +import sanbing.jcpp.app.dal.config.ibatis.enums.AuthorityEnum; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class JwtPair implements Serializable { + + private String token; + private String refreshToken; + + private AuthorityEnum scope; + + public JwtPair(String token, String refreshToken) { + this.token = token; + this.refreshToken = refreshToken; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/JwtToken.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/JwtToken.java new file mode 100644 index 0000000..de5605a --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/JwtToken.java @@ -0,0 +1,13 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.model; + +import java.io.Serializable; + +public interface JwtToken extends Serializable { + String token(); +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/SecurityUser.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/SecurityUser.java new file mode 100644 index 0000000..9c20d0b --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/SecurityUser.java @@ -0,0 +1,56 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.model; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import sanbing.jcpp.app.dal.entity.User; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +public class SecurityUser extends User { + + private static final long serialVersionUID = -797397440703066079L; + + private Collection authorities; + @Getter @Setter + private boolean enabled; + @Getter + @Setter + private UserPrincipal userPrincipal; + @Getter + @Setter + private String sessionId = UUID.randomUUID().toString(); + + public SecurityUser() { + super(); + } + + public SecurityUser(UUID id) { + super(id); + } + + public SecurityUser(User user, UserPrincipal userPrincipal) { + super(user); + this.userPrincipal = userPrincipal; + } + public SecurityUser(User user, boolean enabled, UserPrincipal userPrincipal) { + super(user); + this.enabled = enabled; + this.userPrincipal = userPrincipal; + } + public Collection getAuthorities() { + if (authorities == null) { + authorities = List.of(new SimpleGrantedAuthority("ADMIN")); + } + return authorities; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/UserCredentials.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/UserCredentials.java new file mode 100644 index 0000000..6249ddb --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/UserCredentials.java @@ -0,0 +1,25 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.model; + +import lombok.Data; +import lombok.ToString; + +@Data +@ToString(callSuper = true) +public class UserCredentials { + + private boolean enabled; + private String password; + private String activateToken; + private Long activateTokenExpTime; + private String resetToken; + private Long resetTokenExpTime; + private Long lastLoginTs; + private Integer failedLoginAttempts; + protected long createdTime; +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/UserPrincipal.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/UserPrincipal.java new file mode 100644 index 0000000..2e4a16c --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/UserPrincipal.java @@ -0,0 +1,13 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.model; + +import java.io.Serializable; + +public record UserPrincipal(String value) implements Serializable { + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/AccessJwtToken.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/AccessJwtToken.java new file mode 100644 index 0000000..a3cbd50 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/AccessJwtToken.java @@ -0,0 +1,23 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.model.token; + + +import sanbing.jcpp.app.service.security.model.JwtToken; + +public final class AccessJwtToken implements JwtToken { + private final String rawToken; + + public AccessJwtToken(String rawToken) { + this.rawToken = rawToken; + } + + public String token() { + return this.rawToken; + } + +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/JwtTokenFactory.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/JwtTokenFactory.java new file mode 100644 index 0000000..77be1a3 --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/JwtTokenFactory.java @@ -0,0 +1,190 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.model.token; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Component; +import sanbing.jcpp.app.dal.config.ibatis.enums.AuthorityEnum; +import sanbing.jcpp.app.service.security.config.SecurityProperties; +import sanbing.jcpp.app.service.security.exception.JwtExpiredTokenException; +import sanbing.jcpp.app.service.security.model.JwtPair; +import sanbing.jcpp.app.service.security.model.JwtToken; +import sanbing.jcpp.app.service.security.model.SecurityUser; +import sanbing.jcpp.app.service.security.model.UserPrincipal; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +@Slf4j +public class JwtTokenFactory { + + public static int KEY_LENGTH = Jwts.SIG.HS512.getKeyBitLength(); + + private static final String SCOPES = "scopes"; + private static final String USER_ID = "userId"; + private static final String USER_NAME = "userName"; + private static final String ENABLED = "enabled"; + private static final String SESSION_ID = "sessionId"; + + @Lazy + private final SecurityProperties securityProperties; + + private volatile JwtParser jwtParser; + private volatile SecretKey secretKey; + + /** + * Factory method for issuing new JWT Tokens. + */ + public AccessJwtToken createAccessJwtToken(SecurityUser securityUser) { + if (securityUser.getAuthority() == null) { + throw new IllegalArgumentException("User doesn't have any privileges"); + } + + JwtBuilder jwtBuilder = setUpToken(securityUser, securityUser.getAuthorities().stream() + .map(GrantedAuthority::getAuthority).collect(Collectors.toList()), securityProperties.getJwt().getTokenExpirationTime()); + jwtBuilder.claim(USER_NAME, securityUser.getUserName()) + .claim(ENABLED, securityUser.isEnabled()); + + String token = jwtBuilder.compact(); + + return new AccessJwtToken(token); + } + + public SecurityUser parseAccessJwtToken(String token) { + Jws jwsClaims = parseTokenClaims(token); + Claims claims = jwsClaims.getPayload(); + String subject = claims.getSubject(); + @SuppressWarnings("unchecked") + List scopes = claims.get(SCOPES, List.class); + if (scopes == null || scopes.isEmpty()) { + throw new IllegalArgumentException("JWT Token doesn't have any scopes"); + } + + SecurityUser securityUser = new SecurityUser(UUID.fromString(claims.get(USER_ID, String.class))); + securityUser.setUserName(subject); + securityUser.setAuthority(AuthorityEnum.parse(scopes.get(0))); + if (claims.get(SESSION_ID, String.class) != null) { + securityUser.setSessionId(claims.get(SESSION_ID, String.class)); + } + + UserPrincipal principal = new UserPrincipal(subject); + securityUser.setUserPrincipal(principal); + + return securityUser; + } + + public JwtToken createRefreshToken(SecurityUser securityUser) { + + String token = setUpToken(securityUser, Collections.singletonList(AuthorityEnum.REFRESH_TOKEN.name()), securityProperties.getJwt().getRefreshTokenExpTime()) + .id(UUID.randomUUID().toString()).compact(); + + return new AccessJwtToken(token); + } + + public SecurityUser parseRefreshToken(String token) { + Jws jwsClaims = parseTokenClaims(token); + Claims claims = jwsClaims.getPayload(); + String subject = claims.getSubject(); + @SuppressWarnings("unchecked") + List scopes = claims.get(SCOPES, List.class); + if (scopes == null || scopes.isEmpty()) { + throw new IllegalArgumentException("Refresh Token doesn't have any scopes"); + } + if (!scopes.get(0).equals(AuthorityEnum.REFRESH_TOKEN.name())) { + throw new IllegalArgumentException("Invalid Refresh Token scope"); + } + UserPrincipal principal = new UserPrincipal(subject); + SecurityUser securityUser = new SecurityUser(UUID.fromString(claims.get(USER_ID, String.class))); + securityUser.setUserPrincipal(principal); + if (claims.get(SESSION_ID, String.class) != null) { + securityUser.setSessionId(claims.get(SESSION_ID, String.class)); + } + return securityUser; + } + + private JwtBuilder setUpToken(SecurityUser securityUser, List scopes, long expirationTime) { + if (StringUtils.isBlank(securityUser.getUserName())) { + throw new IllegalArgumentException("Cannot create JWT Token without username/email"); + } + + UserPrincipal principal = securityUser.getUserPrincipal(); + + ClaimsBuilder claimsBuilder = Jwts.claims() + .subject(principal.value()) + .add(USER_ID, securityUser.getId().toString()) + .add(SCOPES, scopes); + if (securityUser.getSessionId() != null) { + claimsBuilder.add(SESSION_ID, securityUser.getSessionId()); + } + + ZonedDateTime currentTime = ZonedDateTime.now(); + + claimsBuilder.expiration(Date.from(currentTime.plusSeconds(expirationTime).toInstant())); + + return Jwts.builder() + .claims(claimsBuilder.build()) + .issuer(securityProperties.getJwt().getTokenIssuer()) + .issuedAt(Date.from(currentTime.toInstant())) + .signWith(getSecretKey(), Jwts.SIG.HS512); + } + + public Jws parseTokenClaims(String token) { + try { + return getJwtParser().parseSignedClaims(token); + } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex) { + log.debug("Invalid JWT Token", ex); + throw new BadCredentialsException("Invalid JWT token: ", ex); + } catch (ExpiredJwtException expiredEx) { + log.debug("JWT Token is expired", expiredEx); + throw new JwtExpiredTokenException(token, "JWT Token expired", expiredEx); + } + } + + public JwtPair createTokenPair(SecurityUser securityUser) { + securityUser.setSessionId(UUID.randomUUID().toString()); + JwtToken accessToken = createAccessJwtToken(securityUser); + JwtToken refreshToken = createRefreshToken(securityUser); + return new JwtPair(accessToken.token(), refreshToken.token()); + } + + private SecretKey getSecretKey() { + if (secretKey == null) { + synchronized (this) { + if (secretKey == null) { + byte[] decodedToken = Base64.getDecoder().decode(securityProperties.getJwt().getTokenSigningKey()); + secretKey = new SecretKeySpec(decodedToken, "HmacSHA512"); + } + } + } + return secretKey; + } + + private JwtParser getJwtParser() { + if (jwtParser == null) { + synchronized (this) { + if (jwtParser == null) { + jwtParser = Jwts.parser() + .verifyWith(Keys.hmacShaKeyFor(Base64.getDecoder().decode(securityProperties.getJwt().getTokenSigningKey()))) + .build(); + } + } + } + return jwtParser; + } +} diff --git a/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/RawAccessJwtToken.java b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/RawAccessJwtToken.java new file mode 100644 index 0000000..04265fc --- /dev/null +++ b/jcpp-app/src/main/java/sanbing/jcpp/app/service/security/model/token/RawAccessJwtToken.java @@ -0,0 +1,18 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.app.service.security.model.token; + + +import sanbing.jcpp.app.service.security.model.JwtToken; + +import java.io.Serializable; + +public record RawAccessJwtToken(String token) implements JwtToken, Serializable { + + private static final long serialVersionUID = -797397445703066079L; + +} diff --git a/jcpp-app/src/main/resources/mapper/AttributeMapper.xml b/jcpp-app/src/main/resources/mapper/AttributeMapper.xml new file mode 100644 index 0000000..5a73919 --- /dev/null +++ b/jcpp-app/src/main/resources/mapper/AttributeMapper.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + DELETE FROM t_attr WHERE entity_id = #{entityId} AND attr_key = #{attrKey} + + + diff --git a/jcpp-app/src/main/resources/mapper/GunMapper.xml b/jcpp-app/src/main/resources/mapper/GunMapper.xml new file mode 100644 index 0000000..d808ced --- /dev/null +++ b/jcpp-app/src/main/resources/mapper/GunMapper.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jcpp-app/src/main/resources/mapper/PileMapper.xml b/jcpp-app/src/main/resources/mapper/PileMapper.xml new file mode 100644 index 0000000..c7d5652 --- /dev/null +++ b/jcpp-app/src/main/resources/mapper/PileMapper.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/jcpp-app/src/main/resources/sql/schema-init.sql b/jcpp-app/src/main/resources/sql/schema-init.sql new file mode 100644 index 0000000..52b0b9c --- /dev/null +++ b/jcpp-app/src/main/resources/sql/schema-init.sql @@ -0,0 +1,131 @@ +/* + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ + +-- 数据库版本表 +CREATE TABLE IF NOT EXISTS t_schema_version +( + version varchar(32) not null primary key +); + +-- 插入初始版本 +INSERT INTO t_schema_version (version) VALUES ('1.0.0') ON CONFLICT (version) DO NOTHING; + +CREATE TABLE IF NOT EXISTS t_user +( + id uuid not null + constraint owner_pkey + primary key, + created_time timestamp default CURRENT_TIMESTAMP not null, + updated_time timestamp, + additional_info jsonb, + status varchar(16) not null, + user_name varchar(255) not null, + user_credentials jsonb not null, + authority varchar(32), + version int default 1 +); + +CREATE UNIQUE INDEX IF NOT EXISTS uni_user_name + on t_user (user_name); + +-- 为t_user表的字段添加注释 +COMMENT ON COLUMN t_user.authority IS '用户权限: SYS_ADMIN, TENANT_ADMIN, CUSTOMER_USER, REFRESH_TOKEN, PRE_VERIFICATION_TOKEN'; + +-- 为authority字段创建索引,便于按权限查询用户 +CREATE INDEX IF NOT EXISTS idx_user_authority + on t_user (authority); + +CREATE TABLE IF NOT EXISTS t_station +( + id uuid not null + constraint station_pkey + primary key, + created_time timestamp default CURRENT_TIMESTAMP not null, + updated_time timestamp, + additional_info jsonb, + station_name varchar(255) not null, + station_code varchar(255) not null, + longitude double precision, + latitude double precision, + province varchar(255), + city varchar(255), + county varchar(255), + address varchar(255), + version int default 1 +); + +CREATE UNIQUE INDEX IF NOT EXISTS uni_station_code + on t_station (station_code); + +CREATE TABLE IF NOT EXISTS t_pile +( + id uuid not null + constraint pile_pkey + primary key, + created_time timestamp default CURRENT_TIMESTAMP not null, + updated_time timestamp, + additional_info jsonb, + pile_name varchar(255) not null, + pile_code varchar(255) not null, + protocol varchar(255) not null, + station_id uuid not null, + brand varchar(255), + model varchar(255), + manufacturer varchar(255), + type varchar(16) not null, + version int default 1 +); + +CREATE UNIQUE INDEX IF NOT EXISTS uni_pile_code + on t_pile (pile_code); + + +CREATE TABLE IF NOT EXISTS t_gun +( + id uuid not null + primary key, + created_time timestamp default CURRENT_TIMESTAMP not null, + updated_time timestamp default CURRENT_TIMESTAMP not null, + additional_info varchar(255), + gun_no varchar(255) not null, + gun_name varchar(255) not null, + gun_code varchar(255) not null, + station_id uuid not null, + pile_id uuid not null, + version int default 1 +); + +CREATE INDEX IF NOT EXISTS idx_gun_pile_id + on t_gun (pile_id); + +CREATE INDEX IF NOT EXISTS idx_gun_pile_gun_code + on t_gun (pile_id, gun_code); + + + +CREATE SEQUENCE IF NOT EXISTS attr_kv_version_seq cache 1; + +-- 属性表:存储充电桩、充电枪的最新属性数据(如状态等) +-- 采用键值对存储结构设计 +CREATE TABLE IF NOT EXISTS t_attr +( + entity_id uuid not null, -- 实体ID (UUID保证全局唯一) + attr_key varchar(255) not null, -- 属性键 (字符串类型提高可读性) + bool_v boolean, -- 布尔值 + str_v varchar(10000000), -- 字符串值 + long_v bigint, -- 长整型值 + dbl_v double precision, -- 双精度值 + json_v json, -- JSON值 + last_update_ts bigint not null, -- 最后更新时间戳 + version int default 0 not null, -- 版本号,用于乐观锁 + PRIMARY KEY (entity_id, attr_key) +); + + + + + diff --git a/jcpp-app/src/main/resources/xss-policy.xml b/jcpp-app/src/main/resources/xss-policy.xml new file mode 100644 index 0000000..e35a22d --- /dev/null +++ b/jcpp-app/src/main/resources/xss-policy.xml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + g + grin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/CacheConstants.java b/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/CacheConstants.java index 71d19ff..30e68f6 100644 --- a/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/CacheConstants.java +++ b/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/CacheConstants.java @@ -10,6 +10,10 @@ public final class CacheConstants { public static final String PILE_CACHE = "piles"; + public static final String GUN_CACHE = "guns"; + public static final String PILE_SESSION_CACHE = "pileSessions"; + public static final String ATTRIBUTES_CACHE = "attributes"; + } diff --git a/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCacheKey.java b/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCacheKey.java index da82877..e9f3109 100644 --- a/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCacheKey.java +++ b/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedCacheKey.java @@ -11,7 +11,7 @@ import java.io.Serializable; public interface VersionedCacheKey extends Serializable { default boolean isVersioned() { - return false; + return true; } } diff --git a/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedRedisCache.java b/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedRedisCache.java index 1cb6791..04c4e1f 100644 --- a/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedRedisCache.java +++ b/jcpp-infrastructure-cache/src/main/java/sanbing/jcpp/infrastructure/cache/VersionedRedisCache.java @@ -70,7 +70,7 @@ public abstract class VersionedRedisCachesanbing jcpp-infrastructure-util - - javax.annotation - javax.annotation-api - com.google.protobuf protobuf-java diff --git a/jcpp-infrastructure-proto/src/main/java/sanbing/jcpp/infrastructure/proto/ProtoConverter.java b/jcpp-infrastructure-proto/src/main/java/sanbing/jcpp/infrastructure/proto/ProtoConverter.java index e1937c9..f1d3c76 100644 --- a/jcpp-infrastructure-proto/src/main/java/sanbing/jcpp/infrastructure/proto/ProtoConverter.java +++ b/jcpp-infrastructure-proto/src/main/java/sanbing/jcpp/infrastructure/proto/ProtoConverter.java @@ -10,6 +10,7 @@ package sanbing.jcpp.infrastructure.proto; import sanbing.jcpp.infrastructure.proto.model.PricingModel; import sanbing.jcpp.infrastructure.proto.model.PricingModel.FlagPrice; import sanbing.jcpp.infrastructure.proto.model.PricingModel.Period; +import sanbing.jcpp.infrastructure.proto.model.PricingModel.TimePeriodItem; import sanbing.jcpp.infrastructure.util.trace.Tracer; import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.*; @@ -17,7 +18,7 @@ import sanbing.jcpp.proto.gen.ProtocolProto.*; import java.util.Map; /** - * @author baigod + * @author 九筒 */ public class ProtoConverter { @@ -30,39 +31,109 @@ public class ProtoConverter { .build(); } + /** + * 将业务层PricingModel转换为Protobuf格式 + * 根据计费规则自动选择对应的价格配置结构 + */ public static PricingModelProto toPricingModel(PricingModel pricingModel) { - // 创建 PricingModelProto 实例 PricingModelProto.Builder builder = PricingModelProto.newBuilder(); - // 设置字段 + // 设置基本信息 builder.setType(PricingModelType.valueOf(pricingModel.getType().name())); builder.setRule(PricingModelRule.valueOf(pricingModel.getRule().name())); - builder.setStandardElec(pricingModel.getStandardElec().toPlainString()); - builder.setStandardServ(pricingModel.getStandardServ().toPlainString()); - // 转换 flagPriceList - for (Map.Entry entry : pricingModel.getFlagPriceList().entrySet()) { - PricingModelFlag flag = entry.getKey(); - FlagPrice flagPrice = entry.getValue(); + // 根据计费规则构建对应的价格配置 + switch (pricingModel.getRule()) { + case STANDARD: + // 标准计费:全天统一价格 + StandardPricingProto standardPricing = StandardPricingProto.newBuilder() + .setElecPrice(pricingModel.getStandardElec().toPlainString()) + .setServPrice(pricingModel.getStandardServ().toPlainString()) + .build(); + builder.setStandardPricing(standardPricing); + break; - FlagPriceProto flagPriceProto = FlagPriceProto.newBuilder() - .setFlag(PricingModelFlag.valueOf(flag.name())) // 枚举转换 - .setElec(flagPrice.getElec().toPlainString()) - .setServ(flagPrice.getServ().toPlainString()) - .build(); + case PEAK_VALLEY_PRICING: + // 峰谷计价:按电网峰谷政策分时段 + PeakValleyPricingProto.Builder peakValleyBuilder = PeakValleyPricingProto.newBuilder(); - builder.putFlagPrice(flag.ordinal(), flagPriceProto); // 按 ordinal 值作为 key 存入 - } + // 转换 flagPriceList + if (pricingModel.getFlagPriceList() != null) { + for (Map.Entry entry : pricingModel.getFlagPriceList().entrySet()) { + PricingModelFlag flag = entry.getKey(); + FlagPrice flagPrice = entry.getValue(); - // 转换 PeriodsList - for (Period period : pricingModel.getPeriodsList()) { - PeriodProto periodProto = PeriodProto.newBuilder() - .setSn(period.getSn()) - .setBegin(period.getBegin().toString()) // 假设 begin 是 LocalTime, 转换为字符串 - .setEnd(period.getEnd().toString()) // 假设 end 是 LocalTime, 转换为字符串 - .setFlag(PricingModelFlag.valueOf(period.getFlag().name())) - .build(); - builder.addPeriod(periodProto); + FlagPriceProto flagPriceProto = FlagPriceProto.newBuilder() + .setFlag(PricingModelFlag.valueOf(flag.name())) + .setElec(flagPrice.getElec().toPlainString()) + .setServ(flagPrice.getServ().toPlainString()) + .build(); + + peakValleyBuilder.putFlagPrice(flag.ordinal(), flagPriceProto); + } + } + + // 转换 PeriodsList + if (pricingModel.getPeriodsList() != null) { + for (Period period : pricingModel.getPeriodsList()) { + PeriodProto periodProto = PeriodProto.newBuilder() + .setSn(period.getSn()) + .setBegin(period.getBegin().toString()) + .setEnd(period.getEnd().toString()) + .setFlag(PricingModelFlag.valueOf(period.getFlag().name())) + .build(); + peakValleyBuilder.addPeriod(periodProto); + } + } + + builder.setPeakValleyPricing(peakValleyBuilder.build()); + break; + + case TIME_PERIOD_PRICING: + // 时段计价:运营商自定义时段价格 + TimePeriodPricingProto.Builder timePeriodBuilder = TimePeriodPricingProto.newBuilder(); + + if (pricingModel.getTimePeriodItems() != null) { + // 转换自定义时段计价数据 + for (TimePeriodItem item : pricingModel.getTimePeriodItems()) { + TimePeriodItemProto.Builder itemBuilder = TimePeriodItemProto.newBuilder() + .setPeriodNo(item.getPeriodNo()) + .setStartTime(item.getStartTime().toString()) + .setEndTime(item.getEndTime().toString()) + .setElecPrice(item.getElecPrice().toPlainString()) + .setServPrice(item.getServPrice().toPlainString()); + + if (item.getDescription() != null) { + itemBuilder.setDescription(item.getDescription()); + } + + timePeriodBuilder.addPeriods(itemBuilder.build()); + } + } else if (pricingModel.getPeriodsList() != null) { + // 兼容处理:将峰谷时段数据转换为时段计价格式 + for (Period period : pricingModel.getPeriodsList()) { + FlagPrice flagPrice = pricingModel.getFlagPriceList() != null ? + pricingModel.getFlagPriceList().get(period.getFlag()) : null; + + TimePeriodItemProto.Builder itemBuilder = TimePeriodItemProto.newBuilder() + .setPeriodNo(period.getSn()) + .setStartTime(period.getBegin().toString()) + .setEndTime(period.getEnd().toString()); + + if (flagPrice != null) { + itemBuilder.setElecPrice(flagPrice.getElec().toPlainString()) + .setServPrice(flagPrice.getServ().toPlainString()); + } + + timePeriodBuilder.addPeriods(itemBuilder.build()); + } + } + + builder.setTimePeriodPricing(timePeriodBuilder.build()); + break; + + default: + throw new IllegalArgumentException("Unsupported pricing rule: " + pricingModel.getRule()); } return builder.build(); diff --git a/jcpp-infrastructure-proto/src/main/java/sanbing/jcpp/infrastructure/proto/model/PricingModel.java b/jcpp-infrastructure-proto/src/main/java/sanbing/jcpp/infrastructure/proto/model/PricingModel.java index fcfb9a7..5e4cfd7 100644 --- a/jcpp-infrastructure-proto/src/main/java/sanbing/jcpp/infrastructure/proto/model/PricingModel.java +++ b/jcpp-infrastructure-proto/src/main/java/sanbing/jcpp/infrastructure/proto/model/PricingModel.java @@ -17,65 +17,86 @@ import java.util.List; import java.util.Map; import java.util.UUID; +/** + * 计费模型 - 支持标准计费、峰谷计价、时段计价三种模式 + */ @Data public class PricingModel { private UUID id; - // 计数器,供充电桩协议使用 + // 序列号,用于充电桩协议通信 private int sequenceNumber; private String pileCode; - private PricingModelType type; + private PricingModelType type; // 计费类型:充电/放电 - private PricingModelRule rule; + private PricingModelRule rule; // 计费规则:标准/峰谷/时段 /** - * 标准电价(单位元) + * 标准电价(元/度)- 标准计费模式使用 */ private BigDecimal standardElec; /** - * 标准服务费(单位元) + * 标准服务费(元/度)- 标准计费模式使用 */ private BigDecimal standardServ; /** - * 分时电价 + * 峰谷价格配置 - 峰谷计费模式使用 + * key: 时段标志(尖峰/峰/平/谷/深谷) + * value: 对应的电费和服务费 */ private Map flagPriceList; /** - * 分时时段 + * 峰谷时段划分 - 峰谷计费模式使用 */ private List periodsList; + /** + * 自定义时段配置 - 时段计价模式使用 + */ + private List timePeriodItems; + + /** + * 峰谷时段定义 - 用于峰谷计价模式 + */ @Setter @Getter public static class Period { - private int sn; - - // 起始时间 - private LocalTime begin; - - // 结束时间 - private LocalTime end; - - // 尖峰平谷标识 - private PricingModelFlag flag; + private int sn; // 时段序号 + private LocalTime begin; // 起始时间 + private LocalTime end; // 结束时间 + private PricingModelFlag flag; // 时段标志(尖峰/峰/平/谷/深谷) } + /** + * 峰谷价格定义 - 对应各时段标志的价格 + */ @Data @AllArgsConstructor @NoArgsConstructor public static class FlagPrice { + private BigDecimal elec; // 电费价格(元/度) + private BigDecimal serv; // 服务费价格(元/度) + } - // 分时电价,单位元 - private BigDecimal elec; - - // 分时服务费,单位元 - private BigDecimal serv; + /** + * 自定义时段定义 - 用于时段计价模式 + */ + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class TimePeriodItem { + private int periodNo; // 时段编号(从1开始) + private LocalTime startTime; // 开始时间 + private LocalTime endTime; // 结束时间 + private BigDecimal elecPrice; // 该时段电费(元/度) + private BigDecimal servPrice; // 该时段服务费(元/度) + private String description; // 时段名称(如"早高峰") } } \ No newline at end of file diff --git a/jcpp-infrastructure-proto/src/main/proto/protocol.proto b/jcpp-infrastructure-proto/src/main/proto/protocol.proto index 918f3e4..6765ea9 100644 --- a/jcpp-infrastructure-proto/src/main/proto/protocol.proto +++ b/jcpp-infrastructure-proto/src/main/proto/protocol.proto @@ -55,7 +55,9 @@ message UplinkQueueMessage { int64 sessionIdLSB = 4; string messageKey = 5; string protocolName = 6; + int64 ts = 7; bytes requestData = 10; + SessionCloseEventProto sessionCloseEventProto = 20; LoginRequest loginRequest = 21; HeartBeatRequest heartBeatRequest = 22; VerifyPricingRequest verifyPricingRequest = 23; @@ -77,7 +79,7 @@ message UplinkQueueMessage { OfflineCardBalanceUpdateResponse offlineCardBalanceUpdateResponse = 40; OfflineCardSyncResponse offlineCardSyncResponse = 41; TimeSyncResponse timeSyncResponse = 42; - + BmsDemandChargerOutputProto bmsDemandChargerOutputProto = 43; } message DownlinkRequestMessage { @@ -110,6 +112,19 @@ message DownlinkResponseMessage { optional string error = 2; } +message SessionCloseEventProto { + string pileCode = 2; // 充电桩编码 + SessionCloseReason reason = 3; // 会话关闭原因 + optional string additionalInfo = 20; // 附加信息 +} + +enum SessionCloseReason { + SESSION_CLOSE_UNKNOWN = 0; // 未知原因 + SESSION_CLOSE_DESTRUCTION = 1; // 自然销毁(空闲超时) + SESSION_CLOSE_ON_CHANNEL_INACTIVE = 2; // 通道不活跃 + SESSION_CLOSE_MANUALLY = 3; // 手动关闭 +} + message LoginRequest { string pileCode = 2; string credential = 3; @@ -159,13 +174,17 @@ message QueryPricingResponse { PricingModelProto pricingModel = 1; } +// 计费模型配置 - 支持三种计费方式 message PricingModelProto { - PricingModelType type = 3; - PricingModelRule rule = 4; - string standardElec = 5; - string standardServ = 6; - map flagPrice = 8; - repeated PeriodProto period = 9; + PricingModelType type = 3; // 计费类型:充电/放电 + PricingModelRule rule = 4; // 计费规则:标准/峰谷/时段 + + // 计费配置:根据rule字段确定使用哪种配置 + oneof pricing_config { + StandardPricingProto standardPricing = 5; // 标准计费:固定价格 + PeakValleyPricingProto peakValleyPricing = 6; // 峰谷计价:按尖峰平谷分时段 + TimePeriodPricingProto timePeriodPricing = 7; // 时段计价:自定义时段价格 + } } message PeriodProto { @@ -181,14 +200,42 @@ message FlagPriceProto { string serv = 3; } +// 标准计费配置 - 全天统一价格 +message StandardPricingProto { + string elecPrice = 1; // 电费价格(元/度) + string servPrice = 2; // 服务费价格(元/度) +} + +// 峰谷计价配置 - 按电网峰谷政策分时计费 +message PeakValleyPricingProto { + map flagPrice = 1; // 各时段价格映射表 + repeated PeriodProto period = 2; // 时段划分配置 +} + +// 时段计价配置 - 运营商自定义时段计费 +message TimePeriodPricingProto { + repeated TimePeriodItemProto periods = 1; // 自定义时段列表 +} + +// 时段计价单个时段定义 +message TimePeriodItemProto { + int32 periodNo = 1; // 时段编号(从1开始) + string startTime = 2; // 开始时间(HH:mm:ss格式) + string endTime = 3; // 结束时间(HH:mm:ss格式) + string elecPrice = 4; // 该时段电费(元/度) + string servPrice = 5; // 该时段服务费(元/度) + optional string description = 6; // 时段名称(如"早高峰") +} + enum PricingModelType { CHARGE = 0; // 充电费率模型 DISCHARGE = 1; // 放电费率模型 } enum PricingModelRule { - STANDARD = 0; - SPLIT_TIME = 1; + STANDARD = 0; // 标准计费:全天统一价格 + PEAK_VALLEY_PRICING = 1; // 峰谷计费:按电网峰谷政策 + TIME_PERIOD_PRICING = 2; // 时段计费:运营商自定义 } enum PricingModelFlag { @@ -213,7 +260,6 @@ enum GunRunStatus { } message GunRunStatusProto { - int64 ts = 1; string pileCode = 4; string gunCode = 5; GunRunStatus GunRunStatus = 41; @@ -222,7 +268,6 @@ message GunRunStatusProto { } message ChargingProgressProto { - int64 ts = 1; string pileCode = 4; string gunCode = 5; string tradeNo = 6; @@ -265,7 +310,6 @@ message RestartPileRequest { } message RemoteStartChargingResponse { - int64 ts = 1; string pileCode = 4; string gunCode = 5; string tradeNo = 6; @@ -274,7 +318,6 @@ message RemoteStartChargingResponse { optional string additionalInfo = 20; } message RestartPileResponse { - int64 ts = 1; string pileCode = 4; bool success = 7; } @@ -285,7 +328,6 @@ message RemoteStopChargingRequest { } message RemoteStopChargingResponse { - int64 ts = 1; string pileCode = 4; string gunCode = 5; bool success = 7; @@ -344,7 +386,6 @@ message TransactionRecordRequest { } message BmsChargingErrorProto { - int64 ts = 1; string pileCode = 4; string gunCode = 5; string tradeNo = 6; @@ -358,7 +399,6 @@ message TransactionRecordResponse { } message BmsParamConfigReportProto { - int64 ts = 1; // 时间戳 string pileCode = 2; // 桩编码 string gunCode = 3; // 枪编码 string tradeNo = 4; // 交易号 @@ -376,7 +416,6 @@ message BmsParamConfigReportProto { } message BmsChargingInfoProto { - int64 ts = 1; // 时间戳 string pileCode = 4; string gunCode = 5; string tradeNo = 6; @@ -404,7 +443,6 @@ message OtaResponse { } message BmsAbortProto { - int64 ts = 1; // 时间戳 string pileCode = 4; string gunCode = 5; string tradeNo = 6; @@ -412,7 +450,6 @@ message BmsAbortProto { } message BmsHandshakeProto { - int64 ts = 1; // 时间戳 string pileCode = 2; // 桩编码 string gunCode = 3; // 枪编码 string tradeNo = 4; // 交易流水号 @@ -433,7 +470,6 @@ message BmsHandshakeProto { } message GroundLockStatusProto { - int64 ts = 1; // 时间戳 string pileCode = 2; // 桩编号 string gunCode = 3; // 枪号 int32 lockStatus = 4; // 车位锁状态 @@ -485,3 +521,11 @@ message TimeSyncResponse { string pileCode = 1; string time = 2; } + +message BmsDemandChargerOutputProto { + string pileCode = 4; + string gunCode = 5; + string tradeNo = 6; + optional string additionalInfo = 20; +} + diff --git a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/AbstractQueueConsumerTemplate.java b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/AbstractQueueConsumerTemplate.java index ccd4ecf..f3c8b36 100644 --- a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/AbstractQueueConsumerTemplate.java +++ b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/AbstractQueueConsumerTemplate.java @@ -107,7 +107,7 @@ public abstract class AbstractQueueConsumerTemplate imple } } catch (IOException e) { log.error("Failed decode record: [{}]", record); - throw new RuntimeException("Failed to decode record: ", e); + throw new RuntimeException("解码记录失败: ", e); } }); return result; diff --git a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/common/QueueConstants.java b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/common/QueueConstants.java index dec3e29..85dab92 100644 --- a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/common/QueueConstants.java +++ b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/common/QueueConstants.java @@ -9,7 +9,7 @@ package sanbing.jcpp.infrastructure.queue.common; import static sanbing.jcpp.infrastructure.util.trace.TracerContextUtil.*; /** - * @author baigod + * @author 九筒 */ public final class QueueConstants { diff --git a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/DefaultServiceInfoProvider.java b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/DefaultServiceInfoProvider.java index 2dc602b..bad0bd5 100644 --- a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/DefaultServiceInfoProvider.java +++ b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/DefaultServiceInfoProvider.java @@ -24,7 +24,7 @@ import java.util.List; /** - * @author baigod + * @author 九筒 */ @Component @Slf4j diff --git a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/HashPartitionProvider.java b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/HashPartitionProvider.java index 59db207..878fde7 100644 --- a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/HashPartitionProvider.java +++ b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/HashPartitionProvider.java @@ -27,7 +27,7 @@ import static sanbing.jcpp.infrastructure.util.JCPPHashUtil.forName; import static sanbing.jcpp.infrastructure.util.JCPPHashUtil.hash; /** - * @author baigod + * @author 九筒 */ @Component @Slf4j @@ -74,7 +74,7 @@ public class HashPartitionProvider implements PartitionProvider { private TopicPartitionInfo resolve(QueueKey queueKey, int hash) { Integer partitionSize = partitionSizesMap.get(queueKey); if (partitionSize == null) { - throw new IllegalStateException("Partitions info for queue " + queueKey + " is missing"); + throw new IllegalStateException("队列 " + queueKey + " 的分区信息缺失"); } int partition = Math.abs(hash % partitionSize); diff --git a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/ServiceInfoProvider.java b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/ServiceInfoProvider.java index 6315d64..754d8cd 100644 --- a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/ServiceInfoProvider.java +++ b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/ServiceInfoProvider.java @@ -10,7 +10,7 @@ package sanbing.jcpp.infrastructure.queue.discovery; import sanbing.jcpp.proto.gen.ClusterProto; /** - * @author baigod + * @author 九筒 */ public interface ServiceInfoProvider { String getServiceId(); diff --git a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/event/JCPPApplicationEventListener.java b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/event/JCPPApplicationEventListener.java index 66a461a..e9997b2 100644 --- a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/event/JCPPApplicationEventListener.java +++ b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/discovery/event/JCPPApplicationEventListener.java @@ -22,7 +22,7 @@ public abstract class JCPPApplicationEventListener> partitionsMap; diff --git a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/kafka/KafkaAdmin.java b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/kafka/KafkaAdmin.java index c6bff58..809ddaf 100644 --- a/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/kafka/KafkaAdmin.java +++ b/jcpp-infrastructure-queue/src/main/java/sanbing/jcpp/infrastructure/queue/kafka/KafkaAdmin.java @@ -60,12 +60,12 @@ public class KafkaAdmin implements QueueAdmin { } case null, default -> { log.warn("[{}] Failed to create topic", topic, ee); - throw new RuntimeException(ee); + throw new RuntimeException("Kafka管理操作失败", ee); } } } catch (Exception e) { log.warn("[{}] Failed to create topic", topic, e); - throw new RuntimeException(e); + throw new RuntimeException("Kafka操作失败", e); } } diff --git a/jcpp-infrastructure-stats/src/main/java/sanbing/jcpp/infrastructure/stats/StatsFactory.java b/jcpp-infrastructure-stats/src/main/java/sanbing/jcpp/infrastructure/stats/StatsFactory.java index 99dc188..38f5b1e 100644 --- a/jcpp-infrastructure-stats/src/main/java/sanbing/jcpp/infrastructure/stats/StatsFactory.java +++ b/jcpp-infrastructure-stats/src/main/java/sanbing/jcpp/infrastructure/stats/StatsFactory.java @@ -16,7 +16,6 @@ public interface StatsFactory { * @param key 指标名 * @param statsName statsName的标签值 * @param otherTags 其他Tag键值对,参数个数需要是偶数 - * @return */ StatsCounter createStatsCounter(String key, String statsName, String... otherTags); @@ -25,7 +24,6 @@ public interface StatsFactory { * * @param key 指标名 * @param tags 自定义Tag键值对,参数个数需要是偶数 - * @return */ DefaultCounter createDefaultCounter(String key, String... tags); @@ -34,7 +32,6 @@ public interface StatsFactory { * * @param key 指标名 * @param tags 自定义Tag键值对,参数个数需要是偶数 - * @return */ MessagesStats createMessagesStats(String key, String... tags); @@ -43,7 +40,6 @@ public interface StatsFactory { * * @param key 指标名 * @param tags 自定义Tag键值对,参数个数需要是偶数 - * @return */ Timer createTimer(String key, String... tags); @@ -53,8 +49,6 @@ public interface StatsFactory { * @param key 指标名 * @param number 初始值 * @param tags 自定义Tag键值对,参数个数需要是偶数 - * @return - * @param */ T createGauge(String key, T number, String... tags); diff --git a/jcpp-infrastructure-util/pom.xml b/jcpp-infrastructure-util/pom.xml index 3c4e36e..e0743d5 100644 --- a/jcpp-infrastructure-util/pom.xml +++ b/jcpp-infrastructure-util/pom.xml @@ -43,7 +43,6 @@ com.google.guava guava - ${guava.version} org.apache.commons @@ -53,7 +52,6 @@ cn.hutool hutool-core - jakarta.validation jakarta.validation-api @@ -78,6 +76,10 @@ io.netty netty-buffer + + org.owasp.antisamy + antisamy + diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/CollectionsUtil.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/CollectionsUtil.java new file mode 100644 index 0000000..bc32ac4 --- /dev/null +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/CollectionsUtil.java @@ -0,0 +1,94 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.infrastructure.util; + +import java.util.*; +import java.util.stream.Collectors; + +public class CollectionsUtil { + /** + * 判断集合是否为空(null 或者 size 为 0)。 + */ + public static boolean isEmpty(Collection collection) { + return collection == null || collection.isEmpty(); + } + + /** + * 判断集合是否不为空。 + */ + public static boolean isNotEmpty(Collection collection) { + return !isEmpty(collection); + } + + /** + * 返回存在于集合 B(新)但不在集合 A(旧)中的元素的新集合。 + */ + public static Set diffSets(Set a, Set b) { + return b.stream().filter(p -> !a.contains(p)).collect(Collectors.toSet()); + } + + /** + * 返回存在于列表 B(新)但不在列表 A(旧)中的元素的新列表。 + */ + public static List diffLists(List a, List b) { + return b.stream().filter(p -> !a.contains(p)).collect(Collectors.toList()); + } + + /** + * 判断集合中是否包含指定元素。 + */ + public static boolean contains(Collection collection, T element) { + return isNotEmpty(collection) && collection.contains(element); + } + + /** + * 统计数组中非空元素的数量。 + */ + public static int countNonNull(T[] array) { + int count = 0; + for (T t : array) { + if (t != null) count++; + } + return count; + } + + /** + * 创建一个 Map,传入键值对参数。 + * 如果参数数量不是偶数,则抛出异常。 + */ + @SuppressWarnings("unchecked") + public static Map mapOf(T... kvs) { + if (kvs.length % 2 != 0) { + throw new IllegalArgumentException("参数数量无效"); + } + Map map = new HashMap<>(); + for (int i = 0; i < kvs.length; i += 2) { + T key = kvs[i]; + T value = kvs[i + 1]; + map.put(key, value); + } + return map; + } + + /** + * 判断集合是否为空或者包含指定元素。 + */ + public static boolean emptyOrContains(Collection collection, V element) { + return isEmpty(collection) || collection.contains(element); + } + + /** + * 合并两个集合并返回一个新的 HashSet。 + */ + public static HashSet concat(Set set1, Set set2) { + HashSet result = new HashSet<>(); + result.addAll(set1); + result.addAll(set2); + return result; + } + +} diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/JCPPHashUtil.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/JCPPHashUtil.java index bc6d00a..5e75804 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/JCPPHashUtil.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/JCPPHashUtil.java @@ -13,7 +13,7 @@ import java.nio.charset.StandardCharsets; import java.util.UUID; /** - * @author baigod + * @author 九筒 */ public class JCPPHashUtil { public static HashFunction forName(String name) { diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/annotation/AppComponent.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/annotation/AppComponent.java index f3b68ca..f2de591 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/annotation/AppComponent.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/annotation/AppComponent.java @@ -15,7 +15,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * @author baigod + * @author 九筒 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/annotation/ProtocolComponent.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/annotation/ProtocolComponent.java index a7923ce..95d86ae 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/annotation/ProtocolComponent.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/annotation/ProtocolComponent.java @@ -20,7 +20,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * @author baigod + * @author 九筒 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/AbstractListeningExecutor.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/AbstractListeningExecutor.java new file mode 100644 index 0000000..edcb336 --- /dev/null +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/AbstractListeningExecutor.java @@ -0,0 +1,54 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.infrastructure.util.async; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + +import java.util.concurrent.Callable; + + +public abstract class AbstractListeningExecutor implements ListeningExecutor { + + private ListeningExecutorService service; + + @PostConstruct + public void init() { + this.service = MoreExecutors.listeningDecorator(JCPPExecutors.newWorkStealingPool(getThreadPollSize(), getClass())); + } + + @PreDestroy + public void destroy() { + if (this.service != null) { + this.service.shutdown(); + } + } + + @Override + public ListenableFuture executeAsync(Callable task) { + return service.submit(task); + } + + public ListenableFuture executeAsync(Runnable task) { + return service.submit(task); + } + + @Override + public void execute(Runnable command) { + service.execute(command); + } + + public ListeningExecutorService executor() { + return service; + } + + protected abstract int getThreadPollSize(); + +} diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/JCPPVirtualThreadFactory.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/JCPPVirtualThreadFactory.java index 7651907..fa29086 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/JCPPVirtualThreadFactory.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/JCPPVirtualThreadFactory.java @@ -9,6 +9,7 @@ package sanbing.jcpp.infrastructure.util.async; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; +@SuppressWarnings("ALL") public class JCPPVirtualThreadFactory implements ThreadFactory { private final String namePrefix; private final AtomicLong threadNumber = new AtomicLong(1); diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/ListeningExecutor.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/ListeningExecutor.java new file mode 100644 index 0000000..b56c593 --- /dev/null +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/async/ListeningExecutor.java @@ -0,0 +1,33 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.infrastructure.util.async; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; + +public interface ListeningExecutor extends Executor { + + ListenableFuture executeAsync(Callable task); + + default ListenableFuture executeAsync(Runnable task) { + return executeAsync(() -> { + task.run(); + return null; + }); + } + + default ListenableFuture submit(Callable task) { + return executeAsync(task); + } + + default ListenableFuture submit(Runnable task) { + return executeAsync(task); + } + +} diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/BCDUtil.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/BCDUtil.java index 834e4be..036a5f2 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/BCDUtil.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/BCDUtil.java @@ -190,7 +190,7 @@ public class BCDUtil { */ public static LocalDateTime bcdToDate(byte[] bcdBytes) { if (bcdBytes == null || bcdBytes.length != BCD_DATE_LENGTH) { - throw new IllegalArgumentException("BCD date bytes must be 8 bytes long"); + throw new IllegalArgumentException("BCD日期字节必须为8字节长度"); } // 检查是否全为0 diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/ByteUtil.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/ByteUtil.java index b16029c..1189bab 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/ByteUtil.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/ByteUtil.java @@ -15,7 +15,7 @@ import java.nio.charset.StandardCharsets; import java.util.UUID; /** - * @author baigod + * @author 九筒 */ public class ByteUtil { @@ -83,11 +83,11 @@ public class ByteUtil { /** * 计算字节数组的累加和,如果累加结果超过1字节,则只取低8位 - * + *

* 示例: * byte[] data = {0x01, 0x02, 0x03}; * byte sum = calculateSum(data); // sum = 0x06 - * + *

* byte[] data2 = {(byte)0xFF, (byte)0xFF}; * byte sum2 = calculateSum(data2); // sum2 = (byte)0xFE (254 + 255 = 509, 取低8位为254) * @@ -110,7 +110,7 @@ public class ByteUtil { /** * 验证数据的累加和是否与期望值相等 - * + *

* 示例: * byte[] data = {0x01, 0x02, 0x03}; * boolean valid = verifySum(data, (byte)0x06); // valid = true diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/config/ShardingThreadPool.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/config/ShardingThreadPool.java index 546d895..5f85c29 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/config/ShardingThreadPool.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/config/ShardingThreadPool.java @@ -27,7 +27,7 @@ import static sanbing.jcpp.infrastructure.util.JCPPHashUtil.forName; import static sanbing.jcpp.infrastructure.util.JCPPHashUtil.hash; /** - * @author baigod + * @author 九筒 */ @Component @Slf4j diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/config/ThreadPoolConfiguration.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/config/ThreadPoolConfiguration.java index 021a40e..3375ea5 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/config/ThreadPoolConfiguration.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/config/ThreadPoolConfiguration.java @@ -17,7 +17,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** - * @author baigod + * @author 九筒 */ @Configuration public class ThreadPoolConfiguration { diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/exception/DownlinkException.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/exception/DownlinkException.java index bb144b2..4855a0a 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/exception/DownlinkException.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/exception/DownlinkException.java @@ -7,7 +7,7 @@ package sanbing.jcpp.infrastructure.util.exception; /** - * @author baigod + * @author 九筒 */ public class DownlinkException extends RuntimeException { diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DataTypeModule.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DataTypeModule.java index 8c93dfb..03b15db 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DataTypeModule.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DataTypeModule.java @@ -24,7 +24,7 @@ import java.util.Date; /** * 类型转换 * - * @author baigod + * @author 九筒 */ public class DataTypeModule extends SimpleModule { public static final DataTypeModule INSTANCE = new DataTypeModule(); diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DateDeserializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DateDeserializer.java index 5c6f70d..c282a75 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DateDeserializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DateDeserializer.java @@ -19,7 +19,7 @@ import java.util.Date; /** * 时间反序列化 * - * @author baigod + * @author 九筒 */ public class DateDeserializer extends JsonDeserializer { public static final DateDeserializer INSTANCE = new DateDeserializer(); diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DateSerializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DateSerializer.java index 705ea6a..dcaeaae 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DateSerializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/DateSerializer.java @@ -17,7 +17,7 @@ import java.util.Date; /** * 时间序列化 * - * @author baigod + * @author 九筒 */ public class DateSerializer extends StdSerializer { public static final DateSerializer INSTANCE = new DateSerializer(); diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/InstantDeserializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/InstantDeserializer.java index d1e7bc3..d0539d9 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/InstantDeserializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/InstantDeserializer.java @@ -18,7 +18,7 @@ import java.time.format.DateTimeFormatter; /** * Instant 反序列化 * - * @author baigod + * @author 九筒 */ public class InstantDeserializer extends com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer { public static final InstantDeserializer INSTANCE = new InstantDeserializer(); diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/InstantSerializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/InstantSerializer.java index c5ecfe9..66817ea 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/InstantSerializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/InstantSerializer.java @@ -11,7 +11,7 @@ import java.time.format.DateTimeFormatter; /** * Instant 序列化 * - * @author baigod + * @author 九筒 */ public class InstantSerializer extends com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer { public static final InstantSerializer INSTANCE = new InstantSerializer(); diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/JacksonUtil.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/JacksonUtil.java index 9e6833b..731a2c3 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/JacksonUtil.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/JacksonUtil.java @@ -18,11 +18,13 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; +import java.io.Reader; +import java.io.Writer; import java.util.Arrays; import java.util.TimeZone; /** - * @author baigod + * @author 九筒 */ public class JacksonUtil { @@ -51,8 +53,8 @@ public class JacksonUtil { try { return fromValue != null ? OBJECT_MAPPER.convertValue(fromValue, toValueType) : null; } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("The given object value: " - + fromValue + " cannot be converted to " + toValueType, e); + throw new IllegalArgumentException("给定的对象值: " + + fromValue + " 无法转换为 " + toValueType, e); } } @@ -60,8 +62,8 @@ public class JacksonUtil { try { return fromValue != null ? OBJECT_MAPPER.convertValue(fromValue, toValueTypeRef) : null; } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("The given object value: " - + fromValue + " cannot be converted to " + toValueTypeRef, e); + throw new IllegalArgumentException("给定的对象值: " + + fromValue + " 无法转换为 " + toValueTypeRef, e); } } @@ -69,8 +71,8 @@ public class JacksonUtil { try { return string != null ? OBJECT_MAPPER.readValue(string, clazz) : null; } catch (IOException e) { - throw new IllegalArgumentException("The given string value: " - + string + " cannot be transformed to Json object", e); + throw new IllegalArgumentException("给定的字符串值: " + + string + " 无法转换为Json对象", e); } } @@ -78,8 +80,8 @@ public class JacksonUtil { try { return string != null ? OBJECT_MAPPER.readValue(string, valueTypeRef) : null; } catch (IOException e) { - throw new IllegalArgumentException("The given string value: " - + string + " cannot be transformed to Json object", e); + throw new IllegalArgumentException("给定的字符串值: " + + string + " 无法转换为Json对象", e); } } @@ -87,8 +89,15 @@ public class JacksonUtil { try { return bytes != null ? OBJECT_MAPPER.readValue(bytes, clazz) : null; } catch (IOException e) { - throw new IllegalArgumentException("The given string value: " - + Arrays.toString(bytes) + " cannot be transformed to Json object", e); + throw new IllegalArgumentException("给定的字节数组: " + + Arrays.toString(bytes) + " 无法转换为Json对象", e); + } + } + public static T fromReader(Reader reader, Class clazz) { + try { + return reader != null ? OBJECT_MAPPER.readValue(reader, clazz) : null; + } catch (IOException e) { + throw new IllegalArgumentException("给定的Reader无法转换为Json对象", e); } } @@ -96,8 +105,8 @@ public class JacksonUtil { try { return OBJECT_MAPPER.readTree(bytes); } catch (IOException e) { - throw new IllegalArgumentException("The given byte[] value: " - + Arrays.toString(bytes) + " cannot be transformed to Json object", e); + throw new IllegalArgumentException("给定的字节数组: " + + Arrays.toString(bytes) + " 无法转换为Json对象", e); } } @@ -105,8 +114,7 @@ public class JacksonUtil { try { return value != null ? OBJECT_MAPPER.writeValueAsString(value) : null; } catch (JsonProcessingException e) { - throw new IllegalArgumentException("The given Json object value: " - + value + " cannot be transformed to a String", e); + throw new IllegalArgumentException("给定的对象值无法转换为字符串: " + value, e); } } @@ -118,26 +126,67 @@ public class JacksonUtil { } } + public static JsonNode toJsonNode(Object value) { + try { + return OBJECT_MAPPER.valueToTree(value); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("给定的对象值无法转换为JSON节点: " + value, e); + } + } + + public static T toPojo(String content, Class type) { + try { + return OBJECT_MAPPER.readValue(content, type); + } catch (IOException e) { + throw new IllegalArgumentException("给定的字符串值无法转换为指定类型: " + content, e); + } + } + + public static T toPojo(String content, TypeReference type) { + try { + return OBJECT_MAPPER.readValue(content, type); + } catch (IOException e) { + throw new IllegalArgumentException("给定的字符串值无法转换为指定类型: " + content, e); + } + } + + public static JsonNode toJson(String content) { + try { + return OBJECT_MAPPER.readTree(content); + } catch (IOException e) { + throw new IllegalArgumentException("给定的字符串值无法转换为JSON: " + content, e); + } + } + + public static JsonNode toJson(byte[] content) { + try { + return OBJECT_MAPPER.readTree(content); + } catch (IOException e) { + throw new IllegalArgumentException("给定的字节数组无法转换为JSON: " + Arrays.toString(content), e); + } + } + + public static T fromJson(JsonNode json, Class type) { + try { + return OBJECT_MAPPER.treeToValue(json, type); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("给定的JSON对象无法转换为指定类型: " + json, e); + } + } + + public static T fromJson(String json, Class type) { + try { + return fromJson(toJson(json), type); + } catch (IllegalArgumentException e) { + throw new RuntimeException("JSON转换失败", e); + } + } + public static T treeToValue(JsonNode node, Class clazz) { try { return OBJECT_MAPPER.treeToValue(node, clazz); - } catch (IOException e) { - throw new IllegalArgumentException("Can't convert value: " + node.toString(), e); - } - } - - public static JsonNode toJsonNode(String value) { - return toJsonNode(value, OBJECT_MAPPER); - } - - public static JsonNode toJsonNode(String value, ObjectMapper mapper) { - if (value == null || value.isEmpty()) { - return null; - } - try { - return mapper.readTree(value); - } catch (IOException e) { - throw new IllegalArgumentException(e); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("无法转换值: " + node.toString(), e); } } @@ -171,8 +220,16 @@ public class JacksonUtil { try { return OBJECT_MAPPER.writeValueAsBytes(value); } catch (JsonProcessingException e) { - throw new IllegalArgumentException("The given Json object value: " - + value + " cannot be transformed to a String", e); + throw new IllegalArgumentException("给定的对象值无法转换为字节数组: " + value, e); + } + } + + public static void writeValue(Writer writer, T value) { + try { + OBJECT_MAPPER.writeValue(writer, value); + } catch (IOException e) { + throw new IllegalArgumentException("The given writer value: " + + writer + "cannot be wrote", e); } } diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LocalDateTimeSerializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LocalDateTimeSerializer.java index e91f5a1..f74f97b 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LocalDateTimeSerializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LocalDateTimeSerializer.java @@ -17,7 +17,7 @@ import java.time.format.DateTimeFormatter; /** * 时间类型序列化工具 * - * @author baigod + * @author 九筒 */ public class LocalDateTimeSerializer extends JsonSerializer { public static final LocalDateTimeSerializer INSTANCE = new LocalDateTimeSerializer(); diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LocalTimeSerializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LocalTimeSerializer.java index 786b085..9056490 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LocalTimeSerializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LocalTimeSerializer.java @@ -17,7 +17,7 @@ import java.time.format.DateTimeFormatter; /** * 时间类型序列化工具 * - * @author baigod + * @author 九筒 */ public class LocalTimeSerializer extends JsonSerializer { public static final LocalTimeSerializer INSTANCE = new LocalTimeSerializer(); diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LongTimestampDeserializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LongTimestampDeserializer.java index 13c444b..8f43a4b 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LongTimestampDeserializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/LongTimestampDeserializer.java @@ -16,7 +16,7 @@ import java.time.ZoneId; /** * 13位时间戳反序列化器 - * @author baigod + * @author 九筒 */ public class LongTimestampDeserializer extends JsonDeserializer { diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/SqlDateDeserializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/SqlDateDeserializer.java index f015960..57c2f0b 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/SqlDateDeserializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/SqlDateDeserializer.java @@ -19,7 +19,7 @@ import java.time.format.DateTimeFormatter; /** * sqlDate 反序列化 * - * @author baigod + * @author 九筒 */ public class SqlDateDeserializer extends JsonDeserializer { diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/SqlDateSerializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/SqlDateSerializer.java index 3ebee75..14724f7 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/SqlDateSerializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/SqlDateSerializer.java @@ -17,7 +17,7 @@ import java.sql.Date; /** * sqlDate序列化 * - * @author baigod + * @author 九筒 */ public class SqlDateSerializer extends StdSerializer { public static final SqlDateSerializer INSTANCE = new SqlDateSerializer(); diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/TimestampDeserializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/TimestampDeserializer.java index 9e76273..4460677 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/TimestampDeserializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/TimestampDeserializer.java @@ -18,7 +18,7 @@ import java.time.format.DateTimeFormatter; /** * timestamp 反序列化 - * @author baigod + * @author 九筒 */ public class TimestampDeserializer extends JsonDeserializer { diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/TimestampSerializer.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/TimestampSerializer.java index 7a70756..952a52a 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/TimestampSerializer.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/jackson/TimestampSerializer.java @@ -17,7 +17,7 @@ import java.sql.Timestamp; /** * timestamp 序列化 * - * @author baigod + * @author 九筒 */ public class TimestampSerializer extends StdSerializer { public static final TimestampSerializer INSTANCE = new TimestampSerializer(); diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/Length.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/Length.java index 521cce3..88ea889 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/Length.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/Length.java @@ -24,6 +24,8 @@ public @interface Length { int max() default 255; + int min() default 0; + Class[] groups() default {}; Class[] payload() default {}; diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoNullChar.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoNullChar.java new file mode 100644 index 0000000..0224117 --- /dev/null +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoNullChar.java @@ -0,0 +1,26 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.infrastructure.util.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = {}) +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.RECORD_COMPONENT}) +@Retention(RetentionPolicy.RUNTIME) +public @interface NoNullChar { + + String message() default "should not contain 0x00 symbol"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoXss.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoXss.java new file mode 100644 index 0000000..0222519 --- /dev/null +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoXss.java @@ -0,0 +1,31 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.infrastructure.util.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +/** + * XSS攻击防护注解 + * 用于验证字符串是否包含XSS攻击内容 + * + * @author 九筒 + */ +@Documented +@Constraint(validatedBy = NoXssValidator.class) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface NoXss { + + String message() default "输入包含潜在的XSS攻击内容"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} \ No newline at end of file diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoXssValidator.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoXssValidator.java new file mode 100644 index 0000000..5a40cc1 --- /dev/null +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/NoXssValidator.java @@ -0,0 +1,61 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.infrastructure.util.validation; + +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.extern.slf4j.Slf4j; + +import java.util.regex.Pattern; + +@Slf4j +public class NoXssValidator implements ConstraintValidator { + + private static final Pattern JS_TEMPLATE_PATTERN = Pattern.compile("\\{\\{.*}}", Pattern.DOTALL); + + // 简化的XSS检查模式,避免依赖OWASP AntiSamy + private static final Pattern[] XSS_PATTERNS = { + Pattern.compile("]*>.*?", Pattern.CASE_INSENSITIVE | Pattern.DOTALL), + Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE), + Pattern.compile("on\\w+\\s*=", Pattern.CASE_INSENSITIVE), + Pattern.compile("]*>.*?", Pattern.CASE_INSENSITIVE | Pattern.DOTALL), + Pattern.compile("]*>.*?

", Pattern.CASE_INSENSITIVE | Pattern.DOTALL), + Pattern.compile("]*>.*?", Pattern.CASE_INSENSITIVE | Pattern.DOTALL), + }; + + @Override + public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { + String stringValue; + if (value instanceof CharSequence || value instanceof JsonNode) { + stringValue = value.toString(); + } else { + return true; + } + return isValid(stringValue); + } + + public static boolean isValid(String stringValue) { + if (stringValue == null || stringValue.isEmpty()) { + return true; + } + + // 检查JS模板语法 + if (JS_TEMPLATE_PATTERN.matcher(stringValue).find()) { + return false; + } + + // 简化的XSS检查 + for (Pattern pattern : XSS_PATTERNS) { + if (pattern.matcher(stringValue).find()) { + return false; + } + } + + return true; + } +} diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/RateLimit.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/RateLimit.java new file mode 100644 index 0000000..da5f4e2 --- /dev/null +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/RateLimit.java @@ -0,0 +1,30 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.infrastructure.util.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +@Constraint(validatedBy = {}) +public @interface RateLimit { + + String message() default "rate limit has duplicate 'Per seconds' configuration."; + + String fieldName() default ""; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/ValidJsonSchema.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/ValidJsonSchema.java new file mode 100644 index 0000000..de2a996 --- /dev/null +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/validation/ValidJsonSchema.java @@ -0,0 +1,26 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.infrastructure.util.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = {}) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValidJsonSchema { + + String message() default "must conform to the Draft 2020-12 meta-schema"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolBootstrap.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolBootstrap.java index cc05177..fd8f0ab 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolBootstrap.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolBootstrap.java @@ -25,7 +25,7 @@ import sanbing.jcpp.protocol.listener.tcp.TcpListener; import static org.springframework.boot.actuate.health.Status.UP; /** - * @author baigod + * @author 九筒 */ @Slf4j public abstract class ProtocolBootstrap implements HealthIndicator { @@ -65,7 +65,7 @@ public abstract class ProtocolBootstrap implements HealthIndicator { protocolContext.getPartitionProvider(), protocolContext.getServiceInfoProvider()); } else { - throw new IllegalArgumentException("Unknown Forwarder type: " + forwarderCfg.getType()); + throw new IllegalArgumentException("未知的转发器类型: " + forwarderCfg.getType()); } TcpCfg tcpCfg = protocolCfg.getListener().getTcp(); diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolContext.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolContext.java index 1f33594..77eb581 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolContext.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolContext.java @@ -21,7 +21,7 @@ import sanbing.jcpp.protocol.provider.ProtocolSessionRegistryProvider; import sanbing.jcpp.protocol.provider.ProtocolsConfigProvider; /** - * @author baigod + * @author 九筒 */ @Component @Getter diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolMessageProcessor.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolMessageProcessor.java index 6306a20..77140be 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolMessageProcessor.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/ProtocolMessageProcessor.java @@ -17,7 +17,7 @@ import sanbing.jcpp.protocol.forwarder.Forwarder; import java.util.UUID; /** - * @author baigod + * @author 九筒 */ @Slf4j public abstract class ProtocolMessageProcessor { @@ -44,25 +44,35 @@ public abstract class ProtocolMessageProcessor { uplinkMsgStats.incrementFailed(); - log.error("{} 消息处理器处理报文异常", listenerToHandlerMsg.session(), e); + log.error("{} 上行消息处理器处理报文异常", listenerToHandlerMsg.session(), e); } })); } protected abstract void uplinkHandle(ListenerToHandlerMsg listenerToHandlerMsg); + /** + * 下行消息处理入口 + * 负责统一的异常处理和日志记录 + */ public void downlinkHandle(SessionToHandlerMsg sessionToHandlerMsg, MessagesStats downlinkMsgStats) throws DownlinkException { try { - downlinkHandle(sessionToHandlerMsg); + doDownlinkHandle(sessionToHandlerMsg); } catch (Exception e) { downlinkMsgStats.incrementFailed(); + + log.warn("下行消息处理失败,session: {}, 异常信息: {}", sessionToHandlerMsg.session(), e.getMessage(), e); throw new DownlinkException(e.getMessage(), e); } } - protected abstract void downlinkHandle(SessionToHandlerMsg sessionToHandlerMsg); + /** + * 下行消息具体处理逻辑 + * 由各协议的具体实现类重写 + */ + protected abstract void doDownlinkHandle(SessionToHandlerMsg sessionToHandlerMsg); } \ No newline at end of file diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/adapter/DownlinkController.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/adapter/DownlinkController.java index 4f52fe4..f9c04e9 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/adapter/DownlinkController.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/adapter/DownlinkController.java @@ -24,7 +24,7 @@ import sanbing.jcpp.protocol.provider.ProtocolSessionRegistryProvider; import java.util.UUID; /** - * @author baigod + * @author 九筒 */ @RestController @RequestMapping("/api") @@ -40,7 +40,7 @@ public class DownlinkController { @PostMapping(value = "/onDownlink", consumes = "application/x-protobuf", produces = "application/x-protobuf") public DeferredResult> onDownlink(@RequestBody DownlinkRequestMessage downlinkMsg) { - log.debug("收到REST下行请求 {}", downlinkMsg); + log.info("收到REST下行请求 {}", downlinkMsg); final DeferredResult> response = new DeferredResult<>(onDownlinkTimeout, ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).build()); diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/adapter/DownlinkGrpcService.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/adapter/DownlinkGrpcService.java index 356aa66..1b49348 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/adapter/DownlinkGrpcService.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/adapter/DownlinkGrpcService.java @@ -15,6 +15,7 @@ import io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoopGroup; import io.grpc.netty.shaded.io.netty.channel.socket.nio.NioServerSocketChannel; import io.grpc.stub.StreamObserver; import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -28,7 +29,6 @@ import sanbing.jcpp.proto.gen.ProtocolProto.*; import sanbing.jcpp.protocol.domain.ProtocolSession; import sanbing.jcpp.protocol.provider.ProtocolSessionRegistryProvider; -import javax.annotation.PreDestroy; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @@ -37,7 +37,7 @@ import static sanbing.jcpp.infrastructure.proto.ProtoConverter.toTracerProto; import static sanbing.jcpp.infrastructure.util.config.ThreadPoolConfiguration.JCPP_COMMON_THREAD_POOL; /** - * @author baigod + * @author 九筒 */ @Service @Slf4j @@ -120,7 +120,7 @@ public class DownlinkGrpcService extends ProtocolInterfaceImplBase { TracerContextUtil.newTracer(tracerProto.getId(), tracerProto.getOrigin(), tracerProto.getTs()); MDCUtils.recordTracer(); - log.debug("通信层收到Grpc下行请求 {}", requestMsg); + log.info("通信层收到Grpc下行请求 {}", requestMsg); if (requestMsg.hasConnectRequestMsg()) { replyLock.lock(); diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/annotation/ProtocolCmd.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/annotation/ProtocolCmd.java index a6561e2..77d74de 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/annotation/ProtocolCmd.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/annotation/ProtocolCmd.java @@ -12,7 +12,7 @@ import java.lang.annotation.*; * 通用协议命令注解 * 所有协议的命令类都应该使用此注解 * - * @author sanbing + * @author 九筒 * @since 2024-12-16 */ @Target(ElementType.TYPE) diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/MemoryCfg.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/MemoryCfg.java index a93e26c..3be8dcb 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/MemoryCfg.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/MemoryCfg.java @@ -10,7 +10,7 @@ import lombok.Getter; import lombok.Setter; /** - * @author baigod + * @author 九筒 */ @Getter @Setter diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/TcpHandlerCfg.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/TcpHandlerCfg.java index 3f2645c..ed5e2d4 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/TcpHandlerCfg.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/TcpHandlerCfg.java @@ -51,7 +51,7 @@ public class TcpHandlerCfg { case TEXT -> HANDLER_MAP.put(type, JacksonUtil.treeToValue(cfgJson, TextHandlerConfiguration.class)); case JSON -> HANDLER_MAP.put(type, JacksonUtil.treeToValue(cfgJson, JsonHandlerConfiguration.class)); case BINARY -> HANDLER_MAP.put(type, JacksonUtil.treeToValue(cfgJson, BinaryHandlerConfiguration.class)); - default -> throw new IllegalArgumentException("Unknown TCP handler type: " + type); + default -> throw new IllegalArgumentException("未知的TCP处理器类型: " + type); } } } diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/enums/TcpHandlerType.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/enums/TcpHandlerType.java index 097d188..545f11c 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/enums/TcpHandlerType.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/cfg/enums/TcpHandlerType.java @@ -7,7 +7,7 @@ package sanbing.jcpp.protocol.cfg.enums; /** - * @author baigod + * @author 九筒 */ public enum TcpHandlerType { TEXT, diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/DownlinkCmdEnum.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/DownlinkCmdEnum.java index da07a1e..75aa1c5 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/DownlinkCmdEnum.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/DownlinkCmdEnum.java @@ -7,7 +7,7 @@ package sanbing.jcpp.protocol.domain; /** - * @author baigod + * @author 九筒 */ public enum DownlinkCmdEnum { diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/ProtocolSession.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/ProtocolSession.java index f403098..996988c 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/ProtocolSession.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/ProtocolSession.java @@ -11,6 +11,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import sanbing.jcpp.proto.gen.ProtocolProto; import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRequestMessage; import sanbing.jcpp.protocol.forwarder.Forwarder; @@ -26,7 +27,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.function.Function; /** - * @author baigod + * @author 九筒 */ @Getter @Slf4j @@ -65,15 +66,46 @@ public abstract class ProtocolSession implements Closeable { @Override public void close() { - close(SessionCloseReason.DESTRUCTION); + close(ProtocolProto.SessionCloseReason.SESSION_CLOSE_DESTRUCTION); } - public void close(SessionCloseReason reason) { + public void close(ProtocolProto.SessionCloseReason reason) { log.info("[{}] Protocol会话关闭,原因: {}", this, reason); scheduledFutures.values().forEach(scheduledFuture -> scheduledFuture.cancel(true)); scheduledFutures.clear(); + + // 转发会话关闭事件到后端 + if (forwarder != null && !pileCodeSet.isEmpty()) { + + for (String pileCode : pileCodeSet) { + ProtocolProto.SessionCloseEventProto sessionCloseEvent = ProtocolProto.SessionCloseEventProto.newBuilder() + .setPileCode(pileCode) + .setReason(reason) + .setAdditionalInfo("Session closed: " + reason) + .build(); + + ProtocolProto.UplinkQueueMessage uplinkQueueMessage = ProtocolProto.UplinkQueueMessage.newBuilder() + .setMessageIdMSB(UUID.randomUUID().getMostSignificantBits()) + .setMessageIdLSB(UUID.randomUUID().getLeastSignificantBits()) + .setSessionIdMSB(id.getMostSignificantBits()) + .setSessionIdLSB(id.getLeastSignificantBits()) + .setMessageKey(pileCode + "_session_close") + .setProtocolName(protocolName) + .setSessionCloseEventProto(sessionCloseEvent) + .build(); + + try { + forwarder.sendMessage(uplinkQueueMessage); + log.debug("[{}] 会话关闭事件已转发,桩编码: {}, 原因: {}", this, pileCode, reason); + } catch (Exception e) { + log.error("[{}] 转发会话关闭事件失败,桩编码: {}", this, pileCode, e); + } + } + } } + + @Override public String toString() { diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/SessionCloseReason.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/SessionCloseReason.java deleted file mode 100644 index 5604648..0000000 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/SessionCloseReason.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * 开源代码,仅供学习和交流研究使用,商用请联系三丙 - * 微信:mohan_88888 - * 抖音:程序员三丙 - * 付费课程知识星球:https://t.zsxq.com/aKtXo - */ -package sanbing.jcpp.protocol.domain; - -/** - * @author baigod - */ -public enum SessionCloseReason { - /** - * 自然销毁 - */ - DESTRUCTION, - - /** - * 失活 - */ - INACTIVE, - - /** - * 手动销毁 - */ - MANUALLY -} \ No newline at end of file diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/SessionToHandlerMsg.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/SessionToHandlerMsg.java index c5ab1ba..6e4784f 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/SessionToHandlerMsg.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/domain/SessionToHandlerMsg.java @@ -9,7 +9,7 @@ package sanbing.jcpp.protocol.domain; import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRequestMessage; /** - * @author baigod + * @author 九筒 */ public record SessionToHandlerMsg(DownlinkRequestMessage downlinkMsg, ProtocolSession session) { } \ No newline at end of file diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/enums/SupportedProtocols.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/enums/SupportedProtocols.java new file mode 100644 index 0000000..100ff8a --- /dev/null +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/enums/SupportedProtocols.java @@ -0,0 +1,75 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.protocol.enums; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 支持的协议定义类 + * 使用常量定义而非枚举,确保注解可以使用编译时常量 + * + * @author 九筒 + * @since 2024-12-22 + */ +public final class SupportedProtocols { + + private SupportedProtocols() { + // 工具类,禁止实例化 + } + + // ==================== 协议常量定义 ==================== + + /** 云快充协议 v1.5.0 */ + public static final String YUNKUAICHONG_V150 = "yunkuaichongV150"; + + /** 云快充协议 v1.6.0 */ + public static final String YUNKUAICHONG_V160 = "yunkuaichongV160"; + + /** 云快充协议 v1.7.0 */ + public static final String YUNKUAICHONG_V170 = "yunkuaichongV170"; + + /** 绿能协议 v3.4.0 */ + public static final String LVNENG_V340 = "lvnengV340"; + + // ==================== 协议显示名称映射 ==================== + + private static final Map PROTOCOL_DISPLAY_NAMES = new HashMap<>(); + + static { + // 协议ID与显示名称的映射关系,便于代码走读时对照 + PROTOCOL_DISPLAY_NAMES.put(YUNKUAICHONG_V150, "云快充 V1.5.0"); + PROTOCOL_DISPLAY_NAMES.put(YUNKUAICHONG_V160, "云快充 V1.6.0"); + PROTOCOL_DISPLAY_NAMES.put(YUNKUAICHONG_V170, "云快充 V1.7.0"); + PROTOCOL_DISPLAY_NAMES.put(LVNENG_V340, "绿能 V3.4.0"); + } + + // ==================== 工具方法 ==================== + + /** + * 获取所有支持的协议 + * 直接从映射表中获取,无需反射 + */ + public static List getAllProtocols() { + List protocols = new ArrayList<>(); + + for (Map.Entry entry : PROTOCOL_DISPLAY_NAMES.entrySet()) { + protocols.add(new ProtocolInfo(entry.getKey(), entry.getValue())); + } + + return protocols; + } + + /** + * 协议信息封装类 + */ + public record ProtocolInfo(String protocolId, String displayName) { + + } +} \ No newline at end of file diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/executor/ProtocolDownlinkCmdExe.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/executor/ProtocolDownlinkCmdExe.java index e13b40f..4baad34 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/executor/ProtocolDownlinkCmdExe.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/executor/ProtocolDownlinkCmdExe.java @@ -13,7 +13,7 @@ import sanbing.jcpp.protocol.domain.ProtocolSession; * 通用协议下行命令执行器接口 * * @param 下行消息类型 - * @author sanbing + * @author 九筒 * @since 2024-12-16 */ public interface ProtocolDownlinkCmdExe { diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/executor/ProtocolUplinkCmdExe.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/executor/ProtocolUplinkCmdExe.java index cce6563..d66cd72 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/executor/ProtocolUplinkCmdExe.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/executor/ProtocolUplinkCmdExe.java @@ -13,7 +13,7 @@ import sanbing.jcpp.protocol.domain.ProtocolSession; * 通用协议上行命令执行器接口 * * @param 上行消息类型 - * @author sanbing + * @author 九筒 * @since 2024-12-16 */ public interface ProtocolUplinkCmdExe { diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/Forwarder.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/Forwarder.java index 374dead..c6dca70 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/Forwarder.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/Forwarder.java @@ -30,18 +30,18 @@ import java.util.function.BiConsumer; import static sanbing.jcpp.infrastructure.queue.common.QueueConstants.*; /** - * @author baigod + * @author 九筒 */ @Slf4j public abstract class Forwarder { protected static final String ERROR = "error"; - AtomicBoolean healthy = new AtomicBoolean(true); + final AtomicBoolean healthy = new AtomicBoolean(true); @Getter private final String protocolName; - protected MessagesStats forwarderMessagesStats; + protected final MessagesStats forwarderMessagesStats; protected final PartitionProvider partitionProvider; diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/KafkaForwarder.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/KafkaForwarder.java index 2dcdd30..bd40723 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/KafkaForwarder.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/KafkaForwarder.java @@ -41,11 +41,11 @@ import java.util.function.BiConsumer; import static sanbing.jcpp.infrastructure.queue.common.QueueConstants.*; /** - * @author baigod + * @author 九筒 */ @Slf4j public class KafkaForwarder extends Forwarder { - AtomicBoolean healthy = new AtomicBoolean(true); + final AtomicBoolean healthy = new AtomicBoolean(true); private static final String OFFSET = "offset"; private static final String PARTITION = "partition"; diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/MemoryForwarder.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/MemoryForwarder.java index 5a1961e..19a72ae 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/MemoryForwarder.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/forwarder/MemoryForwarder.java @@ -20,7 +20,7 @@ import sanbing.jcpp.protocol.cfg.MemoryCfg; import java.util.function.BiConsumer; /** - * @author baigod + * @author 九筒 */ @Slf4j public class MemoryForwarder extends Forwarder { diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/ChannelHandlerInitializer.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/ChannelHandlerInitializer.java index 259d680..ef4dabc 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/ChannelHandlerInitializer.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/ChannelHandlerInitializer.java @@ -42,7 +42,7 @@ import static sanbing.jcpp.protocol.listener.tcp.configs.BinaryHandlerConfigurat import static sanbing.jcpp.protocol.listener.tcp.configs.TextHandlerConfiguration.SYSTEM_LINE_SEPARATOR; /** - * @author baigod + * @author 九筒 */ @Slf4j @RequiredArgsConstructor @@ -131,7 +131,7 @@ public abstract class ChannelHandlerInitializer extends Chann binaryHandlerConfig.getTail()); socketChannel.pipeline().addLast("JCPPHeadTailFrameDecoder", framer); } else { - throw new IllegalArgumentException("Unknown binary decoder"); + throw new IllegalArgumentException("未知的二进制解码器类型"); } socketChannel.pipeline() diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/Listener.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/Listener.java index c953a57..0eac723 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/Listener.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/Listener.java @@ -16,7 +16,7 @@ import sanbing.jcpp.protocol.ProtocolMessageProcessor; import java.util.concurrent.atomic.AtomicInteger; /** - * @author baigod + * @author 九筒 */ public abstract class Listener { @@ -26,10 +26,10 @@ public abstract class Listener { @Getter private final ProtocolMessageProcessor protocolMessageProcessor; - protected AtomicInteger connectionsGauge = new AtomicInteger(); - protected MessagesStats uplinkMsgStats; - protected MessagesStats downlinkMsgStats; - protected Timer downlinkTimer; + protected final AtomicInteger connectionsGauge = new AtomicInteger(); + protected final MessagesStats uplinkMsgStats; + protected final MessagesStats downlinkMsgStats; + protected final Timer downlinkTimer; protected final ChannelHandlerParameter parameter; diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/TcpChannelHandler.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/TcpChannelHandler.java index 85dfc4a..a05a5db 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/TcpChannelHandler.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/listener/tcp/TcpChannelHandler.java @@ -21,10 +21,10 @@ import sanbing.jcpp.infrastructure.util.exception.DownlinkException; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRequestMessage; +import sanbing.jcpp.proto.gen.ProtocolProto.SessionCloseReason; import sanbing.jcpp.protocol.ProtocolMessageProcessor; import sanbing.jcpp.protocol.domain.ListenerToHandlerMsg; import sanbing.jcpp.protocol.domain.ProtocolUplinkMsg; -import sanbing.jcpp.protocol.domain.SessionCloseReason; import sanbing.jcpp.protocol.domain.SessionToHandlerMsg; import sanbing.jcpp.protocol.listener.ChannelHandlerParameter; @@ -130,7 +130,7 @@ public class TcpChannelHandler extends SimpleChannelInboundHandler extends SimpleChannelInboundHandler logTransform) { - if (log.isDebugEnabled()) { - log.debug("[{}]{} 开始发送下行报文:{}", protocolName, tcpSession, logTransform.get()); + if (log.isInfoEnabled()) { + log.info("[{}]{} 开始发送下行报文:{}", protocolName, tcpSession, logTransform.get()); } } @@ -226,8 +226,9 @@ public class TcpChannelHandler extends SimpleChannelInboundHandler * 每个协议模块都应该实现此接口,提供从通用下行命令到协议特定命令的转换 * - * @author sanbing + * @author 九筒 * @since 2024-12-16 */ public interface DownlinkCmdConverter { diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/ProtocolSessionRegistryProvider.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/ProtocolSessionRegistryProvider.java index 3852265..c5d8ffd 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/ProtocolSessionRegistryProvider.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/ProtocolSessionRegistryProvider.java @@ -11,7 +11,7 @@ import sanbing.jcpp.protocol.domain.ProtocolSession; import java.util.UUID; /** - * @author baigod + * @author 九筒 */ public interface ProtocolSessionRegistryProvider { diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/ProtocolsConfigProvider.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/ProtocolsConfigProvider.java index 39ff4b6..71571ae 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/ProtocolsConfigProvider.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/ProtocolsConfigProvider.java @@ -9,7 +9,7 @@ package sanbing.jcpp.protocol.provider; import sanbing.jcpp.protocol.cfg.ProtocolCfg; /** - * @author baigod + * @author 九筒 */ public interface ProtocolsConfigProvider { diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/impl/DefaultProtocolSessionRegistryProvider.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/impl/DefaultProtocolSessionRegistryProvider.java index d85a17f..80eacd3 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/impl/DefaultProtocolSessionRegistryProvider.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/impl/DefaultProtocolSessionRegistryProvider.java @@ -16,8 +16,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import sanbing.jcpp.infrastructure.util.async.JCPPThreadFactory; import sanbing.jcpp.infrastructure.util.config.ThreadPoolConfiguration; +import sanbing.jcpp.proto.gen.ProtocolProto.SessionCloseReason; import sanbing.jcpp.protocol.domain.ProtocolSession; -import sanbing.jcpp.protocol.domain.SessionCloseReason; import sanbing.jcpp.protocol.provider.ProtocolSessionRegistryProvider; import java.time.LocalDateTime; @@ -28,7 +28,7 @@ import java.util.concurrent.TimeUnit; /** - * @author baigod + * @author 九筒 */ @Service @Slf4j @@ -51,7 +51,7 @@ public class DefaultProtocolSessionRegistryProvider implements ProtocolSessionRe public void init() { scheduledExecutorService.scheduleAtFixedRate(() -> sessionCache.asMap().forEach((id, session) -> { if (session.getLastActivityTime().isBefore(LocalDateTime.now().minusSeconds(defaultInactivityTimeoutInSec))) { - session.close(SessionCloseReason.INACTIVE); + session.close(SessionCloseReason.SESSION_CLOSE_DESTRUCTION); unregister(session.getId()); } }), defaultStateCheckIntervalInSec, defaultStateCheckIntervalInSec, TimeUnit.SECONDS); diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/impl/DefaultProtocolsConfigProvider.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/impl/DefaultProtocolsConfigProvider.java index dba5439..6d37e09 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/impl/DefaultProtocolsConfigProvider.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/provider/impl/DefaultProtocolsConfigProvider.java @@ -17,9 +17,9 @@ import sanbing.jcpp.protocol.provider.ProtocolsConfigProvider; import java.util.Map; +@Slf4j @Setter @Service -@Slf4j @ConfigurationProperties("service") public class DefaultProtocolsConfigProvider implements ProtocolsConfigProvider { diff --git a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/routing/ProtocolCommandRouter.java b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/routing/ProtocolCommandRouter.java index 8c739e3..f5d1915 100644 --- a/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/routing/ProtocolCommandRouter.java +++ b/jcpp-protocol-api/src/main/java/sanbing/jcpp/protocol/routing/ProtocolCommandRouter.java @@ -18,11 +18,11 @@ import java.util.function.Predicate; /** * 通用命令路由器 - * + *

* 提供基于协议名+命令字的路由功能,支持多版本协议 * * @param 命令执行器类型 - * @author sanbing + * @author 九筒 * @since 2025-08-25 */ @Slf4j @@ -79,7 +79,7 @@ public class ProtocolCommandRouter { } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { log.error("无法实例化命令执行器 {}: {}", executorClass.getName(), e.getMessage()); - throw new RuntimeException("Failed to instantiate command executor: " + executorClass.getName(), e); + throw new RuntimeException("实例化命令执行器失败: " + executorClass.getName(), e); } } diff --git a/jcpp-protocol-bootstrap/pom.xml b/jcpp-protocol-bootstrap/pom.xml index 696e97f..04e72bf 100644 --- a/jcpp-protocol-bootstrap/pom.xml +++ b/jcpp-protocol-bootstrap/pom.xml @@ -45,24 +45,6 @@ org.springframework.boot spring-boot-maven-plugin - - false - ZIP - sanbing.jcpp.protocol.JCPPProtocolServiceApplication - true - - true - ${project.basedir}/src/layers.xml - - - - - - repackage - build-info - - - org.apache.maven.plugins diff --git a/jcpp-protocol-bootstrap/src/layers.xml b/jcpp-protocol-bootstrap/src/layers.xml deleted file mode 100644 index 64e8aee..0000000 --- a/jcpp-protocol-bootstrap/src/layers.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - org/springframework/boot/loader/** - - - - - - - - - *:*:*SNAPSHOT - - - - - dependencies - spring-boot-loader - snapshot-dependencies - application - - diff --git a/jcpp-protocol-bootstrap/src/main/java/sanbing/jcpp/protocol/JCPPProtocolServiceApplication.java b/jcpp-protocol-bootstrap/src/main/java/sanbing/jcpp/protocol/JCPPProtocolServiceApplication.java index 5aa80ce..125d8b7 100644 --- a/jcpp-protocol-bootstrap/src/main/java/sanbing/jcpp/protocol/JCPPProtocolServiceApplication.java +++ b/jcpp-protocol-bootstrap/src/main/java/sanbing/jcpp/protocol/JCPPProtocolServiceApplication.java @@ -6,16 +6,20 @@ */ package sanbing.jcpp.protocol; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.Banner; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.core.Ordered; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; +import sanbing.jcpp.infrastructure.util.annotation.AfterStartUp; import java.util.Arrays; +import java.util.concurrent.TimeUnit; /** - * @author baigod + * @author 九筒 */ @SpringBootApplication(scanBasePackages = {"sanbing.jcpp.protocol", "sanbing.jcpp.infrastructure.stats", @@ -23,12 +27,16 @@ import java.util.Arrays; "sanbing.jcpp.infrastructure.util"}) @EnableAsync @EnableScheduling +@Slf4j public class JCPPProtocolServiceApplication { private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "protocol-service"; + private static long startTs; + public static void main(String[] args) { + startTs = System.currentTimeMillis(); new SpringApplicationBuilder(JCPPProtocolServiceApplication.class).bannerMode(Banner.Mode.LOG).run(updateArguments(args)); } @@ -41,4 +49,11 @@ public class JCPPProtocolServiceApplication { } return args; } + + @AfterStartUp(order = Ordered.LOWEST_PRECEDENCE) + public void afterStartUp() { + long startupTimeMs = System.currentTimeMillis() - startTs; + log.info("Started JChargePointProtocol Protocol Service in {} seconds", TimeUnit.MILLISECONDS.toSeconds(startupTimeMs)); + } + } \ No newline at end of file diff --git a/jcpp-protocol-bootstrap/src/main/resources/log4j2.xml b/jcpp-protocol-bootstrap/src/main/resources/log4j2.xml index 412fc0e..d06f73b 100644 --- a/jcpp-protocol-bootstrap/src/main/resources/log4j2.xml +++ b/jcpp-protocol-bootstrap/src/main/resources/log4j2.xml @@ -1,4 +1,12 @@ + diff --git a/jcpp-protocol-bootstrap/src/test/java/sanbing/jcpp/protocol/AbstractProtocolTestBase.java b/jcpp-protocol-bootstrap/src/test/java/sanbing/jcpp/protocol/AbstractProtocolTestBase.java index 586519d..b01268e 100644 --- a/jcpp-protocol-bootstrap/src/test/java/sanbing/jcpp/protocol/AbstractProtocolTestBase.java +++ b/jcpp-protocol-bootstrap/src/test/java/sanbing/jcpp/protocol/AbstractProtocolTestBase.java @@ -6,19 +6,18 @@ */ package sanbing.jcpp.protocol; +import jakarta.annotation.Resource; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestMethodOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.core.env.Environment; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; /** - * @author baigod + * @author 九筒 */ @ActiveProfiles("test") @SpringBootTest(classes = JCPPProtocolServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @@ -33,9 +32,7 @@ public class AbstractProtocolTestBase { protected final Logger log = LoggerFactory.getLogger(this.getClass()); - @Autowired + @Resource protected MockMvc mockMvc; - @Autowired - protected Environment environment; } \ No newline at end of file diff --git a/jcpp-protocol-bootstrap/src/test/java/sanbing/jcpp/protocol/adapter/DownlinkControllerIT.java b/jcpp-protocol-bootstrap/src/test/java/sanbing/jcpp/protocol/adapter/DownlinkControllerIT.java index 5cc4e97..e2223e7 100644 --- a/jcpp-protocol-bootstrap/src/test/java/sanbing/jcpp/protocol/adapter/DownlinkControllerIT.java +++ b/jcpp-protocol-bootstrap/src/test/java/sanbing/jcpp/protocol/adapter/DownlinkControllerIT.java @@ -22,8 +22,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; import sanbing.jcpp.infrastructure.util.property.PropertyUtils; -import sanbing.jcpp.proto.gen.ProtocolProto; import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRequestMessage; +import sanbing.jcpp.proto.gen.ProtocolProto.RemoteStartChargingRequest; import sanbing.jcpp.protocol.AbstractProtocolTestBase; import sanbing.jcpp.protocol.domain.DownlinkCmdEnum; import sanbing.jcpp.protocol.domain.ProtocolSession; @@ -61,12 +61,7 @@ class DownlinkControllerIT extends AbstractProtocolTestBase { BinaryHandlerConfiguration binaryHandlerConfig = JacksonUtil.treeToValue(cfgJson, BinaryHandlerConfiguration.class); - ByteOrder byteOrder = LITTLE_ENDIAN_BYTE_ORDER.equalsIgnoreCase(binaryHandlerConfig.getByteOrder()) - ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; - - JCPPLengthFieldBasedFrameDecoder framer = new JCPPLengthFieldBasedFrameDecoder(binaryHandlerConfig.getHead(), byteOrder, - binaryHandlerConfig.getLengthFieldOffset(), binaryHandlerConfig.getLengthFieldLength(), - binaryHandlerConfig.getLengthAdjustment(), binaryHandlerConfig.getInitialBytesToStrip()); + JCPPLengthFieldBasedFrameDecoder framer = getJcppLengthFieldBasedFrameDecoder(binaryHandlerConfig); group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); @@ -92,6 +87,15 @@ class DownlinkControllerIT extends AbstractProtocolTestBase { channel = f.channel(); } + private static JCPPLengthFieldBasedFrameDecoder getJcppLengthFieldBasedFrameDecoder(BinaryHandlerConfiguration binaryHandlerConfig) { + ByteOrder byteOrder = LITTLE_ENDIAN_BYTE_ORDER.equalsIgnoreCase(binaryHandlerConfig.getByteOrder()) + ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; + + return new JCPPLengthFieldBasedFrameDecoder(binaryHandlerConfig.getHead(), byteOrder, + binaryHandlerConfig.getLengthFieldOffset(), binaryHandlerConfig.getLengthFieldLength(), + binaryHandlerConfig.getLengthAdjustment(), binaryHandlerConfig.getInitialBytesToStrip()); + } + @AfterEach void tearDown() { if (channel != null) { @@ -130,7 +134,7 @@ class DownlinkControllerIT extends AbstractProtocolTestBase { .setRequestIdMSB(requestId.getMostSignificantBits()) .setRequestIdLSB(requestId.getLeastSignificantBits()) .setDownlinkCmd(DownlinkCmdEnum.REMOTE_START_CHARGING.name()) - .setRemoteStartChargingRequest(ProtocolProto.RemoteStartChargingRequest.newBuilder() + .setRemoteStartChargingRequest(RemoteStartChargingRequest.newBuilder() .setPileCode(pileCode) .setGunCode("01") .setLimitYuan("100") diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDownlinkCmdExe.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDownlinkCmdExe.java index a02c3ba..84ce6a8 100644 --- a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDownlinkCmdExe.java +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDownlinkCmdExe.java @@ -10,7 +10,7 @@ import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.listener.tcp.TcpSession; /** - * @author baigod + * @author 九筒 */ public abstract class LvnengDownlinkCmdExe extends AbstractLvnengCmdExe { diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDwonlinkMessage.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDwonlinkMessage.java index 5e3c007..1c9a7f9 100644 --- a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDwonlinkMessage.java +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengDwonlinkMessage.java @@ -15,7 +15,7 @@ import java.io.Serializable; import java.util.UUID; /** - * @author baigod + * @author 九筒 */ @Data @Accessors(chain = true) diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolConstants.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolConstants.java index 33bd4d0..edac63f 100644 --- a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolConstants.java +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolConstants.java @@ -6,10 +6,12 @@ */ package sanbing.jcpp.protocol.lvneng; +import sanbing.jcpp.protocol.enums.SupportedProtocols; + /** * 绿能协议常量定义 * - * @author sanbing + * @author 九筒 * @since 2024-12-16 */ public final class LvnengProtocolConstants { @@ -23,7 +25,7 @@ public final class LvnengProtocolConstants { */ public static final class ProtocolNames { /** 绿能协议 v3.4.0 */ - public static final String LVNENG_V340 = "lvnengV340"; + public static final String LVNENG_V340 = SupportedProtocols.LVNENG_V340; // 注解专用简短别名 public static final String V340 = LVNENG_V340; diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolMessageProcessor.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolMessageProcessor.java index 4804166..30d0e5a 100644 --- a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolMessageProcessor.java +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengProtocolMessageProcessor.java @@ -13,7 +13,7 @@ import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.JCPPPair; import sanbing.jcpp.infrastructure.util.codec.ByteUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.proto.gen.ProtocolProto; +import sanbing.jcpp.proto.gen.ProtocolProto.DownlinkRequestMessage; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.ProtocolMessageProcessor; import sanbing.jcpp.protocol.domain.DownlinkCmdEnum; @@ -122,10 +122,10 @@ public class LvnengProtocolMessageProcessor extends ProtocolMessageProcessor { } @Override - protected void downlinkHandle(SessionToHandlerMsg sessionToHandlerMsg) { + protected void doDownlinkHandle(SessionToHandlerMsg sessionToHandlerMsg) { TcpSession session = (TcpSession) sessionToHandlerMsg.session(); - ProtocolProto.DownlinkRequestMessage protocolDownlinkMsg = sessionToHandlerMsg.downlinkMsg(); + DownlinkRequestMessage protocolDownlinkMsg = sessionToHandlerMsg.downlinkMsg(); DownlinkCmdEnum downlinkCmd = DownlinkCmdEnum.valueOf(protocolDownlinkMsg.getDownlinkCmd()); diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengUplinkCmdExe.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengUplinkCmdExe.java index 53a9203..db658bd 100644 --- a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengUplinkCmdExe.java +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/LvnengUplinkCmdExe.java @@ -9,12 +9,13 @@ package sanbing.jcpp.protocol.lvneng; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; +import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.listener.tcp.TcpSession; /** - * @author baigod + * @author 九筒 */ @Slf4j public abstract class LvnengUplinkCmdExe extends AbstractLvnengCmdExe { @@ -22,11 +23,15 @@ public abstract class LvnengUplinkCmdExe extends AbstractLvnengCmdExe { public abstract void execute(TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage, ProtocolContext ctx); protected UplinkQueueMessage.Builder uplinkMessageBuilder(String messageKey, TcpSession tcpSession, LvnengUplinkMessage lvnengUplinkMessage) { + // 从Tracer总获取当前时间 + long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); + return UplinkQueueMessage.newBuilder() .setMessageIdMSB(lvnengUplinkMessage.getId().getMostSignificantBits()) .setMessageIdLSB(lvnengUplinkMessage.getId().getLeastSignificantBits()) .setSessionIdMSB(tcpSession.getId().getMostSignificantBits()) .setSessionIdLSB(tcpSession.getId().getLeastSignificantBits()) + .setTs(ts) .setRequestData(ByteString.copyFrom(JacksonUtil.writeValueAsBytes(lvnengUplinkMessage))) .setMessageKey(messageKey) .setProtocolName(tcpSession.getProtocolName()); diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/mapping/LvnengDownlinkCmdConverter.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/mapping/LvnengDownlinkCmdConverter.java index f5cd6aa..d99ac0d 100644 --- a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/mapping/LvnengDownlinkCmdConverter.java +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/mapping/LvnengDownlinkCmdConverter.java @@ -14,13 +14,13 @@ import java.util.concurrent.ConcurrentHashMap; /** * 绿能协议下行命令转换器(单例) - * + *

* 建立通用下行命令与绿能协议特定命令字的显式转换关系 * 使用Map存储转换关系,提供O(1)性能 - * + *

* 采用单例模式,避免重复实例化 * - * @author sanbing + * @author 九筒 * @since 2024-12-16 */ public class LvnengDownlinkCmdConverter implements DownlinkCmdConverter { diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340LoginAckDLCmd.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340LoginAckDLCmd.java index 4a66c85..2873d52 100644 --- a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340LoginAckDLCmd.java +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340LoginAckDLCmd.java @@ -28,9 +28,9 @@ import java.util.Arrays; import java.util.concurrent.TimeUnit; import static sanbing.jcpp.infrastructure.util.config.ThreadPoolConfiguration.PROTOCOL_SESSION_SCHEDULED; +import static sanbing.jcpp.proto.gen.ProtocolProto.SessionCloseReason.SESSION_CLOSE_MANUALLY; import static sanbing.jcpp.protocol.domain.DownlinkCmdEnum.LOGIN_ACK; import static sanbing.jcpp.protocol.domain.DownlinkCmdEnum.SYNC_TIME_REQUEST; -import static sanbing.jcpp.protocol.domain.SessionCloseReason.MANUALLY; import static sanbing.jcpp.protocol.listener.tcp.TcpSession.SCHEDULE_KEY_AUTO_SYNC_TIME; import static sanbing.jcpp.protocol.lvneng.LvnengProtocolConstants.ProtocolNames.V340; @@ -74,7 +74,7 @@ public class LvnengV340LoginAckDLCmd extends LvnengDownlinkCmdExe { loginAck(tcpSession, requestData, new byte[]{0x00, 0x00, 0x00, 0x00}); // 断开连接 - tcpSession.close(MANUALLY); + tcpSession.close(SESSION_CLOSE_MANUALLY); } } diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340RealTimeDataULCmd.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340RealTimeDataULCmd.java index 6114091..00157d9 100644 --- a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340RealTimeDataULCmd.java +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340RealTimeDataULCmd.java @@ -13,8 +13,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; -import sanbing.jcpp.proto.gen.ProtocolProto; +import sanbing.jcpp.proto.gen.ProtocolProto.ChargingProgressProto; import sanbing.jcpp.proto.gen.ProtocolProto.GunRunStatus; import sanbing.jcpp.proto.gen.ProtocolProto.GunRunStatusProto; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; @@ -50,8 +49,6 @@ public class LvnengV340RealTimeDataULCmd extends LvnengUplinkCmdExe { ByteBuf byteBuf = Unpooled.wrappedBuffer(lvnengUplinkMessage.getMsgBody()); ObjectNode additionalInfo = JacksonUtil.newObjectNode(); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); //1预留 byteBuf.skipBytes(2); @@ -84,8 +81,8 @@ public class LvnengV340RealTimeDataULCmd extends LvnengUplinkCmdExe { byte soc = byteBuf.readByte(); additionalInfo.put("soc", soc); - /** 9 告警码,0-无告警 非0参靠枚举类 - * @see LvnengAlarmCodeEnum + /* 9 告警码,0-无告警 非0参靠枚举类 + @see LvnengAlarmCodeEnum */ long alarmCode = byteBuf.readUnsignedInt(); String alarmCodeDesc = alarmCode == 0L ? "" : LvnengAlarmCodeEnum.getByCode(alarmCode); @@ -231,7 +228,6 @@ public class LvnengV340RealTimeDataULCmd extends LvnengUplinkCmdExe { // 抢状态 GunRunStatus gunRunStatus = parseGunRunStatus(pileStatus); GunRunStatusProto gunRunStatusProto = GunRunStatusProto.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setGunCode(gunCode + "") .setGunRunStatus(gunRunStatus) @@ -249,8 +245,7 @@ public class LvnengV340RealTimeDataULCmd extends LvnengUplinkCmdExe { if (StringUtils.isNotBlank(tradeNo)) { // 充电进度 - ProtocolProto.ChargingProgressProto.Builder chargingProgressProtoBuilder = ProtocolProto.ChargingProgressProto.newBuilder() - .setTs(ts) + ChargingProgressProto.Builder chargingProgressProtoBuilder = ChargingProgressProto.newBuilder() .setPileCode(pileCode) .setGunCode(gunCode + "") .setTradeNo(tradeNo) diff --git a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordULCmd.java b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordULCmd.java index af7b25c..6eee3de 100644 --- a/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordULCmd.java +++ b/jcpp-protocol-lvneng/src/main/java/sanbing/jcpp/protocol/lvneng/v340/cmd/LvnengV340TransactionRecordULCmd.java @@ -13,12 +13,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.proto.gen.ProtocolProto; -import sanbing.jcpp.proto.gen.ProtocolProto.TimePeriodDetail; +import sanbing.jcpp.proto.gen.ProtocolProto.*; import sanbing.jcpp.proto.gen.ProtocolProto.TimePeriodDetail.PeriodItem; -import sanbing.jcpp.proto.gen.ProtocolProto.TransactionDetail; -import sanbing.jcpp.proto.gen.ProtocolProto.TransactionRecordRequest; -import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.annotation.ProtocolCmd; import sanbing.jcpp.protocol.listener.tcp.TcpSession; @@ -189,7 +185,7 @@ public class LvnengV340TransactionRecordULCmd extends LvnengUplinkCmdExe { .build(); //订单明细 TransactionDetail transactionDetail = TransactionDetail.newBuilder() - .setType(ProtocolProto.DetailType.TIME_PERIOD) + .setType(DetailType.TIME_PERIOD) .setTimePeriod(timePeriodDetail) .build(); diff --git a/jcpp-protocol-yunkuaichong/READMD.md b/jcpp-protocol-yunkuaichong/READMD.md index ecdc196..0c53860 100644 --- a/jcpp-protocol-yunkuaichong/READMD.md +++ b/jcpp-protocol-yunkuaichong/READMD.md @@ -73,6 +73,9 @@ --- +#### 0x23 充电过程BMS需求与充电机输出 +`68 30 6c 40 00 23 20 23 12 12 00 00 10 01 25 08 27 23 03 54 04 67 20 23 12 12 00 00 10 01 a6 13 48 17 02 8d 12 12 14 58 01 53 11 00 92 12 32 14 00 00 b2 d8` + #### 0x25 充电过程BMS信息 `68 31 01 00 00 25 32 01 02 00 00 00 00 11 15 11 16 15 55 35 02 60 20 23 12 12 00 00 10 01 01 01 01 01 01 01 00 01 00 01 00 01 00 01 00 01 00 01 00 00 00 BE C8` @@ -113,14 +116,17 @@ #### 0x41 远程账户余额更新应答 `68 14 19 00 00 41 20 23 12 12 00 00 10 10 00 00 00 00 12 34 56 00 bc 16` +--- + #### 0x44 离线卡数据同步 `68 3f 02 00 00 44 20 23 12 12 00 00 10 03 00 00 00 10 00 00 00 00 12 34 56 10 00 00 00 00 12 34 56 10 00 00 00 00 12 34 57 10 00 00 00 00 12 34 57 10 00 00 00 00 12 34 58 10 00 00 00 00 12 34 58 ba 94` #### 0x43 离线卡数据同步应答 -#### 成功 +##### 成功 `68 0d 19 00 00 43 20 23 12 12 00 00 10 01 00 38 83` -#### 失败 +##### 失败 `68 0d 19 00 00 43 20 23 12 12 00 00 10 00 01 f8 d3` + --- #### 0x56 对时设置 @@ -128,3 +134,5 @@ #### 0x55 对时设置应答 `68 12 01 00 00 55 20 23 12 12 00 00 10 E0 2E 0C 0C 15 08 19 AB 37` + + diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/AbstractYunKuaiChongCmdExe.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/AbstractYunKuaiChongCmdExe.java index 77bfdad..382bfc7 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/AbstractYunKuaiChongCmdExe.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/AbstractYunKuaiChongCmdExe.java @@ -11,7 +11,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.apache.commons.lang3.StringUtils; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; -import sanbing.jcpp.proto.gen.ProtocolProto; +import sanbing.jcpp.proto.gen.ProtocolProto.PeriodProto; import sanbing.jcpp.protocol.domain.DownlinkCmdEnum; import sanbing.jcpp.protocol.listener.tcp.TcpSession; import sanbing.jcpp.protocol.listener.tcp.enums.SequenceNumberLength; @@ -29,7 +29,7 @@ import static sanbing.jcpp.infrastructure.util.codec.ByteUtil.crcSum; import static sanbing.jcpp.infrastructure.util.codec.ByteUtil.toBytes; /** - * @author baigod + * @author 九筒 */ public class AbstractYunKuaiChongCmdExe { @@ -61,8 +61,8 @@ public class AbstractYunKuaiChongCmdExe { return BCDUtil.toBytes(PRICING_ID_DECIMAL_FORMAT.format(pricingId % 10000)); } - protected static byte getFlagForCurrentTime(List periodList, LocalTime currentTime) { - for (ProtocolProto.PeriodProto period : periodList) { + protected static byte getFlagForCurrentTime(List periodList, LocalTime currentTime) { + for (PeriodProto period : periodList) { LocalTime beginLt = LocalTime.parse(period.getBegin()); LocalTime endLt = "00:00".equals(period.getEnd()) ? LocalTime.MAX : LocalTime.parse(period.getEnd()); if ((currentTime.equals(beginLt) || currentTime.isAfter(beginLt)) && currentTime.isBefore(endLt)) { @@ -218,7 +218,7 @@ public class AbstractYunKuaiChongCmdExe { */ protected static void writeParamFillZero(ByteBuf target, String param, int maxLength) { if (maxLength <= 0) { - throw new IllegalArgumentException("maxLength must be positive"); + throw new IllegalArgumentException("最大长度必须为正数"); } if (param == null) { @@ -257,7 +257,7 @@ public class AbstractYunKuaiChongCmdExe { */ protected static void writeParamFillZero(ByteBuf target, int param, int maxLength) { if (maxLength <= 0) { - throw new IllegalArgumentException("maxLength must be positive"); + throw new IllegalArgumentException("最大长度必须为正数"); } // 确保目标ByteBuf有足够的写入空间 diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongDownlinkCmdExe.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongDownlinkCmdExe.java index f5797ec..18d12c9 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongDownlinkCmdExe.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongDownlinkCmdExe.java @@ -10,7 +10,7 @@ import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.listener.tcp.TcpSession; /** - * @author baigod + * @author 九筒 */ public abstract class YunKuaiChongDownlinkCmdExe extends AbstractYunKuaiChongCmdExe { diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongDwonlinkMessage.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongDwonlinkMessage.java index 8f1440d..c2df4cb 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongDwonlinkMessage.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongDwonlinkMessage.java @@ -15,7 +15,7 @@ import java.io.Serializable; import java.util.UUID; /** - * @author baigod + * @author 九筒 */ @Data @Accessors(chain = true) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolConstants.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolConstants.java index 7f19e0a..4283bfe 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolConstants.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolConstants.java @@ -6,10 +6,12 @@ */ package sanbing.jcpp.protocol.yunkuaichong; +import sanbing.jcpp.protocol.enums.SupportedProtocols; + /** * 云快充协议常量定义 * - * @author sanbing + * @author 九筒 * @since 2024-12-16 */ public final class YunKuaiChongProtocolConstants { @@ -23,13 +25,13 @@ public final class YunKuaiChongProtocolConstants { */ public static final class ProtocolNames { /** 云快充协议 v1.5.0 */ - public static final String YUNKUAICHONG_V150 = "yunkuaichongV150"; + public static final String YUNKUAICHONG_V150 = SupportedProtocols.YUNKUAICHONG_V150; /** 云快充协议 v1.6.0 */ - public static final String YUNKUAICHONG_V160 = "yunkuaichongV160"; + public static final String YUNKUAICHONG_V160 = SupportedProtocols.YUNKUAICHONG_V160; /** 云快充协议 v1.7.0 */ - public static final String YUNKUAICHONG_V170 = "yunkuaichongV170"; + public static final String YUNKUAICHONG_V170 = SupportedProtocols.YUNKUAICHONG_V170; // 注解专用简短别名 public static final String V150 = YUNKUAICHONG_V150; @@ -40,4 +42,4 @@ public final class YunKuaiChongProtocolConstants { // 工具类,禁止实例化 } } -} +} \ No newline at end of file diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolMessageProcessor.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolMessageProcessor.java index d741b9d..2edb4eb 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolMessageProcessor.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongProtocolMessageProcessor.java @@ -137,7 +137,7 @@ public class YunKuaiChongProtocolMessageProcessor extends ProtocolMessageProcess } @Override - public void downlinkHandle(SessionToHandlerMsg sessionToHandlerMsg) { + protected void doDownlinkHandle(SessionToHandlerMsg sessionToHandlerMsg) { TcpSession session = (TcpSession) sessionToHandlerMsg.session(); DownlinkRequestMessage protocolDownlinkMsg = sessionToHandlerMsg.downlinkMsg(); diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongUplinkCmdExe.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongUplinkCmdExe.java index ec3a801..cd6b046 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongUplinkCmdExe.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/YunKuaiChongUplinkCmdExe.java @@ -9,12 +9,13 @@ package sanbing.jcpp.protocol.yunkuaichong; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; +import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.listener.tcp.TcpSession; /** - * @author baigod + * @author 九筒 */ @Slf4j public abstract class YunKuaiChongUplinkCmdExe extends AbstractYunKuaiChongCmdExe { @@ -22,11 +23,15 @@ public abstract class YunKuaiChongUplinkCmdExe extends AbstractYunKuaiChongCmdEx public abstract void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx); protected UplinkQueueMessage.Builder uplinkMessageBuilder(String messageKey, TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage) { + // 从Tracer总获取当前时间 + long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); + return UplinkQueueMessage.newBuilder() .setMessageIdMSB(yunKuaiChongUplinkMessage.getId().getMostSignificantBits()) .setMessageIdLSB(yunKuaiChongUplinkMessage.getId().getLeastSignificantBits()) .setSessionIdMSB(tcpSession.getId().getMostSignificantBits()) .setSessionIdLSB(tcpSession.getId().getLeastSignificantBits()) + .setTs(ts) .setRequestData(ByteString.copyFrom(JacksonUtil.writeValueAsBytes(yunKuaiChongUplinkMessage))) .setMessageKey(messageKey) .setProtocolName(tcpSession.getProtocolName()); diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/mapping/YunKuaiChongDownlinkCmdConverter.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/mapping/YunKuaiChongDownlinkCmdConverter.java index b1a7428..27a08c4 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/mapping/YunKuaiChongDownlinkCmdConverter.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/mapping/YunKuaiChongDownlinkCmdConverter.java @@ -14,13 +14,13 @@ import java.util.concurrent.ConcurrentHashMap; /** * 云快充协议下行命令转换器(单例) - * + *

* 建立通用下行命令与云快充协议特定命令字的显式转换关系 * 使用Map存储转换关系,提供O(1)性能 - * + *

* 采用单例模式,避免重复实例化 * - * @author sanbing + * @author 九筒 * @since 2024-12-16 */ public class YunKuaiChongDownlinkCmdConverter implements DownlinkCmdConverter { diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/YunkuaichongV150ProtocolBootstrap.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/YunkuaichongV150ProtocolBootstrap.java index e1d436e..b7b1cf8 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/YunkuaichongV150ProtocolBootstrap.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/YunkuaichongV150ProtocolBootstrap.java @@ -15,7 +15,7 @@ import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolMessageProcessor; import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.YUNKUAICHONG_V150; /** - * @author baigod + * @author 九筒 */ @ProtocolComponent(YUNKUAICHONG_V150) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsAbortULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsAbortULCmd.java index 562a028..86383ab 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsAbortULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsAbortULCmd.java @@ -14,7 +14,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.BmsAbortProto; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; @@ -40,9 +39,6 @@ public class YunKuaiChongV150BmsAbortULCmd extends YunKuaiChongUplinkCmdExe { log.debug("{} 云快充1.5.0充电阶段BMS中止", tcpSession); ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); - ObjectNode additionalInfo = JacksonUtil.newObjectNode(); // 1.交易流水号 byte[] tradeNoBytes = new byte[16]; @@ -71,7 +67,6 @@ public class YunKuaiChongV150BmsAbortULCmd extends YunKuaiChongUplinkCmdExe { additionalInfo.put("BMS中止充电错误原因", parseErrorReasons(errorReasonByte)); BmsAbortProto proto = BmsAbortProto.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setGunCode(gunCode) .setTradeNo(tradeNo) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsChargingErrorULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsChargingErrorULCmd.java index d25d13b..bdedfe6 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsChargingErrorULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsChargingErrorULCmd.java @@ -12,7 +12,6 @@ import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.BmsChargingErrorProto; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; @@ -34,8 +33,6 @@ public class YunKuaiChongV150BmsChargingErrorULCmd extends YunKuaiChongUplinkCmd public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) { log.debug("{} 云快充1.5.0 充电桩与 BMS 充电错误报文", tcpSession); ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); ObjectNode additionalInfo = JacksonUtil.newObjectNode(); byte[] tradeNoBytes = new byte[16]; @@ -118,7 +115,6 @@ public class YunKuaiChongV150BmsChargingErrorULCmd extends YunKuaiChongUplinkCmd // 转发到后端 BmsChargingErrorProto bmsChargingErrorProto = BmsChargingErrorProto.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setTradeNo(tradeNo) .setGunCode(gunCode) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsChargingInfoULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsChargingInfoULCmd.java index c9cbeb5..ed95e72 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsChargingInfoULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsChargingInfoULCmd.java @@ -12,7 +12,6 @@ import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.BmsChargingInfoProto; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; @@ -35,8 +34,6 @@ public class YunKuaiChongV150BmsChargingInfoULCmd extends YunKuaiChongUplinkCmdE public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) { log.debug("{} 云快充1.5.0充电过程BMS信息", tcpSession); ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); ObjectNode additionalInfo = JacksonUtil.newObjectNode(); // 1.交易流水号 @@ -79,7 +76,6 @@ public class YunKuaiChongV150BmsChargingInfoULCmd extends YunKuaiChongUplinkCmdE byteBuf.skipBytes(2); BmsChargingInfoProto bmsCharingInfoProto = BmsChargingInfoProto.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setTradeNo(tradeNo) .setGunCode(gunCode) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsDemandChargerOutputULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsDemandChargerOutputULCmd.java new file mode 100644 index 0000000..933fcb7 --- /dev/null +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsDemandChargerOutputULCmd.java @@ -0,0 +1,110 @@ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +package sanbing.jcpp.protocol.yunkuaichong.v150.cmd; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.extern.slf4j.Slf4j; +import sanbing.jcpp.infrastructure.util.codec.BCDUtil; +import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; +import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; +import sanbing.jcpp.proto.gen.ProtocolProto.BmsDemandChargerOutputProto; +import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; +import sanbing.jcpp.protocol.ProtocolContext; +import sanbing.jcpp.protocol.annotation.ProtocolCmd; +import sanbing.jcpp.protocol.listener.tcp.TcpSession; +import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkCmdExe; +import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage; + +import java.math.BigDecimal; + +import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.*; + +/** + * 云快充1.5.0 充电过程BMS需求与充电机输出 + */ +@Slf4j +@ProtocolCmd(value = 0x23, protocolNames = {V150, V160, V170}) +public class YunKuaiChongV150BmsDemandChargerOutputULCmd extends YunKuaiChongUplinkCmdExe { + + @Override + public void execute(TcpSession tcpSession, YunKuaiChongUplinkMessage yunKuaiChongUplinkMessage, ProtocolContext ctx) { + log.debug("{} 云快充1.5.0充电过程BMS需求与充电机输出", tcpSession); + ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); + + // 从Tracer总获取当前时间 + long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); + + ObjectNode additionalInfo = JacksonUtil.newObjectNode(); + + // 1.交易流水号 + byte[] tradeNoBytes = new byte[16]; + byteBuf.readBytes(tradeNoBytes); + String tradeNo = BCDUtil.toString(tradeNoBytes); + + // 2.桩编号 + byte[] pileCodeBytes = new byte[7]; + byteBuf.readBytes(pileCodeBytes); + String pileCode = BCDUtil.toString(pileCodeBytes); + + // 3.枪号 + byte gunCodeByte = byteBuf.readByte(); + String gunCode = BCDUtil.toString(gunCodeByte); + + // 4.BMS电压需求 0.1 V/位, 0 V 偏移量 + additionalInfo.put("BMS电压需求", reduceMagnification(byteBuf.readUnsignedShortLE(), 10)); + + // 5.BMS电流需求 0.1 A/位, -400 A 偏移量 + additionalInfo.put("BMS电流需求", reduceMagnification(byteBuf.readUnsignedShortLE(), 10).subtract(new BigDecimal("400"))); + + // 6.BMS充电模式 0x01:恒压充电; 0x02:恒流充电 + additionalInfo.put("BMS充电模式", byteBuf.readByte() == 0x01 ? "恒压充电" : "恒流充电"); + + // 7.BMS充电电压测量值 0.1 V/位, 0 V 偏移量 + additionalInfo.put("BMS充电电压测量值", reduceMagnification(byteBuf.readUnsignedShortLE(), 10)); + + // 8.BMS充电电流测量值 0.1 A/位, -400 A 偏移量 + additionalInfo.put("BMS充电电流测量值", reduceMagnification(byteBuf.readUnsignedShortLE(), 10).subtract(new BigDecimal("400"))); + + // 9.BMS最高单体动力蓄电池电压及组号 + int i = byteBuf.readUnsignedShortLE(); + // 1-12 位:最高单体动力蓄电池电压,数据分辨率: 0.01V/位,0V偏移量;数据范围:0~24V + additionalInfo.put("BMS最高单体动力蓄电池电压", reduceMagnification(i & 0x0FFF, 100)); + // 13-16 位: 最高单体动力蓄电池电压所在组号,数据分辨率: 1/位, 0 偏移量;数据范围:0~15 + additionalInfo.put("BMS最高单体动力蓄电池电压所在组号", (i >> 12) & 0x0F); + + // 10.BMS当前荷电状态 SOC( %) + additionalInfo.put("BMS当前荷电状态SOC", byteBuf.readByte()); + + // 11.BMS 估算剩余充电时间 + additionalInfo.put("BMS估算剩余充电时间", byteBuf.readUnsignedShortLE()); + + // 12.电桩电压输出值 + additionalInfo.put("电桩电压输出值", reduceMagnification(byteBuf.readUnsignedShortLE(), 10)); + + // 13.电桩电流输出值 + additionalInfo.put("电桩电流输出值", reduceMagnification(byteBuf.readUnsignedShortLE(), 10).subtract(new BigDecimal("400"))); + + // 14.累计充电时间 + additionalInfo.put("累计充电时间", byteBuf.readUnsignedShortLE()); + + BmsDemandChargerOutputProto proto = BmsDemandChargerOutputProto.newBuilder() + .setPileCode(pileCode) + .setGunCode(gunCode) + .setTradeNo(tradeNo) + .setAdditionalInfo(additionalInfo.toString()) + .build(); + + // 转发到后端 + UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(pileCode, tcpSession, yunKuaiChongUplinkMessage) + .setBmsDemandChargerOutputProto(proto) + .build(); + + tcpSession.getForwarder().sendMessage(uplinkQueueMessage); + } +} diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsHandshakeULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsHandshakeULCmd.java index 074d43e..c52913a 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsHandshakeULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsHandshakeULCmd.java @@ -13,7 +13,6 @@ import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.BmsHandshakeProto; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; @@ -29,7 +28,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0 充电握手 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x15, protocolNames = {V150, V160, V170}) @@ -39,9 +38,6 @@ public class YunKuaiChongV150BmsHandshakeULCmd extends YunKuaiChongUplinkCmdExe log.debug("{} 云快充1.5.0充电握手", tcpSession); ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); - ObjectNode additionalInfo = JacksonUtil.newObjectNode(); // 1.交易流水号 @@ -108,7 +104,6 @@ public class YunKuaiChongV150BmsHandshakeULCmd extends YunKuaiChongUplinkCmdExe // 构建BmsHandshakeProto对象 BmsHandshakeProto bmsHandshakeProto = BmsHandshakeProto.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setGunCode(gunCode) .setTradeNo(tradeNo) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsParamConfigReportULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsParamConfigReportULCmd.java index a642350..c7077c7 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsParamConfigReportULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150BmsParamConfigReportULCmd.java @@ -10,7 +10,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.BmsParamConfigReportProto; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; @@ -46,9 +45,6 @@ public class YunKuaiChongV150BmsParamConfigReportULCmd extends YunKuaiChongUplin log.info("{} 云快充1.5.0充电桩参数配置帧请求", tcpSession); // 将消息体包装为ByteBuf以便读取 ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); - /* 按协议顺序解析消息体 */ // 1. 交易流水号:16字节BCD编码字符串 @@ -95,7 +91,6 @@ public class YunKuaiChongV150BmsParamConfigReportULCmd extends YunKuaiChongUplin // 转发到后端 BmsParamConfigReportProto bmsParamConfigReportProto = BmsParamConfigReportProto.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setTradeNo(tradeNo) .setGunCode(gunCode) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150HeartbeatULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150HeartbeatULCmd.java index 2f7ba0a..112bd57 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150HeartbeatULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150HeartbeatULCmd.java @@ -26,7 +26,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0 充电桩心跳包 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x03, protocolNames = {V150, V160, V170}) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LockStatusULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LockStatusULCmd.java index d87550b..4198bbb 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LockStatusULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LockStatusULCmd.java @@ -10,7 +10,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.GroundLockStatusProto; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; @@ -40,8 +39,6 @@ public class YunKuaiChongV150LockStatusULCmd extends YunKuaiChongUplinkCmdExe { log.info("{} 云快充1.5.0地锁状态/报警信息帧请求", tcpSession); // 将消息体包装为ByteBuf以便读取 ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); /* 按协议顺序解析消息体 */ @@ -73,7 +70,6 @@ public class YunKuaiChongV150LockStatusULCmd extends YunKuaiChongUplinkCmdExe { // 构建转发消息 GroundLockStatusProto groundLockStatusProto = GroundLockStatusProto.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setGunCode(gunCode) .setLockStatus(lockStatus) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java index 68fb8c5..e227de7 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java @@ -28,9 +28,9 @@ import java.util.Arrays; import java.util.concurrent.TimeUnit; import static sanbing.jcpp.infrastructure.util.config.ThreadPoolConfiguration.PROTOCOL_SESSION_SCHEDULED; +import static sanbing.jcpp.proto.gen.ProtocolProto.SessionCloseReason.SESSION_CLOSE_MANUALLY; import static sanbing.jcpp.protocol.domain.DownlinkCmdEnum.LOGIN_ACK; import static sanbing.jcpp.protocol.domain.DownlinkCmdEnum.SYNC_TIME_REQUEST; -import static sanbing.jcpp.protocol.domain.SessionCloseReason.MANUALLY; import static sanbing.jcpp.protocol.listener.tcp.TcpSession.SCHEDULE_KEY_AUTO_SYNC_TIME; import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.FAILURE_BYTE; import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage.SUCCESS_BYTE; @@ -39,7 +39,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0登录认证应答 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x02, protocolNames = {V150, V160, V170}) @@ -78,7 +78,7 @@ public class YunKuaiChongV150LoginAckDLCmd extends YunKuaiChongDownlinkCmdExe { loginAck(tcpSession, pileCodeBytes, requestData, false); // 断开连接 - tcpSession.close(MANUALLY); + tcpSession.close(SESSION_CLOSE_MANUALLY); } } diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OfflineCardSyncRequestDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OfflineCardSyncRequestDLCmd.java index 331bd56..10db42d 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OfflineCardSyncRequestDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OfflineCardSyncRequestDLCmd.java @@ -10,7 +10,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; -import sanbing.jcpp.proto.gen.ProtocolProto; +import sanbing.jcpp.proto.gen.ProtocolProto.OfflineCardSyncRequest; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.annotation.ProtocolCmd; import sanbing.jcpp.protocol.listener.tcp.TcpSession; @@ -39,7 +39,7 @@ public class YunKuaiChongV150OfflineCardSyncRequestDLCmd extends YunKuaiChongDow return; } - ProtocolProto.OfflineCardSyncRequest request = message.getMsg().getOfflineCardSyncRequest(); + OfflineCardSyncRequest request = message.getMsg().getOfflineCardSyncRequest(); if (request.getTotal() > 15) { log.error("云快充1.5.0 离线卡数据同步 下发卡个数最大支持: 15个当前: {}个", request.getTotal()); @@ -67,7 +67,7 @@ public class YunKuaiChongV150OfflineCardSyncRequestDLCmd extends YunKuaiChongDow * @param request request * @return bufferInitialCapacity */ - private int bufferInitialCapacity(ProtocolProto.OfflineCardSyncRequest request) { + private int bufferInitialCapacity(OfflineCardSyncRequest request) { return (8 + 8) * request.getCardInfoCount() + 7 + 1; } diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OfflineCardSyncResponseULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OfflineCardSyncResponseULCmd.java index 4a702d1..a56cdaa 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OfflineCardSyncResponseULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OfflineCardSyncResponseULCmd.java @@ -11,7 +11,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; -import sanbing.jcpp.proto.gen.ProtocolProto; +import sanbing.jcpp.proto.gen.ProtocolProto.OfflineCardSyncResponse; +import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.annotation.ProtocolCmd; import sanbing.jcpp.protocol.listener.tcp.TcpSession; @@ -65,8 +66,8 @@ public class YunKuaiChongV150OfflineCardSyncResponseULCmd extends YunKuaiChongUp failureReason = byteBuf.readByte(); } - ProtocolProto.UplinkQueueMessage queueMessage = uplinkMessageBuilder(pileCode, tcpSession, message) - .setOfflineCardSyncResponse(ProtocolProto.OfflineCardSyncResponse.newBuilder() + UplinkQueueMessage queueMessage = uplinkMessageBuilder(pileCode, tcpSession, message) + .setOfflineCardSyncResponse(OfflineCardSyncResponse.newBuilder() .setPileCode(pileCode) .setSuccess(saveResult == 0x01) .setErrorMsg(errorMsg(saveResult, failureReason)) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OtaResponseULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OtaResponseULCmd.java index bc724ad..1c7de1d 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OtaResponseULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150OtaResponseULCmd.java @@ -10,7 +10,8 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; -import sanbing.jcpp.proto.gen.ProtocolProto; +import sanbing.jcpp.proto.gen.ProtocolProto.OtaResponse; +import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.annotation.ProtocolCmd; import sanbing.jcpp.protocol.listener.tcp.TcpSession; @@ -56,8 +57,8 @@ public class YunKuaiChongV150OtaResponseULCmd extends YunKuaiChongUplinkCmdExe { // 升级状态 // 0x00成功 0x01编号错误 0x01程序与桩型号不符 0x01下载更新文件超时 byte upgradeStatus = byteBuf.readByte(); - ProtocolProto.UplinkQueueMessage queueMessage = uplinkMessageBuilder(pileCode, tcpSession, message) - .setOtaResponse(ProtocolProto.OtaResponse.newBuilder() + UplinkQueueMessage queueMessage = uplinkMessageBuilder(pileCode, tcpSession, message) + .setOtaResponse(OtaResponse.newBuilder() .setPileCode(pileCode) .setSuccess(upgradeStatus == 0x00) .setErrorMsg(UPGRADE_STATUS.getOrDefault(upgradeStatus,UNKNOWN_MSG)) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150QueryPricingModelAckDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150QueryPricingModelAckDLCmd.java index 33c4f87..58168b7 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150QueryPricingModelAckDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150QueryPricingModelAckDLCmd.java @@ -9,10 +9,7 @@ package sanbing.jcpp.protocol.yunkuaichong.v150.cmd; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; -import sanbing.jcpp.proto.gen.ProtocolProto.FlagPriceProto; -import sanbing.jcpp.proto.gen.ProtocolProto.PeriodProto; -import sanbing.jcpp.proto.gen.ProtocolProto.PricingModelProto; -import sanbing.jcpp.proto.gen.ProtocolProto.QueryPricingResponse; +import sanbing.jcpp.proto.gen.ProtocolProto.*; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.annotation.ProtocolCmd; import sanbing.jcpp.protocol.listener.tcp.TcpSession; @@ -31,7 +28,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 计费模型请求应答 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x0A, protocolNames = {V150, V160, V170}) @@ -50,8 +47,20 @@ public class YunKuaiChongV150QueryPricingModelAckDLCmd extends YunKuaiChongDownl long pricingId = queryPricingResponse.getPricingId(); String pileCode = queryPricingResponse.getPileCode(); PricingModelProto pricingModel = queryPricingResponse.getPricingModel(); - Map flagPriceMap = pricingModel.getFlagPriceMap(); - List periodList = pricingModel.getPeriodList(); + // 适配新的protobuf结构:根据计费规则获取价格信息 + Map flagPriceMap = null; + List periodList = null; + + if (pricingModel.hasPeakValleyPricing()) { + // 峰谷计价:使用预定义的尖峰平谷时段 + PeakValleyPricingProto peakValleyPricing = pricingModel.getPeakValleyPricing(); + flagPriceMap = peakValleyPricing.getFlagPriceMap(); + periodList = peakValleyPricing.getPeriodList(); + } else { + // 未知计费模式 + log.info("未知的计费模式,桩编号: {}, 计费ID: {}, 计费规则: {}", pileCode, pricingId, pricingModel.getRule()); + throw new IllegalArgumentException("未知的计费模式: " + pricingModel.getRule()); + } // 从上行报文中取出桩编号字节数组 byte[] pileCodeBytes = encodePileCode(pileCode); diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150QueryPricingModelULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150QueryPricingModelULCmd.java index 05cd90c..eb225e8 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150QueryPricingModelULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150QueryPricingModelULCmd.java @@ -24,7 +24,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0充电桩计费模型请求 - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x09, protocolNames = {V150, V160, V170}) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RealTimeDataULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RealTimeDataULCmd.java index 82f4661..945efd8 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RealTimeDataULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RealTimeDataULCmd.java @@ -13,7 +13,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.ChargingProgressProto; import sanbing.jcpp.proto.gen.ProtocolProto.GunRunStatus; import sanbing.jcpp.proto.gen.ProtocolProto.GunRunStatusProto; @@ -33,7 +32,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0上传实时监测数据 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x13, protocolNames = {V150, V160, V170}) @@ -61,9 +60,6 @@ public class YunKuaiChongV150RealTimeDataULCmd extends YunKuaiChongUplinkCmdExe log.info("{} 云快充1.5.0上传实时监测数据", tcpSession); ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); - ObjectNode additionalInfo = JacksonUtil.newObjectNode(); // 1.交易流水号 @@ -138,7 +134,6 @@ public class YunKuaiChongV150RealTimeDataULCmd extends YunKuaiChongUplinkCmdExe // 抢状态 GunRunStatus gunRunStatus = parseGunRunStatus(gunStatus, gunInsert, tradeNo); GunRunStatusProto.Builder gunRunStatusProtoBuilder = GunRunStatusProto.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setGunCode(gunCode) .setGunRunStatus(gunRunStatus) @@ -156,7 +151,6 @@ public class YunKuaiChongV150RealTimeDataULCmd extends YunKuaiChongUplinkCmdExe // 充电进度 ChargingProgressProto.Builder chargingProgressProtoBuilder = ChargingProgressProto.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setGunCode(gunCode) .setTradeNo(tradeNo) @@ -204,7 +198,7 @@ public class YunKuaiChongV150RealTimeDataULCmd extends YunKuaiChongUplinkCmdExe public static boolean[] parseFaults(byte[] bytes) { // 确保输入有效 if (bytes.length != 2) { - throw new IllegalArgumentException("输入 byte 数组长度不为 2"); + throw new IllegalArgumentException("输入字节数组长度必须为2字节"); } // 创建一个布尔数组来存储故障状态 diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStartDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStartDLCmd.java index 2a304e9..06a7bb2 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStartDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStartDLCmd.java @@ -24,7 +24,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0 运营平台远程控制启机 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x34, protocolNames = {V150, V160, V170}) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStartResultULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStartResultULCmd.java index 96eda9f..5f391d0 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStartResultULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStartResultULCmd.java @@ -12,7 +12,6 @@ import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.RemoteStartChargingResponse; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; @@ -26,7 +25,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0 远程启动充电命令回复 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x33, protocolNames = {V150, V160, V170}) @@ -37,9 +36,6 @@ public class YunKuaiChongV150RemoteStartResultULCmd extends YunKuaiChongUplinkCm log.info("{} 云快充1.5.0远程启动充电命令回复", tcpSession); ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); - ObjectNode additionalInfo = JacksonUtil.newObjectNode(); // 1.交易流水号 @@ -64,7 +60,6 @@ public class YunKuaiChongV150RemoteStartResultULCmd extends YunKuaiChongUplinkCm String failReason = mapFailCode(failReasonByte); RemoteStartChargingResponse remoteStartChargingResponse = RemoteStartChargingResponse.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setGunCode(gunCode) .setTradeNo(tradeNo) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStopDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStopDLCmd.java index 88f3692..904fa74 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStopDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStopDLCmd.java @@ -22,7 +22,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0 运营平台远程停机 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x36, protocolNames = {V150, V160, V170}) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStopResultULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStopResultULCmd.java index c9db7be..0a3b067 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStopResultULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RemoteStopResultULCmd.java @@ -12,7 +12,6 @@ import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.RemoteStopChargingResponse; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; @@ -26,7 +25,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0 远程停机命令回复 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x35, protocolNames = {V150, V160, V170}) @@ -36,9 +35,6 @@ public class YunKuaiChongV150RemoteStopResultULCmd extends YunKuaiChongUplinkCmd log.info("{} 云快充1.5.0远程停机命令回复", tcpSession); ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); - ObjectNode additionalInfo = JacksonUtil.newObjectNode(); // 1.桩编号 @@ -58,7 +54,6 @@ public class YunKuaiChongV150RemoteStopResultULCmd extends YunKuaiChongUplinkCmd String failReason = mapFailCode(failReasonByte); RemoteStopChargingResponse remoteStopChargingResponse = RemoteStopChargingResponse.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setGunCode(gunCode) .setSuccess(isSuccess) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RestartPileAckULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RestartPileAckULCmd.java index 0ef811a..90c7b45 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RestartPileAckULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150RestartPileAckULCmd.java @@ -10,7 +10,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.RestartPileResponse; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; @@ -33,9 +32,6 @@ public class YunKuaiChongV150RestartPileAckULCmd extends YunKuaiChongUplinkCmdEx log.info("{} 云快充1.5.0远程重启动充电命令回复", tcpSession); ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); - // 1.桩编号 byte[] pileCodeBytes = new byte[7]; byteBuf.readBytes(pileCodeBytes); @@ -45,7 +41,6 @@ public class YunKuaiChongV150RestartPileAckULCmd extends YunKuaiChongUplinkCmdEx boolean isSuccess = (byteBuf.readByte() == 0x01); RestartPileResponse restartPileResponse = RestartPileResponse.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setSuccess(isSuccess) .build(); diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150SetPricingModelAckULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150SetPricingModelAckULCmd.java index 8a141d6..dc9778d 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150SetPricingModelAckULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150SetPricingModelAckULCmd.java @@ -25,7 +25,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0 计费模型应答 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x57, protocolNames = {V150, V160, V170}) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150SetPricingModelDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150SetPricingModelDLCmd.java index 3608e21..2aec71c 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150SetPricingModelDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150SetPricingModelDLCmd.java @@ -9,10 +9,7 @@ package sanbing.jcpp.protocol.yunkuaichong.v150.cmd; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; -import sanbing.jcpp.proto.gen.ProtocolProto.FlagPriceProto; -import sanbing.jcpp.proto.gen.ProtocolProto.PeriodProto; -import sanbing.jcpp.proto.gen.ProtocolProto.PricingModelProto; -import sanbing.jcpp.proto.gen.ProtocolProto.SetPricingRequest; +import sanbing.jcpp.proto.gen.ProtocolProto.*; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.annotation.ProtocolCmd; import sanbing.jcpp.protocol.listener.tcp.TcpSession; @@ -32,7 +29,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0 计费模型设置 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x58, protocolNames = {V150, V160, V170}) @@ -50,8 +47,20 @@ public class YunKuaiChongV150SetPricingModelDLCmd extends YunKuaiChongDownlinkCm long pricingId = setPricingRequest.getPricingId(); String pileCode = setPricingRequest.getPileCode(); PricingModelProto pricingModel = setPricingRequest.getPricingModel(); - Map flagPriceMap = pricingModel.getFlagPriceMap(); - List periodList = pricingModel.getPeriodList(); + // 适配新的protobuf结构:根据计费规则获取价格信息 + Map flagPriceMap = null; + List periodList = null; + + if (pricingModel.hasPeakValleyPricing()) { + // 峰谷计价:使用预定义的尖峰平谷时段 + PeakValleyPricingProto peakValleyPricing = pricingModel.getPeakValleyPricing(); + flagPriceMap = peakValleyPricing.getFlagPriceMap(); + periodList = peakValleyPricing.getPeriodList(); + } else { + // 未知计费模式 + log.info("未知的计费模式,桩编号: {}, 计费ID: {}, 计费规则: {}", pileCode, pricingId, pricingModel.getRule()); + throw new IllegalArgumentException("未知的计费模式: " + pricingModel.getRule()); + } // 反转取出桩编号字节数组 byte[] pileCodeBytes = encodePileCode(pileCode); diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TimeSyncDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TimeSyncDLCmd.java index 71f272a..e63ee01 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TimeSyncDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TimeSyncDLCmd.java @@ -11,7 +11,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.CP56Time2aUtil; -import sanbing.jcpp.proto.gen.ProtocolProto; +import sanbing.jcpp.proto.gen.ProtocolProto.TimeSyncRequest; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.annotation.ProtocolCmd; import sanbing.jcpp.protocol.listener.tcp.TcpSession; @@ -36,7 +36,7 @@ public class YunKuaiChongV150TimeSyncDLCmd extends YunKuaiChongDownlinkCmdExe { if (!yunKuaiChongDwonlinkMessage.getMsg().hasTimeSyncRequest()) { return; } - ProtocolProto.TimeSyncRequest timeSyncRequest = yunKuaiChongDwonlinkMessage.getMsg().getTimeSyncRequest(); + TimeSyncRequest timeSyncRequest = yunKuaiChongDwonlinkMessage.getMsg().getTimeSyncRequest(); String pileCode = timeSyncRequest.getPileCode(); String time = timeSyncRequest.getTime(); ByteBuf syncTimeMsgBody = Unpooled.buffer(14); diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TimeSyncResultULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TimeSyncResultULCmd.java index 3a9a3c8..029cc2d 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TimeSyncResultULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TimeSyncResultULCmd.java @@ -12,7 +12,8 @@ import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.codec.CP56Time2aUtil; -import sanbing.jcpp.proto.gen.ProtocolProto; +import sanbing.jcpp.proto.gen.ProtocolProto.TimeSyncResponse; +import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; import sanbing.jcpp.protocol.annotation.ProtocolCmd; import sanbing.jcpp.protocol.listener.tcp.TcpSession; @@ -44,11 +45,11 @@ public class YunKuaiChongV150TimeSyncResultULCmd extends YunKuaiChongUplinkCmdEx byteBuf.readBytes(timeBytes); LocalDateTime dateTime = CP56Time2aUtil.decode(timeBytes); String time = DateUtil.formatLocalDateTime(dateTime); - ProtocolProto.TimeSyncResponse timeSyncResponse = ProtocolProto.TimeSyncResponse.newBuilder() + TimeSyncResponse timeSyncResponse = TimeSyncResponse.newBuilder() .setPileCode(pileCode) .setTime(time) .build(); - ProtocolProto.UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(pileCode, tcpSession, yunKuaiChongUplinkMessage) + UplinkQueueMessage uplinkQueueMessage = uplinkMessageBuilder(pileCode, tcpSession, yunKuaiChongUplinkMessage) .setTimeSyncResponse(timeSyncResponse) .build(); tcpSession.getForwarder().sendMessage(uplinkQueueMessage); diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordAckDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordAckDLCmd.java index 5ab5bab..340aaf2 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordAckDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordAckDLCmd.java @@ -25,7 +25,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0 交易记录确认 - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x40, protocolNames = {V150, V160, V170}) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordULCmd.java index bf0accf..c57eeaa 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordULCmd.java @@ -31,7 +31,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0 交易记录 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x3B, protocolNames = {V150, V160}) @@ -200,7 +200,7 @@ public class YunKuaiChongV150TransactionRecordULCmd extends YunKuaiChongUplinkCm public static long readLongLE5Byte(byte[] bytes) { // 确保字节数组的长度至少为 5 if (bytes.length < 5) { - throw new IllegalArgumentException("Byte array must contain at least 5 bytes."); + throw new IllegalArgumentException("字节数组长度必须至少为5字节"); } // 使用小端字节序读取 5 字节数字 diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150VerifyPricingModelAckDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150VerifyPricingModelAckDLCmd.java index c2f7429..9fa4e7e 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150VerifyPricingModelAckDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150VerifyPricingModelAckDLCmd.java @@ -28,7 +28,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0计费模型验证请求应答 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x06, protocolNames = {V150, V160, V170}) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150VerifyPricingModelULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150VerifyPricingModelULCmd.java index a104a10..7befe10 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150VerifyPricingModelULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150VerifyPricingModelULCmd.java @@ -27,7 +27,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.5.0计费模型验证请求 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x05, protocolNames = {V150, V160, V170}) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/YunkuaichongV160ProtocolBootstrap.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/YunkuaichongV160ProtocolBootstrap.java index e036a43..e9c3b51 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/YunkuaichongV160ProtocolBootstrap.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/YunkuaichongV160ProtocolBootstrap.java @@ -15,7 +15,7 @@ import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolMessageProcessor; import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.YUNKUAICHONG_V160; /** - * @author baigod + * @author 九筒 */ @ProtocolComponent(YUNKUAICHONG_V160) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/cmd/YunKuaiChongV160RemoteParallelStartDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/cmd/YunKuaiChongV160RemoteParallelStartDLCmd.java index 447530d..999b6d0 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/cmd/YunKuaiChongV160RemoteParallelStartDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/cmd/YunKuaiChongV160RemoteParallelStartDLCmd.java @@ -29,7 +29,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.6.0 运营平台远程控制并充启机 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0xA4, protocolNames = {V160, V170}) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/cmd/YunKuaiChongV160RemoteParallelStartResultULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/cmd/YunKuaiChongV160RemoteParallelStartResultULCmd.java index 200ef2f..5d8494d 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/cmd/YunKuaiChongV160RemoteParallelStartResultULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v160/cmd/YunKuaiChongV160RemoteParallelStartResultULCmd.java @@ -12,7 +12,6 @@ import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import sanbing.jcpp.infrastructure.util.codec.BCDUtil; import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil; -import sanbing.jcpp.infrastructure.util.trace.TracerContextUtil; import sanbing.jcpp.proto.gen.ProtocolProto.RemoteStartChargingResponse; import sanbing.jcpp.proto.gen.ProtocolProto.UplinkQueueMessage; import sanbing.jcpp.protocol.ProtocolContext; @@ -26,7 +25,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.6.0 远程并充启机命令回复 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0xA3, protocolNames = {V160}) @@ -37,9 +36,6 @@ public class YunKuaiChongV160RemoteParallelStartResultULCmd extends YunKuaiChong log.info("{} 云快充1.6.远程并充启机命令回复", tcpSession); ByteBuf byteBuf = Unpooled.wrappedBuffer(yunKuaiChongUplinkMessage.getMsgBody()); - // 从Tracer总获取当前时间 - long ts = TracerContextUtil.getCurrentTracer().getTracerTs(); - ObjectNode additionalInfo = JacksonUtil.newObjectNode(); // 1.交易流水号 @@ -75,7 +71,6 @@ public class YunKuaiChongV160RemoteParallelStartResultULCmd extends YunKuaiChong additionalInfo.put("并充序号", parallelSeqNo); RemoteStartChargingResponse remoteStartChargingResponse = RemoteStartChargingResponse.newBuilder() - .setTs(ts) .setPileCode(pileCode) .setGunCode(gunCode) .setTradeNo(tradeNo) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v170/YunkuaichongV170ProtocolBootstrap.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v170/YunkuaichongV170ProtocolBootstrap.java index fc425e1..dad396b 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v170/YunkuaichongV170ProtocolBootstrap.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v170/YunkuaichongV170ProtocolBootstrap.java @@ -15,7 +15,7 @@ import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolMessageProcessor; import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.ProtocolNames.YUNKUAICHONG_V170; /** - * @author baigod + * @author 九筒 */ @ProtocolComponent(YUNKUAICHONG_V170) diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v170/cmd/YunKuaiChongV170TransactionRecordULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v170/cmd/YunKuaiChongV170TransactionRecordULCmd.java index b03f85f..d8d6b30 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v170/cmd/YunKuaiChongV170TransactionRecordULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v170/cmd/YunKuaiChongV170TransactionRecordULCmd.java @@ -31,7 +31,7 @@ import static sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongProtocolConstants.P /** * 云快充1.7.0 交易记录 * - * @author baigod + * @author 九筒 */ @Slf4j @ProtocolCmd(value = 0x3D, protocolNames = {V170}) @@ -224,7 +224,7 @@ public class YunKuaiChongV170TransactionRecordULCmd extends YunKuaiChongUplinkCm public static long readLongLE5Byte(byte[] bytes) { // 确保字节数组的长度至少为 5 if (bytes.length < 5) { - throw new IllegalArgumentException("Byte array must contain at least 5 bytes."); + throw new IllegalArgumentException("字节数组长度必须至少为5字节"); } // 使用小端字节序读取 5 字节数字 diff --git a/jcpp-testing/src/main/java/module-info.java b/jcpp-testing/src/main/java/module-info.java index f398a9f..062989e 100644 --- a/jcpp-testing/src/main/java/module-info.java +++ b/jcpp-testing/src/main/java/module-info.java @@ -4,5 +4,11 @@ * 抖音:程序员三丙 * 付费课程知识星球:https://t.zsxq.com/aKtXo */ +/** + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:... + */ module jcpp.testing { } \ No newline at end of file diff --git a/jcpp-web-ui/.gitignore b/jcpp-web-ui/.gitignore new file mode 100644 index 0000000..4d29575 --- /dev/null +++ b/jcpp-web-ui/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/jcpp-web-ui/LICENSE b/jcpp-web-ui/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/jcpp-web-ui/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/jcpp-web-ui/package-lock.json b/jcpp-web-ui/package-lock.json new file mode 100644 index 0000000..932a629 --- /dev/null +++ b/jcpp-web-ui/package-lock.json @@ -0,0 +1,19432 @@ +{ + "name": "jcpp-web-ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jcpp-web-ui", + "version": "0.1.0", + "dependencies": { + "@ant-design/icons": "^6.0.0", + "@ant-design/v5-patch-for-react-19": "^1.0.3", + "@antv/g2": "^5.4.0", + "@types/node": "^16.18.126", + "@types/react": "^19.1.12", + "@types/react-dom": "^19.1.9", + "antd": "^5.27.2", + "axios": "^1.11.0", + "china-division": "^2.7.0", + "echarts": "^6.0.0", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-router-dom": "^7.8.2", + "react-scripts": "5.0.1", + "typescript": "^4.9.5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ant-design/colors": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-8.0.0.tgz", + "integrity": "sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.24.0", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz", + "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-3.0.0.tgz", + "integrity": "sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-6.0.1.tgz", + "integrity": "sha512-BsAoYa8NTwh5GfpziqStAyWHNyp8vkc9PkuIR/Cu8O8WkhRzrpx260zd5ygsXMhQEGtfGGFjdAG0DfjhGBOBHw==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@rc-component/util": "^1.2.1", + "classnames": "^2.2.6" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT" + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@ant-design/v5-patch-for-react-19": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@ant-design/v5-patch-for-react-19/-/v5-patch-for-react-19-1.0.3.tgz", + "integrity": "sha512-iWfZuSUl5kuhqLUw7jJXUQFMMkM7XpW7apmKzQBQHU0cpifYW4A79xIBt9YVO5IBajKpPG5UKP87Ft7Yrw1p/w==", + "license": "MIT", + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "antd": ">=5.22.6", + "react": ">=19.0.0", + "react-dom": ">=19.0.0" + } + }, + "node_modules/@antv/component": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@antv/component/-/component-2.1.6.tgz", + "integrity": "sha512-e9StChXXG7grqP1uVqDZQp502hkwOX0sRNxbOOIrBeVD1x1NtZ+FaOEeBG5JXgbEWfMmPf0wEir4VGG4EPIGvw==", + "license": "MIT", + "dependencies": { + "@antv/g": "^6.1.11", + "@antv/scale": "^0.4.16", + "@antv/util": "^3.3.10", + "svg-path-parser": "^1.1.0" + } + }, + "node_modules/@antv/component/node_modules/@antv/scale": { + "version": "0.4.16", + "resolved": "https://registry.npmmirror.com/@antv/scale/-/scale-0.4.16.tgz", + "integrity": "sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.7", + "color-string": "^1.5.5", + "fecha": "^4.2.1" + } + }, + "node_modules/@antv/coord": { + "version": "0.4.7", + "resolved": "https://registry.npmmirror.com/@antv/coord/-/coord-0.4.7.tgz", + "integrity": "sha512-UTbrMLhwJUkKzqJx5KFnSRpU3BqrdLORJbwUbHK2zHSCT3q3bjcFA//ZYLVfIlwqFDXp/hzfMyRtp0c77A9ZVA==", + "license": "MIT", + "dependencies": { + "@antv/scale": "^0.4.12", + "@antv/util": "^2.0.13", + "gl-matrix": "^3.4.3" + } + }, + "node_modules/@antv/coord/node_modules/@antv/scale": { + "version": "0.4.16", + "resolved": "https://registry.npmmirror.com/@antv/scale/-/scale-0.4.16.tgz", + "integrity": "sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.7", + "color-string": "^1.5.5", + "fecha": "^4.2.1" + } + }, + "node_modules/@antv/coord/node_modules/@antv/scale/node_modules/@antv/util": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/@antv/util/-/util-3.3.11.tgz", + "integrity": "sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "gl-matrix": "^3.3.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@antv/coord/node_modules/@antv/util": { + "version": "2.0.17", + "resolved": "https://registry.npmmirror.com/@antv/util/-/util-2.0.17.tgz", + "integrity": "sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q==", + "license": "ISC", + "dependencies": { + "csstype": "^3.0.8", + "tslib": "^2.0.3" + } + }, + "node_modules/@antv/event-emitter": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/@antv/event-emitter/-/event-emitter-0.1.3.tgz", + "integrity": "sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg==", + "license": "MIT" + }, + "node_modules/@antv/expr": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@antv/expr/-/expr-1.0.2.tgz", + "integrity": "sha512-vrfdmPHkTuiS5voVutKl2l06w1ihBh9A8SFdQPEE+2KMVpkymzGOF1eWpfkbGZ7tiFE15GodVdhhHomD/hdIwg==", + "license": "MIT" + }, + "node_modules/@antv/g": { + "version": "6.1.28", + "resolved": "https://registry.npmmirror.com/@antv/g/-/g-6.1.28.tgz", + "integrity": "sha512-BwavpbKGR4NEJD3BtVxfBFjCcxy5gsWoUNnBisfG1qfjhGTt7QvUYHFH46+mHJjHMIdYjuFw2T0ZYVtxBddxSg==", + "license": "MIT", + "dependencies": { + "@antv/g-camera-api": "2.0.41", + "@antv/g-dom-mutation-observer-api": "2.0.38", + "@antv/g-lite": "2.3.2", + "@antv/g-web-animations-api": "2.1.28", + "@babel/runtime": "^7.25.6" + } + }, + "node_modules/@antv/g-camera-api": { + "version": "2.0.41", + "resolved": "https://registry.npmmirror.com/@antv/g-camera-api/-/g-camera-api-2.0.41.tgz", + "integrity": "sha512-dF52/wpzHDKi7ZzPlaHurEjWrF9aBKL2udDwQkEeVtfkJ0DHaavr3BAvhuGhtHoecRYQJvpzP1OkGNDLQJQQlw==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-canvas": { + "version": "2.0.48", + "resolved": "https://registry.npmmirror.com/@antv/g-canvas/-/g-canvas-2.0.48.tgz", + "integrity": "sha512-P98cTLRbKbCAcUVgHqMjKcvOany6nR7wvt+g+sazIfKSMUCWgjLTOjlLezux2up3At29mt80StaV2AR3d61YQA==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@antv/g-plugin-canvas-path-generator": "2.1.22", + "@antv/g-plugin-canvas-picker": "2.1.27", + "@antv/g-plugin-canvas-renderer": "2.3.3", + "@antv/g-plugin-dom-interaction": "2.1.27", + "@antv/g-plugin-html-renderer": "2.1.27", + "@antv/g-plugin-image-loader": "2.1.26", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-dom-mutation-observer-api": { + "version": "2.0.38", + "resolved": "https://registry.npmmirror.com/@antv/g-dom-mutation-observer-api/-/g-dom-mutation-observer-api-2.0.38.tgz", + "integrity": "sha512-xzgbt8GUOiToBeDVv+jmGkDE+HtI9tD6uO8TirJbCya88DKcY/jurQALq0NdWKgMJLn7WPiUKyDwHWimwQcBJw==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@babel/runtime": "^7.25.6" + } + }, + "node_modules/@antv/g-lite": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/@antv/g-lite/-/g-lite-2.3.2.tgz", + "integrity": "sha512-fkIxRoqLOGsNPwsp26bPp58cPWuX3E4wQ9cfkB/DHy5LtLrPpvOwHWB3+MBPgZwzk8jTTjchiXa756ZFOAWyQQ==", + "license": "MIT", + "dependencies": { + "@antv/g-math": "3.0.1", + "@antv/util": "^3.3.5", + "@antv/vendor": "^1.0.3", + "@babel/runtime": "^7.25.6", + "eventemitter3": "^5.0.1", + "gl-matrix": "^3.4.3", + "rbush": "^3.0.1", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-math": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/@antv/g-math/-/g-math-3.0.1.tgz", + "integrity": "sha512-FvkDBNRpj+HsLINunrL2PW0OlG368MlpHuihbxleuajGim5kra8tgISwCLmAf8Yz2b1CgZ9PvpohqiLzHS7HLg==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-canvas-path-generator": { + "version": "2.1.22", + "resolved": "https://registry.npmmirror.com/@antv/g-plugin-canvas-path-generator/-/g-plugin-canvas-path-generator-2.1.22.tgz", + "integrity": "sha512-Z0IawzTGgTppa9IpkNNKsqgoU89oOjUsiU8GZZlkDkUggQTHP0wOxTeLAb43YgClx3aTI3bRs44uMQutNdSVxw==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@antv/g-math": "3.0.1", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-canvas-picker": { + "version": "2.1.27", + "resolved": "https://registry.npmmirror.com/@antv/g-plugin-canvas-picker/-/g-plugin-canvas-picker-2.1.27.tgz", + "integrity": "sha512-DHQ0YLYNXAm6O63pW6nKs/R0fuqlUYfehNs/EtzrmqyUkKASd/Vhs4HLNeHTMUdBMgg41T+x5qay0GGttK4Xdw==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@antv/g-math": "3.0.1", + "@antv/g-plugin-canvas-path-generator": "2.1.22", + "@antv/g-plugin-canvas-renderer": "2.3.3", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-canvas-renderer": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/@antv/g-plugin-canvas-renderer/-/g-plugin-canvas-renderer-2.3.3.tgz", + "integrity": "sha512-d6JkZy1YmLnvI9wsbO8QVpBz7z7tl6JRQkF5hx9XLDtf2fD4n83KINeMq13skiNwaiudS771WWiBtfzUHB73pQ==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@antv/g-math": "3.0.1", + "@antv/g-plugin-canvas-path-generator": "2.1.22", + "@antv/g-plugin-image-loader": "2.1.26", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-dom-interaction": { + "version": "2.1.27", + "resolved": "https://registry.npmmirror.com/@antv/g-plugin-dom-interaction/-/g-plugin-dom-interaction-2.1.27.tgz", + "integrity": "sha512-hltVZZH+bj0uXmGSR+6BIwhCFYyHmDIQi3vrj/Wn1Dn6PgufvMCXfjr3DfmkQnY+FFP8ZCpg5N9MaE0BE9OddA==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-dragndrop": { + "version": "2.0.38", + "resolved": "https://registry.npmmirror.com/@antv/g-plugin-dragndrop/-/g-plugin-dragndrop-2.0.38.tgz", + "integrity": "sha512-yCef5ER759i0WpuOekFQ+AcDTu0N/COMbkPOG6YuswVnhQH447GUpuNm7Le+Mq26qONlXTDyjxuMHoUOWwJ7Cw==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-html-renderer": { + "version": "2.1.27", + "resolved": "https://registry.npmmirror.com/@antv/g-plugin-html-renderer/-/g-plugin-html-renderer-2.1.27.tgz", + "integrity": "sha512-NnI4GxDBb71o/XZzoRdi0xI3xg7GJmthyO5xP5/MiOFmwJ/jW/QDz17vUonmzUVbCt6upikHV5GyYOaogRqdVg==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-plugin-image-loader": { + "version": "2.1.26", + "resolved": "https://registry.npmmirror.com/@antv/g-plugin-image-loader/-/g-plugin-image-loader-2.1.26.tgz", + "integrity": "sha512-AElV0QOX2LAhB3jr9XtvkynntuKhcaU5n7avu5ynM5VoAtMaJRANhCyefA2G3myeJxWcHk4nWDX6u4YMaZnnvw==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "gl-matrix": "^3.4.3", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g-web-animations-api": { + "version": "2.1.28", + "resolved": "https://registry.npmmirror.com/@antv/g-web-animations-api/-/g-web-animations-api-2.1.28.tgz", + "integrity": "sha512-V5g8bO2D1hb8fRMMi5hXL/De+1UDRzW3C5EX07oazR0q71GONASP+sVwniZdt9R1HAmJSN5dvW3SqWeU3EEstQ==", + "license": "MIT", + "dependencies": { + "@antv/g-lite": "2.3.2", + "@antv/util": "^3.3.5", + "@babel/runtime": "^7.25.6", + "tslib": "^2.5.3" + } + }, + "node_modules/@antv/g2": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/@antv/g2/-/g2-5.4.0.tgz", + "integrity": "sha512-aNw4lvi9IdsdoMHZbrAhZbFy9y5Ppv6zyjtgTKHdMl8x4xnoweW/4YwryRZh2/2Z497dnobPcVyPu5Iwoz87Ww==", + "license": "MIT", + "dependencies": { + "@antv/component": "^2.1.3", + "@antv/coord": "^0.4.7", + "@antv/event-emitter": "^0.1.3", + "@antv/expr": "^1.0.2", + "@antv/g": "^6.1.24", + "@antv/g-canvas": "^2.0.43", + "@antv/g-plugin-dragndrop": "^2.0.35", + "@antv/scale": "^0.5.0", + "@antv/util": "^3.3.10", + "@antv/vendor": "^1.0.11", + "flru": "^1.0.2", + "pdfast": "^0.2.0" + } + }, + "node_modules/@antv/scale": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/@antv/scale/-/scale-0.5.2.tgz", + "integrity": "sha512-rTHRAwvpHWC5PGZF/mJ2ZuTDqwwvVBDRph0Uu5PV9BXwzV7K8+9lsqGJ+XHVLxe8c6bKog5nlzvV/dcYb0d5Ow==", + "license": "MIT", + "dependencies": { + "@antv/util": "^3.3.7", + "color-string": "^1.5.5", + "fecha": "^4.2.1" + } + }, + "node_modules/@antv/util": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/@antv/util/-/util-3.3.11.tgz", + "integrity": "sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "gl-matrix": "^3.3.0", + "tslib": "^2.3.1" + } + }, + "node_modules/@antv/vendor": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@antv/vendor/-/vendor-1.0.11.tgz", + "integrity": "sha512-LmhPEQ+aapk3barntaiIxJ5VHno/Tyab2JnfdcPzp5xONh/8VSfed4bo/9xKo5HcUAEydko38vYLfj6lJliLiw==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.2.1", + "@types/d3-color": "^3.1.3", + "@types/d3-dispatch": "^3.0.6", + "@types/d3-dsv": "^3.0.7", + "@types/d3-ease": "^3.0.2", + "@types/d3-fetch": "^3.0.7", + "@types/d3-force": "^3.0.10", + "@types/d3-format": "^3.0.4", + "@types/d3-geo": "^3.1.0", + "@types/d3-hierarchy": "^3.1.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-path": "^3.1.0", + "@types/d3-quadtree": "^3.0.6", + "@types/d3-random": "^3.0.3", + "@types/d3-scale": "^4.0.9", + "@types/d3-scale-chromatic": "^3.1.0", + "@types/d3-shape": "^3.1.7", + "@types/d3-time": "^3.0.4", + "@types/d3-timer": "^3.0.2", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-dispatch": "^3.0.1", + "d3-dsv": "^3.0.1", + "d3-ease": "^3.0.1", + "d3-fetch": "^3.0.1", + "d3-force": "^3.0.0", + "d3-force-3d": "^3.0.5", + "d3-format": "^3.1.0", + "d3-geo": "^3.1.1", + "d3-geo-projection": "^4.0.0", + "d3-hierarchy": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-quadtree": "^3.0.1", + "d3-random": "^3.0.1", + "d3-regression": "^1.3.10", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/eslint-parser/-/eslint-parser-7.28.4.tgz", + "integrity": "sha512-Aa+yDiH87980jR6zvRfFuCR1+dLb00vBydhTL+zI992Rz/wQhSvuxjmOOuJOgO3XmakO6RykRGD2S1mq1AtgHA==", + "license": "MIT", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz", + "integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-decorators": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", + "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz", + "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", + "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-flow": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", + "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", + "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.3", + "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "license": "MIT" + }, + "node_modules/@csstools/normalize.css": { + "version": "12.1.1", + "resolved": "https://registry.npmmirror.com/@csstools/normalize.css/-/normalize.css-12.1.1.tgz", + "integrity": "sha512-YAYeJ+Xqh7fUou1d1j9XHl44BmsuThiTr4iNrgCQ3J27IbhXsxXDGZ1cXv8Qvs99d4rBbLiSKy3+WZiet32PcQ==", + "license": "CC0-1.0" + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "license": "CC0-1.0", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "license": "CC0-1.0", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/core": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "license": "MIT", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz", + "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/globals/-/globals-27.5.1.tgz", + "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/types": "^27.5.1", + "expect": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/reporters/-/reporters-27.5.1.tgz", + "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-haste-map": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmmirror.com/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9", + "source-map": "^0.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/source-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/test-result": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "license": "MIT", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.5.1", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@jest/transform/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmmirror.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.17", + "resolved": "https://registry.npmmirror.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.17.tgz", + "integrity": "sha512-tXDyE1/jzFsHXjhRZQ3hMl0IVhYe5qula43LDWIhVfjp9G/nT5OQY5AORVOrkEGAUltBJOfOWeETbmhm6kHhuQ==", + "license": "MIT", + "dependencies": { + "ansi-html": "^0.0.9", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^4.2.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <5.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x || 5.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/color-picker/node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@rc-component/qrcode/-/qrcode-1.0.0.tgz", + "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmmirror.com/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.3.0.tgz", + "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/util": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@rc-component/util/-/util-1.3.0.tgz", + "integrity": "sha512-hfXE04CVsxI/slmWKeSh6du7sSKpbvVdVEZCa8A+2QWDlL97EsCYme2c3ZWLn1uC9FR21JoewlrhUPWO4QgO8w==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "11.2.1", + "resolved": "https://registry.npmmirror.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", + "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "license": "MIT", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "license": "MIT" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.12.0", + "resolved": "https://registry.npmmirror.com/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz", + "integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==", + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmmirror.com/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "license": "Apache-2.0", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", + "license": "MIT", + "dependencies": { + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.12.6" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", + "svg-parser": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", + "svgo": "^1.2.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/webpack": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/@svgr/webpack/-/webpack-5.5.0.tgz", + "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/plugin-transform-react-constant-elements": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@svgr/core": "^5.5.0", + "@svgr/plugin-jsx": "^5.5.0", + "@svgr/plugin-svgo": "^5.5.0", + "loader-utils": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmmirror.com/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "8.56.12", + "resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-8.56.12.tgz", + "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmmirror.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.7", + "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmmirror.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.16", + "resolved": "https://registry.npmmirror.com/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "16.18.126", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-16.18.126.tgz", + "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", + "license": "MIT" + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmmirror.com/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmmirror.com/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "license": "MIT" + }, + "node_modules/@types/q": { + "version": "1.5.8", + "resolved": "https://registry.npmmirror.com/@types/q/-/q-1.5.8.tgz", + "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.12", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.1.12.tgz", + "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.9", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.1.9.tgz", + "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmmirror.com/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmmirror.com/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmmirror.com/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmmirror.com/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmmirror.com/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", + "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "license": "BSD-3-Clause" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmmirror.com/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmmirror.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd": { + "version": "5.27.3", + "resolved": "https://registry.npmmirror.com/antd/-/antd-5.27.3.tgz", + "integrity": "sha512-Jewp1ek1iyqoAyjWyPgzc2kioZ+7S3jh39a+tld/j4ucnuf/cBk4omfyIdhLz49pVNsaEcRp5LtJOSQPFwPgpA==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.2.1", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.0.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.3.0", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.34.0", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.3.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.0", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.4", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.8", + "rc-slider": "~11.1.8", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.52.6", + "rc-tabs": "~15.7.0", + "rc-textarea": "~1.10.2", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.9.2", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd/node_modules/@ant-design/colors": { + "version": "7.2.1", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-7.2.1.tgz", + "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/antd/node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/antd/node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmmirror.com/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/array.prototype.reduce/-/array.prototype.reduce-1.0.8.tgz", + "integrity": "sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-array-method-boxes-properly": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "is-string": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmmirror.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmmirror.com/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "license": "MIT", + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-loader": { + "version": "8.4.1", + "resolved": "https://registry.npmmirror.com/babel-loader/-/babel-loader-8.4.1.tgz", + "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", + "license": "MIT", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.4", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmmirror.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-named-asset-import": { + "version": "0.3.8", + "resolved": "https://registry.npmmirror.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", + "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", + "license": "MIT", + "peerDependencies": { + "@babel/core": "^7.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "license": "MIT" + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-react-app": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/babel-preset-react-app/-/babel-preset-react-app-10.1.0.tgz", + "integrity": "sha512-f9B1xMdnkCIqe+2dHrJsoQFRz7reChaAHE/65SdaykPklQqhme2WaC08oD3is77x9ff98/9EazAKFDZv5rFEQg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/babel-preset-react-app/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.11", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "license": "MIT" + }, + "node_modules/bfj": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/bfj/-/bfj-7.1.0.tgz", + "integrity": "sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==", + "license": "MIT", + "dependencies": { + "bluebird": "^3.7.2", + "check-types": "^11.2.3", + "hoopy": "^0.1.4", + "jsonpath": "^1.1.1", + "tryer": "^1.0.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "license": "BSD-2-Clause" + }, + "node_modules/browserslist": { + "version": "4.25.4", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.25.4.tgz", + "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001737", + "electron-to-chromium": "^1.5.211", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001741", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/check-types": { + "version": "11.2.3", + "resolved": "https://registry.npmmirror.com/check-types/-/check-types-11.2.3.tgz", + "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==", + "license": "MIT" + }, + "node_modules/china-division": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/china-division/-/china-division-2.7.0.tgz", + "integrity": "sha512-4uUPAT+1WfqDh5jytq7omdCmHNk3j+k76zEG/2IqaGcYB90c2SwcixttcypdsZ3T/9tN1TTpBDoeZn+Yw/qBEA==", + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "license": "MIT" + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/coa": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "license": "MIT", + "dependencies": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/coa/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/coa/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/coa/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/coa/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmmirror.com/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "license": "MIT" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-js": { + "version": "3.45.1", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.45.1", + "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.45.1.tgz", + "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.45.1", + "resolved": "https://registry.npmmirror.com/core-js-pure/-/core-js-pure-3.45.1.tgz", + "integrity": "sha512-OHnWFKgTUshEU8MK+lOs1H8kC8GkTi9Z1tvNkxrCcw9wl3MJIO7q2ld77wjWn4/xuGrVu2X+nME1iIIPBSdyEQ==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "3.4.1", + "resolved": "https://registry.npmmirror.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", + "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "license": "MIT", + "dependencies": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "license": "CC0-1.0", + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmmirror.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "7.11.2", + "resolved": "https://registry.npmmirror.com/cssdb/-/cssdb-7.11.2.tgz", + "integrity": "sha512-lhQ32TFkc1X4eTefGfYPvgovRSzIMofHkigfH8nWtyRL4XJLsRhJFreRvEgKzept7x1rjBuy3J/MurXLaFxW/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "CC0-1.0" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmmirror.com/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmmirror.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "license": "MIT", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "license": "MIT", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, + "node_modules/csso/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmmirror.com/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmmirror.com/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo-projection": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", + "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", + "license": "ISC", + "dependencies": { + "commander": "7", + "d3-array": "1 - 3", + "d3-geo": "1.12.0 - 3" + }, + "bin": { + "geo2svg": "bin/geo2svg.js", + "geograticule": "bin/geograticule.js", + "geoproject": "bin/geoproject.js", + "geoquantize": "bin/geoquantize.js", + "geostitch": "bin/geostitch.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-regression": { + "version": "1.3.10", + "resolved": "https://registry.npmmirror.com/d3-regression/-/d3-regression-1.3.10.tgz", + "integrity": "sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "license": "BSD-2-Clause" + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "license": "BSD-2-Clause", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" + }, + "node_modules/detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "license": "MIT", + "dependencies": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "bin": { + "detect": "bin/detect-port", + "detect-port": "bin/detect-port" + }, + "engines": { + "node": ">= 4.2.1" + } + }, + "node_modules/detect-port-alt/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/detect-port-alt/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "deprecated": "Use your platform's native DOMException instead", + "license": "MIT", + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "license": "BSD-2-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.214", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.214.tgz", + "integrity": "sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==", + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmmirror.com/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmmirror.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "license": "BSD-3-Clause", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmmirror.com/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmmirror.com/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmmirror.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmmirror.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "5.11.1", + "resolved": "https://registry.npmmirror.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz", + "integrity": "sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^5.58.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", + "license": "MIT", + "dependencies": { + "@types/eslint": "^7.29.0 || ^8.4.1", + "jest-worker": "^28.0.2", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmmirror.com/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmmirror.com/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "license": "ISC" + }, + "node_modules/flru": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/flru/-/flru-1.0.2.tgz", + "integrity": "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "6.5.3", + "resolved": "https://registry.npmmirror.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=10", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "license": "ISC" + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmmirror.com/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "license": "MIT" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "license": "MIT" + }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", + "license": "(Apache-2.0 OR MPL-1.1)" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.4", + "resolved": "https://registry.npmmirror.com/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz", + "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==", + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmmirror.com/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "license": "ISC" + }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "license": "MIT", + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "license": "MIT" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmmirror.com/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "license": "MIT", + "dependencies": { + "@jest/core": "^27.5.1", + "import-local": "^3.0.2", + "jest-cli": "^27.5.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz", + "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-circus/-/jest-circus-27.5.1.tgz", + "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-cli": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-cli/-/jest-cli-27.5.1.tgz", + "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", + "license": "MIT", + "dependencies": { + "@jest/core": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-config/-/jest-config-27.5.1.tgz", + "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.8.0", + "@jest/test-sequencer": "^27.5.1", + "@jest/types": "^27.5.1", + "babel-jest": "^27.5.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.9", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-jasmine2": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-jasmine2": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", + "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", + "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", + "license": "MIT", + "dependencies": { + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-resolve/-/jest-resolve-27.5.1.tgz", + "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", + "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-snapshot": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-runner/-/jest-runner-27.5.1.tgz", + "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", + "license": "MIT", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-leak-detector": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-runtime/-/jest-runtime-27.5.1.tgz", + "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/globals": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-serializer": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz", + "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^27.5.1", + "semver": "^7.3.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-validate/-/jest-validate-27.5.1.tgz", + "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^27.5.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "leven": "^3.1.0", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watch-typeahead": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", + "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "jest-regex-util": "^28.0.0", + "jest-watcher": "^28.0.0", + "slash": "^4.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmmirror.com/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmmirror.com/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "license": "MIT", + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmmirror.com/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmmirror.com/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmmirror.com/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmmirror.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "license": "MIT", + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmmirror.com/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmmirror.com/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "license": "MIT", + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length/node_modules/char-regex": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/char-regex/-/char-regex-2.0.2.tgz", + "integrity": "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watcher": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.5.1", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmmirror.com/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/form-data": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "license": "MIT" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "license": "MIT", + "dependencies": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" + } + }, + "node_modules/jsonpath/node_modules/esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmmirror.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmmirror.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/launch-editor": { + "version": "2.11.1", + "resolved": "https://registry.npmmirror.com/launch-editor/-/launch-editor-2.11.1.tgz", + "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmmirror.com/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.4", + "resolved": "https://registry.npmmirror.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", + "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmmirror.com/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "license": "MIT" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.20", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.20.tgz", + "integrity": "sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.22", + "resolved": "https://registry.npmmirror.com/nwsapi/-/nwsapi-2.2.22.tgz", + "integrity": "sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmmirror.com/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmmirror.com/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.8", + "resolved": "https://registry.npmmirror.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz", + "integrity": "sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==", + "license": "MIT", + "dependencies": { + "array.prototype.reduce": "^1.0.6", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "gopd": "^1.0.1", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmmirror.com/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pdfast": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/pdfast/-/pdfast-0.2.0.tgz", + "integrity": "sha512-cq6TTu6qKSFUHwEahi68k/kqN2mfepjkGrG9Un70cgdRRKLKY6Rf8P8uvP2NvZktaQZNF3YE7agEkLj0vGK9bA==", + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-browser-comments": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", + "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", + "license": "CC0-1.0", + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "browserslist": ">=4", + "postcss": ">=8" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmmirror.com/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmmirror.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmmirror.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.11", + "resolved": "https://registry.npmmirror.com/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", + "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmmirror.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-flexbugs-fixes": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.1.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "license": "CC0-1.0", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "license": "CC0-1.0", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmmirror.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmmirror.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "license": "MIT", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmmirror.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-normalize": { + "version": "10.0.1", + "resolved": "https://registry.npmmirror.com/postcss-normalize/-/postcss-normalize-10.0.1.tgz", + "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/normalize.css": "*", + "postcss-browser-comments": "^4", + "sanitize.css": "*" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "browserslist": ">= 4", + "postcss": ">= 8" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "license": "MIT", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "license": "MIT", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmmirror.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmmirror.com/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "license": "CC0-1.0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.8.3", + "resolved": "https://registry.npmmirror.com/postcss-preset-env/-/postcss-preset-env-7.8.3.tgz", + "integrity": "sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag==", + "license": "CC0-1.0", + "dependencies": { + "@csstools/postcss-cascade-layers": "^1.1.1", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.13", + "browserslist": "^4.21.4", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.1.0", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.10", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.2.0", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmmirror.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "license": "CC0-1.0", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-svgo/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/postcss-svgo/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, + "node_modules/postcss-svgo/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-svgo/node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmmirror.com/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "license": "ISC" + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "license": "MIT", + "dependencies": { + "quickselect": "^2.0.0" + } + }, + "node_modules/rc-cascader": { + "version": "3.34.0", + "resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-3.34.0.tgz", + "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmmirror.com/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/rc-drawer/-/rc-drawer-7.3.0.tgz", + "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-2.7.0.tgz", + "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "resolved": "https://registry.npmmirror.com/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "resolved": "https://registry.npmmirror.com/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.20.0", + "resolved": "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-2.20.0.tgz", + "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.8.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.10.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmmirror.com/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.4", + "resolved": "https://registry.npmmirror.com/rc-notification/-/rc-notification-5.6.4.tgz", + "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rc-overflow/-/rc-overflow-1.4.1.tgz", + "integrity": "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmmirror.com/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "resolved": "https://registry.npmmirror.com/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/rc-segmented/-/rc-segmented-2.7.0.tgz", + "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.8", + "resolved": "https://registry.npmmirror.com/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.8", + "resolved": "https://registry.npmmirror.com/rc-slider/-/rc-slider-11.1.8.tgz", + "integrity": "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.52.7", + "resolved": "https://registry.npmmirror.com/rc-table/-/rc-table-7.52.7.tgz", + "integrity": "sha512-yuZfnTpuHwRa4JH+F28wQfGeDzqtgIDvLBBJk5sFncXQjTExhtBNc6dPfVo5pL5SjabJEoejefs6wsrAKfhDoQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.7.0", + "resolved": "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-15.7.0.tgz", + "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.10.2", + "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.10.2.tgz", + "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.8.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "resolved": "https://registry.npmmirror.com/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "resolved": "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "resolved": "https://registry.npmmirror.com/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.9.2", + "resolved": "https://registry.npmmirror.com/rc-upload/-/rc-upload-4.9.2.tgz", + "integrity": "sha512-nHx+9rbd1FKMiMRYsqQ3NkXUv7COHPBo3X1Obwq9SWS6/diF/A0aJ5OHubvwUAIDs+4RMleljV0pcrNUc823GQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-virtual-list": { + "version": "3.19.1", + "resolved": "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.19.1.tgz", + "integrity": "sha512-DCapO2oyPqmooGhxBuXHM4lFuX+sshQwWqqkuyFA+4rShLe//+GEPVwiDgO+jKtKHtbeYwZoNvetwfHdOf+iUQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmmirror.com/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-app-polyfill": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", + "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", + "license": "MIT", + "dependencies": { + "core-js": "^3.19.2", + "object-assign": "^4.1.1", + "promise": "^8.1.0", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.9", + "whatwg-fetch": "^3.6.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/react-dev-utils/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-error-overlay": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/react-error-overlay/-/react-error-overlay-6.1.0.tgz", + "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.11.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.11.0.tgz", + "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.8.2", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.8.2.tgz", + "integrity": "sha512-7M2fR1JbIZ/jFWqelpvSZx+7vd7UlBTfdZqf6OSdF9g6+sfdqJDAWcak6ervbHph200ePlu+7G8LdoiC3ReyAQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.8.2", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.8.2.tgz", + "integrity": "sha512-Z4VM5mKDipal2jQ385H6UBhiiEDlnJPx6jyWsTYoZQdl5TrjxEV2a9yl3Fi60NBJxYzOTGTTHXPi0pdizvTwow==", + "license": "MIT", + "dependencies": { + "react-router": "7.8.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-scripts": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/react-scripts/-/react-scripts-5.0.1.tgz", + "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", + "@svgr/webpack": "^5.5.0", + "babel-jest": "^27.4.2", + "babel-loader": "^8.2.3", + "babel-plugin-named-asset-import": "^0.3.8", + "babel-preset-react-app": "^10.0.1", + "bfj": "^7.0.2", + "browserslist": "^4.18.1", + "camelcase": "^6.2.1", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "css-loader": "^6.5.1", + "css-minimizer-webpack-plugin": "^3.2.0", + "dotenv": "^10.0.0", + "dotenv-expand": "^5.1.0", + "eslint": "^8.3.0", + "eslint-config-react-app": "^7.0.1", + "eslint-webpack-plugin": "^3.1.1", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.0", + "html-webpack-plugin": "^5.5.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^27.4.3", + "jest-resolve": "^27.4.2", + "jest-watch-typeahead": "^1.0.0", + "mini-css-extract-plugin": "^2.4.5", + "postcss": "^8.4.4", + "postcss-flexbugs-fixes": "^5.0.2", + "postcss-loader": "^6.2.1", + "postcss-normalize": "^10.0.1", + "postcss-preset-env": "^7.0.1", + "prompts": "^2.4.2", + "react-app-polyfill": "^3.0.0", + "react-dev-utils": "^12.0.1", + "react-refresh": "^0.11.0", + "resolve": "^1.20.0", + "resolve-url-loader": "^4.0.0", + "sass-loader": "^12.3.0", + "semver": "^7.3.5", + "source-map-loader": "^3.0.0", + "style-loader": "^3.3.1", + "tailwindcss": "^3.0.2", + "terser-webpack-plugin": "^5.2.5", + "webpack": "^5.64.4", + "webpack-dev-server": "^4.6.0", + "webpack-manifest-plugin": "^4.0.2", + "workbox-webpack-plugin": "^6.4.1" + }, + "bin": { + "react-scripts": "bin/react-scripts.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + }, + "peerDependencies": { + "react": ">= 16", + "typescript": "^3.2.1 || ^4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/regex-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/regex-parser/-/regex-parser-2.3.1.tgz", + "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-url-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", + "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", + "license": "MIT", + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^7.0.35", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=8.9" + }, + "peerDependencies": { + "rework": "1.0.1", + "rework-visit": "1.0.0" + }, + "peerDependenciesMeta": { + "rework": { + "optional": true + }, + "rework-visit": { + "optional": true + } + } + }, + "node_modules/resolve-url-loader/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/resolve-url-loader/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC" + }, + "node_modules/resolve-url-loader/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmmirror.com/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sanitize.css": { + "version": "13.0.0", + "resolved": "https://registry.npmmirror.com/sanitize.css/-/sanitize.css-13.0.0.tgz", + "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==", + "license": "CC0-1.0" + }, + "node_modules/sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmmirror.com/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "license": "MIT", + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "license": "ISC" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/source-map-loader/-/source-map-loader-3.0.2.tgz", + "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", + "license": "MIT", + "dependencies": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "license": "MIT" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "license": "MIT" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "license": "MIT", + "dependencies": { + "escodegen": "^1.8.1" + } + }, + "node_modules/static-eval/node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmmirror.com/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/static-eval/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/static-eval/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "license": "MIT", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-eval/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmmirror.com/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "license": "MIT" + }, + "node_modules/svg-path-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/svg-path-parser/-/svg-path-parser-1.1.0.tgz", + "integrity": "sha512-jGCUqcQyXpfe38R7RFfhrMyfXcBmpMNJI/B+4CE9/Unkh98UporAc461GTthv+TVDuZXsBx7/WiwJb1Oh4tt4A==", + "license": "MIT" + }, + "node_modules/svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "license": "MIT", + "dependencies": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/svgo/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/svgo/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/svgo/node_modules/css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmmirror.com/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "license": "BSD-2-Clause" + }, + "node_modules/svgo/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/svgo/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/svgo/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/tapable": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmmirror.com/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/throat": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/throat/-/throat-6.0.2.tgz", + "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==", + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", + "license": "MIT" + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmmirror.com/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmmirror.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmmirror.com/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==", + "license": "MIT" + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", + "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "license": "ISC", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "license": "MIT", + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmmirror.com/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10.4" + } + }, + "node_modules/webpack": { + "version": "5.101.3", + "resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.101.3.tgz", + "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmmirror.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-manifest-plugin": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", + "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", + "license": "MIT", + "dependencies": { + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "webpack": "^4.44.2 || ^5.47.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-2.3.1.tgz", + "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "license": "MIT", + "dependencies": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmmirror.com/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmmirror.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "license": "MIT", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-background-sync": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", + "integrity": "sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==", + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-broadcast-update/-/workbox-broadcast-update-6.6.0.tgz", + "integrity": "sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-build": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-build/-/workbox-build-6.6.0.tgz", + "integrity": "sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==", + "license": "MIT", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.11.1", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^11.2.1", + "@rollup/plugin-replace": "^2.4.1", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.43.1", + "rollup-plugin-terser": "^7.0.0", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "6.6.0", + "workbox-broadcast-update": "6.6.0", + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-google-analytics": "6.6.0", + "workbox-navigation-preload": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-range-requests": "6.6.0", + "workbox-recipes": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0", + "workbox-streams": "6.6.0", + "workbox-sw": "6.6.0", + "workbox-window": "6.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmmirror.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "license": "MIT", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/workbox-build/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "license": "BSD-2-Clause" + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-cacheable-response/-/workbox-cacheable-response-6.6.0.tgz", + "integrity": "sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==", + "deprecated": "workbox-background-sync@6.6.0", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-core": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-core/-/workbox-core-6.6.0.tgz", + "integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==", + "license": "MIT" + }, + "node_modules/workbox-expiration": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-expiration/-/workbox-expiration-6.6.0.tgz", + "integrity": "sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==", + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-google-analytics": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-google-analytics/-/workbox-google-analytics-6.6.0.tgz", + "integrity": "sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==", + "deprecated": "It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained", + "license": "MIT", + "dependencies": { + "workbox-background-sync": "6.6.0", + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-navigation-preload/-/workbox-navigation-preload-6.6.0.tgz", + "integrity": "sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-precaching": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-precaching/-/workbox-precaching-6.6.0.tgz", + "integrity": "sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-range-requests": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-range-requests/-/workbox-range-requests-6.6.0.tgz", + "integrity": "sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-recipes": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-recipes/-/workbox-recipes-6.6.0.tgz", + "integrity": "sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==", + "license": "MIT", + "dependencies": { + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-routing": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-routing/-/workbox-routing-6.6.0.tgz", + "integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-strategies": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-strategies/-/workbox-strategies-6.6.0.tgz", + "integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-streams": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-streams/-/workbox-streams-6.6.0.tgz", + "integrity": "sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==", + "license": "MIT", + "dependencies": { + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0" + } + }, + "node_modules/workbox-sw": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-sw/-/workbox-sw-6.6.0.tgz", + "integrity": "sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==", + "license": "MIT" + }, + "node_modules/workbox-webpack-plugin": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.0.tgz", + "integrity": "sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==", + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "^2.1.0", + "pretty-bytes": "^5.4.1", + "upath": "^1.2.0", + "webpack-sources": "^1.4.3", + "workbox-build": "6.6.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "webpack": "^4.4.0 || ^5.9.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "license": "MIT", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/workbox-window": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/workbox-window/-/workbox-window-6.6.0.tgz", + "integrity": "sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==", + "license": "MIT", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "6.6.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "license": "Apache-2.0" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + } + } +} diff --git a/jcpp-web-ui/package.json b/jcpp-web-ui/package.json new file mode 100644 index 0000000..dbd9395 --- /dev/null +++ b/jcpp-web-ui/package.json @@ -0,0 +1,44 @@ +{ + "name": "jcpp-web-ui", + "version": "0.1.0", + "private": true, + "dependencies": { + "@ant-design/icons": "^6.0.0", + "@ant-design/v5-patch-for-react-19": "^1.0.3", + "@antv/g2": "^5.4.0", + "@types/node": "^16.18.126", + "@types/react": "^19.1.12", + "@types/react-dom": "^19.1.9", + "antd": "^5.27.2", + "axios": "^1.11.0", + "china-division": "^2.7.0", + "echarts": "^6.0.0", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-router-dom": "^7.8.2", + "react-scripts": "5.0.1", + "typescript": "^4.9.5" + }, + "scripts": { + "start": "GENERATE_SOURCEMAP=false react-scripts start", + "build": "GENERATE_SOURCEMAP=false react-scripts build", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/jcpp-web-ui/pom.xml b/jcpp-web-ui/pom.xml new file mode 100644 index 0000000..3b2831f --- /dev/null +++ b/jcpp-web-ui/pom.xml @@ -0,0 +1,159 @@ + + + + + sanbing + jcpp-parent + 1.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + jcpp-web-ui + jar + JChargePointProtocol Server Web UI Module + 前端模块 + + + ${basedir}/.. + + + + + + com.github.eirslett + frontend-maven-plugin + + target + ${basedir} + + + + install node and npm + + install-node-and-npm + + + v22.9.0 + 10.8.0 + https://npmmirror.com/mirrors/node/ + + + + set npm registry + + npm + + + config set registry https://registry.npmmirror.com + + + + npm install + + npm + + + install --no-optional + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-frontend-build + compile + + copy-resources + + + ${project.build.directory}/classes/public + + + ${basedir}/build + false + + + + + + + + + + + npm-build + + true + + + + + com.github.eirslett + frontend-maven-plugin + + target + ${basedir} + + + + npm build + compile + + npm + + + run build + + + + + + + + + npm-start + + + npm-start + + + + + + com.github.eirslett + frontend-maven-plugin + + target + ${basedir} + + + + npm start + + npm + + + start + + + + + + + + + + diff --git a/jcpp-web-ui/public/favicon.ico b/jcpp-web-ui/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..d1c78eaa3b4dcaf00dab492bf00f3528a57e3ff6 GIT binary patch literal 1118 zcmeHDISzm@3^NiB=)}Up$Q$?~k1mOdrOHy340Iz^>&CHr1Xyg80$UGY1vUVuO{1J? zyrI`-#(rNlGe>X8cwM9ZtWR}3M|tM;4d#mKSACTK{?-BZHR_kvzw95+Ci + + + + + + + + + + + JCPP充电桩管理系统 + + + +

+ + \ No newline at end of file diff --git a/jcpp-web-ui/public/manifest.json b/jcpp-web-ui/public/manifest.json new file mode 100644 index 0000000..8ccd320 --- /dev/null +++ b/jcpp-web-ui/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "JCPP充电桩", + "name": "JCPP充电桩协议管理系统", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/jcpp-web-ui/public/robots.txt b/jcpp-web-ui/public/robots.txt new file mode 100644 index 0000000..0a043b6 --- /dev/null +++ b/jcpp-web-ui/public/robots.txt @@ -0,0 +1,10 @@ +==== + 开源代码,仅供学习和交流研究使用,商用请联系三丙 + 微信:mohan_88888 + 抖音:程序员三丙 + 付费课程知识星球:https://t.zsxq.com/aKtXo +==== + +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/jcpp-web-ui/src/App.css b/jcpp-web-ui/src/App.css new file mode 100644 index 0000000..3fc2829 --- /dev/null +++ b/jcpp-web-ui/src/App.css @@ -0,0 +1,181 @@ +/* + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ + +/* 全局样式重置 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', + 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji', + 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-size: 14px; + line-height: 1.5715; + color: #000000d9; + background-color: #f0f2f5; +} + +.App { + min-height: 100vh; +} + +/* 覆盖antd样式 */ +.ant-layout { + background: #f0f2f5; +} + +.ant-layout-content { + background: #f0f2f5; +} + +/* 表格样式优化 */ +.ant-table-thead > tr > th { + background: #fafafa; + font-weight: 600; +} + +/* 卡片样式优化 */ +.ant-card { + border-radius: 8px; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02); +} + +/* 按钮样式优化 */ +.ant-btn { + border-radius: 6px; +} + +/* Modal样式优化 */ +.ant-modal { + border-radius: 8px; +} + + +/* 用户信息显示样式 */ +.user-info-wrapper { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 16px; + border-radius: 20px; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border: 1px solid rgba(0, 0, 0, 0.06); + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.user-info-wrapper::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(24, 144, 255, 0.1) 0%, rgba(114, 46, 209, 0.1) 100%); + opacity: 0; + transition: opacity 0.3s ease; +} + +.user-info-wrapper:hover::before { + opacity: 1; +} + +.user-info-wrapper:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(24, 144, 255, 0.15); + border-color: rgba(24, 144, 255, 0.3); +} + +.user-avatar { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; + border: 2px solid rgba(255, 255, 255, 0.9); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); + position: relative; + z-index: 1; +} + +.user-details { + display: flex; + flex-direction: column; + gap: 2px; + position: relative; + z-index: 1; +} + +.user-name { + font-size: 14px; + font-weight: 600; + color: #2c3e50; + line-height: 1.2; + letter-spacing: 0.01em; +} + +.user-role { + font-size: 12px; + color: #6c757d; + font-weight: 400; + line-height: 1; +} + +.dropdown-arrow { + font-size: 10px; + color: #6c757d; + transition: all 0.3s ease; + position: relative; + z-index: 1; +} + +.user-info-wrapper:hover .dropdown-arrow { + color: #495057; + transform: rotate(180deg); +} + +/* 响应式优化 */ +@media (max-width: 768px) { + .ant-form-inline .ant-form-item { + margin-bottom: 16px; + } + + .ant-table { + font-size: 12px; + } + + .user-info-wrapper { + padding: 6px 12px; + gap: 8px; + } + + .user-details { + display: none; + } + + .user-avatar { + width: 32px !important; + height: 32px !important; + } +} + +@media (max-width: 480px) { + .user-info-wrapper { + padding: 4px 8px; + border-radius: 16px; + } + + .user-avatar { + width: 28px !important; + height: 28px !important; + } + + .dropdown-arrow { + font-size: 8px; + } +} \ No newline at end of file diff --git a/jcpp-web-ui/src/App.tsx b/jcpp-web-ui/src/App.tsx new file mode 100644 index 0000000..0bf9b0c --- /dev/null +++ b/jcpp-web-ui/src/App.tsx @@ -0,0 +1,85 @@ +/* + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +import React, {useEffect} from 'react'; +import {BrowserRouter as Router, Navigate, Route, Routes} from 'react-router-dom'; +import {ConfigProvider, message} from 'antd'; +import zhCN from 'antd/locale/zh_CN'; +import {AuthProvider} from './contexts/AuthContext'; +import {ToastProvider} from './contexts/ToastContext'; +import ProtectedRoute from './components/ProtectedRoute'; +import Layout from './components/Layout'; +import Login from './components/Login'; +import Dashboard from './components/Dashboard'; +import StationManagement from './components/StationManagement'; +import PileManagement from './components/PileManagement'; +import GunManagement from './components/GunManagement'; +import NotFoundRedirect from './components/NotFoundRedirect'; +import './App.css'; + +const App: React.FC = () => { + // 配置全局message + useEffect(() => { + message.config({ + top: 50, // 距离顶部位置 + duration: 3, // 默认持续时间3秒 + maxCount: 3, // 最多同时显示3个 + }); + }, []); + + return ( + + + + + + {/* 登录页面 */} + } /> + + {/* 根路径重定向 */} + } /> + + {/* 受保护的路由 - 使用 /page 前缀 */} + + + + + + } /> + + + + + + } /> + + + + + + } /> + + + + + + } /> + + {/* 智能404重定向 - 根据登录状态决定重定向目标 */} + } /> + + + + + + ); +}; + +export default App; \ No newline at end of file diff --git a/jcpp-web-ui/src/assets/icons/logo192.svg b/jcpp-web-ui/src/assets/icons/logo192.svg new file mode 100644 index 0000000..6b09fad --- /dev/null +++ b/jcpp-web-ui/src/assets/icons/logo192.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcpp-web-ui/src/components/AppLogo.tsx b/jcpp-web-ui/src/components/AppLogo.tsx new file mode 100644 index 0000000..904b1cb --- /dev/null +++ b/jcpp-web-ui/src/components/AppLogo.tsx @@ -0,0 +1,35 @@ +/* + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +import React from 'react'; +import logo192 from '../assets/icons/logo192.svg'; + +interface AppLogoProps { + size?: number; + className?: string; +} + +/** + * 应用Logo组件 + * 使用新的麻将桌风格充电桩图标 + */ +const AppLogo: React.FC = ({ size = 48, className }) => { + return ( + JCPP充电桩管理系统 + ); +}; + +export default AppLogo; diff --git a/jcpp-web-ui/src/components/Dashboard.tsx b/jcpp-web-ui/src/components/Dashboard.tsx new file mode 100644 index 0000000..9151032 --- /dev/null +++ b/jcpp-web-ui/src/components/Dashboard.tsx @@ -0,0 +1,447 @@ +/* + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +import React, {useEffect, useRef, useState} from 'react'; +import {Alert, Button, Card, Col, Row, Spin, Statistic} from 'antd'; +import {AimOutlined, EnvironmentOutlined, ReloadOutlined, ThunderboltOutlined} from '@ant-design/icons'; +import * as echarts from 'echarts'; +import {type DashboardStats, getDashboardStats} from '../services/dashboardService'; +import {getErrorMessage} from '../services/api'; +import {showMessage} from '../utils'; + +const Dashboard: React.FC = () => { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const pileChartRef = useRef(null); + const gunChartRef = useRef(null); + const pileChartInstance = useRef(null); + const gunChartInstance = useRef(null); + + // 加载仪表盘数据 + const loadDashboardStats = async () => { + setLoading(true); + setError(null); + try { + const data = await getDashboardStats(); + setStats(data); + console.log('Dashboard stats loaded:', data); + } catch (error: any) { + const errorMessage = getErrorMessage(error); + setError(errorMessage); + showMessage.error(`加载仪表盘数据失败:${errorMessage}`); + console.error('Dashboard stats loading failed:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + loadDashboardStats(); + // 每30秒自动刷新数据 + const interval = setInterval(loadDashboardStats, 30000); + return () => clearInterval(interval); + }, []); + + // 充电桩状态饼图 + useEffect(() => { + if (!pileChartRef.current || loading || !stats?.pileStatusDistribution) return; + + // 销毁之前的图表实例 + if (pileChartInstance.current) { + pileChartInstance.current.dispose(); + } + + // 创建新的图表实例 + const chart = echarts.init(pileChartRef.current); + pileChartInstance.current = chart; + + const { pileStatusDistribution } = stats; + const data = [ + { + name: '在线', + value: pileStatusDistribution.onlinePiles, + itemStyle: { color: '#52c41a' } + }, + { + name: '离线', + value: pileStatusDistribution.offlinePiles, + itemStyle: { color: '#ff7875' } + } + ].filter(item => item.value > 0); + + const option = { + backgroundColor: 'transparent', + title: { + text: '充电桩在线状态', + left: 'center', + top: 15, + textStyle: { + fontSize: 16, + fontWeight: 'normal', + color: '#262626' + } + }, + tooltip: { + trigger: 'item', + formatter: '{a}
{b}: {c}台 ({d}%)', + backgroundColor: 'rgba(0, 0, 0, 0.75)', + borderWidth: 0, + textStyle: { + color: '#fff', + fontSize: 12 + } + }, + legend: { + orient: 'horizontal', + bottom: 15, + data: data.map(item => item.name), + textStyle: { + fontSize: 12, + color: '#666' + } + }, + series: [ + { + name: '充电桩状态', + type: 'pie', + radius: ['45%', '65%'], + center: ['50%', '55%'], + data: data, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.2)' + } + }, + label: { + formatter: '{b}\n{c}台\n{d}%', + fontSize: 11, + color: '#666' + }, + labelLine: { + length: 10, + length2: 5 + } + } + ] + }; + + chart.setOption(option); + + // 窗口大小变化时重新调整图表 + const handleResize = () => chart.resize(); + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [stats, loading]); + + // 充电枪状态饼图 + useEffect(() => { + if (!gunChartRef.current || loading || !stats?.gunStatusDistribution) return; + + // 销毁之前的图表实例 + if (gunChartInstance.current) { + gunChartInstance.current.dispose(); + } + + // 创建新的图表实例 + const chart = echarts.init(gunChartRef.current); + gunChartInstance.current = chart; + + const { gunStatusDistribution } = stats; + + // 准备数据 - 只显示有数据的状态 + const statusData = [ + { name: '空闲', value: gunStatusDistribution.idleGuns, color: '#52c41a' }, + { name: '已插枪', value: gunStatusDistribution.insertedGuns, color: '#faad14' }, + { name: '充电中', value: gunStatusDistribution.chargingGuns, color: '#1890ff' }, + { name: '充电完成', value: gunStatusDistribution.chargeCompleteGuns, color: '#13c2c2' }, + { name: '放电准备', value: gunStatusDistribution.dischargeReadyGuns, color: '#722ed1' }, + { name: '放电中', value: gunStatusDistribution.dischargingGuns, color: '#eb2f96' }, + { name: '放电完成', value: gunStatusDistribution.dischargeCompleteGuns, color: '#fa8c16' }, + { name: '预约', value: gunStatusDistribution.reservedGuns, color: '#a0d911' }, + { name: '故障', value: gunStatusDistribution.faultGuns, color: '#ff7875' } + ].filter(item => item.value > 0); + + const data = statusData.map(item => ({ + name: item.name, + value: item.value, + itemStyle: { color: item.color } + })); + + const option = { + backgroundColor: 'transparent', + title: { + text: '充电枪运行状态', + left: 'center', + top: 15, + textStyle: { + fontSize: 16, + fontWeight: 'normal', + color: '#262626' + } + }, + tooltip: { + trigger: 'item', + formatter: (params: any) => { + const percentage = ((params.value / gunStatusDistribution.totalGuns) * 100).toFixed(1); + return `${params.name}
数量: ${params.value}台
占比: ${percentage}%`; + }, + backgroundColor: 'rgba(0, 0, 0, 0.75)', + borderWidth: 0, + textStyle: { + color: '#fff', + fontSize: 12 + } + }, + legend: { + orient: 'vertical', + left: 'left', + top: 'middle', + data: data.map(item => item.name), + textStyle: { + fontSize: 11, + color: '#666' + }, + formatter: (name: string) => { + const item = statusData.find(d => d.name === name); + return item ? `${name} (${item.value})` : name; + } + }, + series: [ + { + name: '充电枪状态', + type: 'pie', + radius: ['35%', '60%'], + center: ['65%', '55%'], + data: data, + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.2)' + } + }, + label: { + formatter: '{b}\n{d}%', + fontSize: 10, + color: '#666' + }, + labelLine: { + length: 8, + length2: 3 + } + } + ] + }; + + chart.setOption(option); + + // 窗口大小变化时重新调整图表 + const handleResize = () => chart.resize(); + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [stats, loading]); + + // 组件卸载时清理图表 + useEffect(() => { + return () => { + if (pileChartInstance.current) { + pileChartInstance.current.dispose(); + } + if (gunChartInstance.current) { + gunChartInstance.current.dispose(); + } + }; + }, []); + + // 首次加载状态 + if (loading && !stats) { + return ( +
+ +
加载仪表盘数据中...
+
+ ); + } + + // 错误状态 + if (error && !stats) { + return ( +
+ + 重试 + + } + /> +
+ ); + } + + return ( +
+ {/* 标题区域 */} +
+

+ 充电站管理仪表盘 +

+ +
+ + {/* 统计卡片 */} + + + + } + valueStyle={{ color: '#262626', fontSize: '24px', fontWeight: '500' }} + /> + + + + + } + valueStyle={{ color: '#262626', fontSize: '24px', fontWeight: '500' }} + /> + + + + + } + valueStyle={{ color: '#262626', fontSize: '24px', fontWeight: '500' }} + /> + + + + + {/* 图表区域 */} + {stats?.pileStatusDistribution && stats?.gunStatusDistribution ? ( + + + +
+ + + + + +
+ + + + ) : ( + +
+ {stats ? '暂无图表数据' : '等待图表数据加载...'} +
+
+ )} +
+ ); +}; + +export default Dashboard; \ No newline at end of file diff --git a/jcpp-web-ui/src/components/GlobalToast.tsx b/jcpp-web-ui/src/components/GlobalToast.tsx new file mode 100644 index 0000000..1f2a2a1 --- /dev/null +++ b/jcpp-web-ui/src/components/GlobalToast.tsx @@ -0,0 +1,111 @@ +/* + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +import React, {useEffect, useState} from 'react'; +import {Alert} from 'antd'; +import {CheckCircleOutlined, CloseCircleOutlined} from '@ant-design/icons'; + +export interface ToastMessage { + id: string; + message: string; + type: 'success' | 'error'; + duration?: number; // 显示时长,毫秒 +} + +interface GlobalToastProps { + messages: ToastMessage[]; + onRemove: (id: string) => void; +} + +const GlobalToast: React.FC = ({ messages, onRemove }) => { + const [visibleMessages, setVisibleMessages] = useState([]); + + useEffect(() => { + setVisibleMessages(messages); + + // 为每个消息设置自动消失定时器 + messages.forEach((message) => { + const duration = message.duration || 3000; // 默认3秒 + setTimeout(() => { + onRemove(message.id); + }, duration); + }); + }, [messages, onRemove]); + + if (visibleMessages.length === 0) { + return null; + } + + return ( +
+ {visibleMessages.map((message, index) => ( +
+ : } + showIcon + closable + onClose={() => onRemove(message.id)} + style={{ + borderRadius: '12px', + boxShadow: '0 8px 32px rgba(0, 0, 0, 0.12)', + border: 'none', + backgroundColor: message.type === 'success' ? '#f6ffed' : '#fff2f0', + fontSize: '14px', + lineHeight: '1.5', + padding: '12px 16px', + minHeight: '56px', + display: 'flex', + alignItems: 'center', + backdropFilter: 'blur(8px)', + ...(message.type === 'success' ? { + background: 'linear-gradient(135deg, #f6ffed 0%, #d9f7be 100%)', + borderLeft: '4px solid #52c41a' + } : { + background: 'linear-gradient(135deg, #fff2f0 0%, #ffccc7 100%)', + borderLeft: '4px solid #ff4d4f' + }) + }} + /> +
+ ))} + +
+ ); +}; + +export default GlobalToast; diff --git a/jcpp-web-ui/src/components/GunManagement.tsx b/jcpp-web-ui/src/components/GunManagement.tsx new file mode 100644 index 0000000..7207863 --- /dev/null +++ b/jcpp-web-ui/src/components/GunManagement.tsx @@ -0,0 +1,970 @@ +/* + * 开源代码,仅供学习和交流研究使用,商用请联系三丙 + * 微信:mohan_88888 + * 抖音:程序员三丙 + * 付费课程知识星球:https://t.zsxq.com/aKtXo + */ +import React, {useEffect, useMemo, useState} from 'react'; +import { + Button, + Card, + Checkbox, + Col, + Dropdown, + Form, + Input, + Modal, + Popconfirm, + Row, + Select, + Space, + Table, + Tag, + Typography +} from 'antd'; +import {DeleteOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, TableOutlined} from '@ant-design/icons'; +import type {ColumnsType, TableProps} from 'antd/es/table'; +import {formatTimestamp, generateGunCode, showMessage} from '../utils'; +import {getErrorMessage} from '../services/api'; +import * as gunService from '../services/gunService'; +import * as stationService from '../services/stationService'; +import {pileService} from '../services/pileService'; +import type {Gun, GunCreateRequest, PileOption, StationOption} from '../types'; + +const { confirm } = Modal; + +const GunManagement: React.FC = () => { + const [dataSource, setDataSource] = useState([]); + const [loading, setLoading] = useState(false); + const [searchForm] = Form.useForm(); + const [form] = Form.useForm(); + const [stationOptions, setStationOptions] = useState([]); + const [pileOptions, setPileOptions] = useState([]); + const [modalVisible, setModalVisible] = useState(false); + const [modalLoading, setModalLoading] = useState(false); + const [isEdit, setIsEdit] = useState(false); + const [currentRecord, setCurrentRecord] = useState(null); + + // 分页和搜索状态 + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 10, + total: 0, + showSizeChanger: true, + showQuickJumper: true, + showTotal: (total: number) => `共 ${total} 条记录` + }); + + const [searchParams, setSearchParams] = useState<{ + page: number; + size: number; + gunName?: string; + gunCode?: string; + gunNo?: string; + stationId?: string; + sortField?: string; + sortOrder?: string; + }>({ + page: 1, + size: 10 + }); + + // 批量删除相关状态 + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [batchDeleting, setBatchDeleting] = useState(false); + + // 列可见性配置 + interface ColumnConfig { + key: string; + title: string; + defaultVisible: boolean; + } + + const columnConfigs: ColumnConfig[] = [ + { key: 'gunName', title: '充电枪名称', defaultVisible: true }, + { key: 'gunCode', title: '充电枪编码', defaultVisible: true }, + { key: 'gunNo', title: '枪号', defaultVisible: true }, + { key: 'stationName', title: '所属充电站', defaultVisible: true }, + { key: 'pileName', title: '所属充电桩', defaultVisible: true }, + { key: 'runStatus', title: '运行状态', defaultVisible: true }, + { key: 'createdTime', title: '创建时间', defaultVisible: true }, + { key: 'updatedTime', title: '更新时间', defaultVisible: false }, + ]; + + // 列可见性状态 + const [visibleColumns, setVisibleColumns] = useState>(() => { + const defaultVisible: Record = {}; + columnConfigs.forEach(config => { + defaultVisible[config.key] = config.defaultVisible; + }); + return defaultVisible; + }); + + // 列顺序状态(不包含action列,action列始终在最后) + const [columnOrder, setColumnOrder] = useState(() => { + return columnConfigs.map(config => config.key); + }); + + // 完整的表格列定义 + const allColumns: ColumnsType = useMemo(() => [ + { + title: '充电枪名称', + dataIndex: 'gunName', + key: 'gunName', + width: 200, + sorter: true, + }, + { + title: '充电枪编码', + dataIndex: 'gunCode', + key: 'gunCode', + width: 150, + sorter: true, + }, + { + title: '枪号', + dataIndex: 'gunNo', + key: 'gunNo', + width: 55, + sorter: true, + }, + { + title: '所属充电站', + dataIndex: 'stationName', + key: 'stationName', + width: 150, + sorter: true, + render: (stationName: string) => stationName || '-', + }, + { + title: '所属充电桩', + dataIndex: 'pileName', + key: 'pileName', + width: 150, + sorter: true, + render: (pileName: string, record: Gun) => ( +
+
{pileName || record.pileCode || '-'}
+ {record.pileCode && pileName && ( +
{record.pileCode}
+ )} +
+ ), + }, + { + title: '运行状态', + dataIndex: 'runStatus', + key: 'runStatus', + width: 100, + render: (status: string) => { + const getRunStatusColor = (status: string) => { + const colors: Record = { + 'IDLE': 'green', + 'INSERTED': 'orange', + 'CHARGING': 'blue', + 'CHARGE_COMPLETE': 'cyan', + 'DISCHARGE_READY': 'purple', + 'DISCHARGING': 'magenta', + 'DISCHARGE_COMPLETE': 'lime', + 'RESERVED': 'geekblue', + 'FAULT': 'red' + }; + return colors[status] || 'default'; + }; + + const getRunStatusText = (status: string) => { + const texts: Record = { + 'IDLE': '空闲', + 'INSERTED': '已插枪', + 'CHARGING': '充电中', + 'CHARGE_COMPLETE': '充电完成', + 'DISCHARGE_READY': '放电准备', + 'DISCHARGING': '放电中', + 'DISCHARGE_COMPLETE': '放电完成', + 'RESERVED': '预约中', + 'FAULT': '故障' + }; + return texts[status] || status; + }; + + return {getRunStatusText(status)}; + }, + }, + { + title: '创建时间', + dataIndex: 'createdTime', + key: 'createdTime', + width: 95, + sorter: true, + render: (timestamp: number) => { + const formatted = formatTimestamp(timestamp); + if (!formatted || formatted === '-') return formatted; + const parts = formatted.split(' '); + return ( +
+
{parts[0]}
+
{parts[1]}
+
+ ); + } + }, + { + title: '更新时间', + dataIndex: 'updatedTime', + key: 'updatedTime', + width: 95, + sorter: true, + render: (timestamp: number) => { + const formatted = formatTimestamp(timestamp); + if (!formatted || formatted === '-') return formatted; + const parts = formatted.split(' '); + return ( +
+
{parts[0]}
+
{parts[1]}
+
+ ); + } + }, + { + title: '操作', + key: 'action', + width: 150, + fixed: 'right', + render: (record: Gun) => ( + + + + +

确定要删除充电枪 {record.gunName} 吗?

+

此操作不可撤销,请谨慎操作!

+
+ } + onConfirm={() => handleDelete(record)} + okText="确定删除" + okType="danger" + cancelText="取消" + > + + + + ), + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + ], []); + + // 根据可见性和顺序过滤并排序列 + const visibleColumnsData = useMemo(() => { + // 先按照用户定义的顺序排序(不包含action) + const orderedColumns = columnOrder.map(key => { + return allColumns.find(col => col.key === key); + }).filter(Boolean) as ColumnsType; + + // 过滤出可见的列 + const filtered = orderedColumns.filter(column => { + return visibleColumns[column.key as string]; + }); + + // 找到操作列并确保始终在最后 + const actionColumn = allColumns.find(col => col.key === 'action'); + + return actionColumn ? [...filtered, actionColumn] : filtered; + }, [visibleColumns, columnOrder, allColumns]); + + // 列选择器变更处理 + const handleColumnVisibilityChange = (checkedValues: string[]) => { + const newVisibleColumns: Record = {}; + columnConfigs.forEach(config => { + newVisibleColumns[config.key] = checkedValues.includes(config.key); + }); + setVisibleColumns(newVisibleColumns); + }; + + // 移动列顺序 + const moveColumn = (index: number, direction: 'up' | 'down') => { + const visibleKeys = columnOrder.filter(key => visibleColumns[key]); + const currentKey = visibleKeys[index]; + const targetIndex = direction === 'up' ? index - 1 : index + 1; + + if (targetIndex >= 0 && targetIndex < visibleKeys.length) { + const targetKey = visibleKeys[targetIndex]; + + // 在原始顺序中交换位置 + const newOrder = [...columnOrder]; + const currentOriginalIndex = newOrder.indexOf(currentKey); + const targetOriginalIndex = newOrder.indexOf(targetKey); + + [newOrder[currentOriginalIndex], newOrder[targetOriginalIndex]] = + [newOrder[targetOriginalIndex], newOrder[currentOriginalIndex]]; + + setColumnOrder(newOrder); + } + }; + + // 列选择器菜单 + const columnSelectorMenu = { + items: [ + { + key: 'column-selector', + label: ( +
e.stopPropagation()}> + 自定义列显示 + + {/* 列可见性选择 */} +
+ 选择显示列: + visibleColumns[key])} + onChange={handleColumnVisibilityChange} + style={{ width: '100%' }} + > +
+ {columnOrder.map(key => { + const config = columnConfigs.find(c => c.key === key); + if (!config) return null; + return ( + + {config.title} + + ); + })} +
+
+
+ + {/* 列顺序调整 */} +
+ 调整列顺序: +
+ {columnOrder.filter(key => visibleColumns[key]).map((key, index, visibleKeys) => { + const config = columnConfigs.find(c => c.key === key); + if (!config) return null; + + return ( +
+ {config.title} + + +
+ ); + })} +
+
+
+ ), + }, + ], + }; + + // 加载数据 + const loadData = async () => { + setLoading(true); + try { + const response = await gunService.getGuns(searchParams); + const { records, total } = response; + setDataSource(records); + setPagination(prev => ({ + ...prev, + current: searchParams.page, + pageSize: searchParams.size, + total + })); + } catch (error: any) { + console.error('加载充电枪数据失败:', error); + const errorMessage = getErrorMessage(error); + showMessage.error(errorMessage); + } finally { + setLoading(false); + } + }; + + // 加载充电站选项 + const loadStationOptions = async () => { + try { + const response = await stationService.getStationOptions(); + setStationOptions(Array.isArray(response) ? response : []); + } catch (error: any) { + console.error('加载充电站选项失败:', error); + } + }; + + // 加载充电桩选项 + const loadPileOptions = async () => { + try { + const response = await pileService.getPileOptions(); + setPileOptions(response.data || []); + } catch (error: any) { + console.error('加载充电桩选项失败:', error); + } + }; + + // 监听搜索参数变化 + useEffect(() => { + loadData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchParams]); + + // 初始化加载充电站选项和充电桩选项 + useEffect(() => { + loadStationOptions(); + loadPileOptions(); + }, []); + + // 处理表格变化 + const handleTableChange: TableProps['onChange'] = (pag, filters, sorter) => { + let newParams = { + ...searchParams, + page: pag.current || 1, + size: pag.pageSize || 10 + }; + + // 处理排序 + if (sorter && !Array.isArray(sorter) && sorter.field) { + newParams.sortField = sorter.field as string; + newParams.sortOrder = sorter.order === 'ascend' ? 'asc' : 'desc'; + } else { + delete newParams.sortField; + delete newParams.sortOrder; + } + + setSearchParams(newParams); + }; + + // 搜索处理 + const handleSearch = (values: any) => { + const newParams = { + page: 1, + size: pagination.pageSize, + ...values + }; + setSearchParams(newParams); + }; + + // 重置搜索 + const handleReset = () => { + searchForm.resetFields(); + const newParams = { + page: 1, + size: pagination.pageSize + }; + setSearchParams(newParams); + }; + + // 显示新建模态框 + const showCreateModal = () => { + setIsEdit(false); + setCurrentRecord(null); + setModalVisible(true); + form.resetFields(); + }; + + // 处理编辑 + const handleEdit = (record: Gun) => { + setIsEdit(true); + setCurrentRecord(record); + setModalVisible(true); + form.setFieldsValue({ + ...record, + gunNo: record.gunNo.toString() + }); + }; + + // 处理查看 + const handleView = (record: Gun) => { + showMessage.info('查看功能待实现'); + }; + + // 生成充电枪编码 + const handleGenerateGunCode = () => { + const pileId = form.getFieldValue('pileId'); + const gunNo = form.getFieldValue('gunNo'); + + if (!pileId || !gunNo) { + showMessage.warning('请先选择充电桩和填写枪号'); + return; + } + + const selectedPile = pileOptions.find(p => p.id === pileId); + if (selectedPile) { + const code = generateGunCode(selectedPile.pileCode, gunNo); + form.setFieldValue('gunCode', code); + } + }; + + // 处理表单提交 + const handleSubmit = async () => { + try { + const values = await form.validateFields(); + setModalLoading(true); + + if (isEdit && currentRecord) { + // 编辑功能待实现 + showMessage.info('编辑功能待实现'); + } else { + // 新建充电枪 + const createData: GunCreateRequest = { + gunName: values.gunName, + gunNo: values.gunNo, + gunCode: values.gunCode, + stationId: values.stationId, + pileId: values.pileId + }; + await gunService.createGun(createData); + showMessage.success('充电枪创建成功'); + } + + setModalVisible(false); + // 清空选择状态并重新加载数据 + setSelectedRowKeys([]); + loadData(); + } catch (error: any) { + if (error.errorFields) { + // 表单验证错误 + return; + } + showMessage.error(getErrorMessage(error)); + } finally { + setModalLoading(false); + } + }; + + // 取消模态框 + const handleCancel = () => { + setModalVisible(false); + form.resetFields(); + }; + + // 处理删除 + const handleDelete = async (record: Gun) => { + try { + console.log('开始删除充电枪:', record.gunName, 'ID:', record.id); + await gunService.deleteGun(record.id); + console.log('删除充电枪成功:', record.gunName); + showMessage.success(`充电枪 "${record.gunName}" 删除成功`); + // 清空选择状态并重新加载数据 + setSelectedRowKeys([]); + loadData(); + } catch (error: any) { + console.error('删除充电枪失败:', error); + console.error('错误详情:', { + response: error?.response, + data: error?.response?.data, + status: error?.response?.status, + message: error?.message + }); + + const errorMessage = getErrorMessage(error); + console.log('处理后的错误消息:', errorMessage); + + showMessage.error(`删除充电枪 "${record.gunName}" 失败:${errorMessage}`); + } + }; + + // 批量删除 + const handleBatchDelete = async () => { + if (selectedRowKeys.length === 0) { + showMessage.warning('请先选择要删除的记录'); + return; + } + + confirm({ + title: '确认批量删除', + content: ( +
+

您确定要删除选中的 {selectedRowKeys.length} 条充电枪吗?

+

⚠️ 此操作不可撤销,请谨慎操作!

+
+ ), + okText: '确认删除', + okType: 'danger', + cancelText: '取消', + width: 420, + centered: true, + onOk: async () => { + setBatchDeleting(true); + let successCount = 0; + let failCount = 0; + const failedNames: string[] = []; + const failedReasons: string[] = []; + + try { + // 使用 for...of 循环按顺序删除 + for (const key of selectedRowKeys) { + try { + await gunService.deleteGun(key as string); + successCount++; + + // 每删除一个都更新进度提示 + if (selectedRowKeys.length > 3) { + showMessage.loading(`正在删除... (${successCount}/${selectedRowKeys.length})`); + } + } catch (error: any) { + failCount++; + const record = dataSource.find(item => item.id === key); + const gunName = record?.gunName || `ID: ${key}`; + failedNames.push(gunName); + + // 获取详细错误信息 + const errorMessage = getErrorMessage(error); + failedReasons.push(`${gunName}: ${errorMessage}`); + } + } + + // 显示删除结果 + if (failCount === 0) { + showMessage.success(`批量删除成功,共删除 ${successCount} 条充电枪`); + } else if (successCount === 0) { + // 全部失败 + showMessage.error( + `批量删除失败,所有 ${failCount} 条充电枪都删除失败。失败原因:${failedReasons.join('; ')}` + ); + } else { + // 部分成功 + showMessage.warning( + `删除完成:成功 ${successCount} 条,失败 ${failCount} 条。失败原因:${failedReasons.join('; ')}` + ); + } + + // 重新加载数据并清空选择 + setSelectedRowKeys([]); + loadData(); + } catch (error: any) { + // 处理整体操作异常 + const errorMessage = getErrorMessage(error); + showMessage.error(`批量删除操作失败:${errorMessage}`); + } finally { + setBatchDeleting(false); + } + } + }); + }; + + // 行选择配置 + const rowSelection = { + selectedRowKeys, + onChange: (newSelectedRowKeys: React.Key[]) => { + setSelectedRowKeys(newSelectedRowKeys); + }, + }; + + return ( +
+
+

+ 充电枪管理 +

+ + {selectedRowKeys.length > 0 && ( + + )} + + +
+ + {/* 搜索表单 */} + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + {/* 数据表格 */} + +