From a5d5c0006d3646914ba718b98cea01e8dd9c51e8 Mon Sep 17 00:00:00 2001 From: cuizhibin Date: Thu, 7 Aug 2025 15:02:35 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=9B=9E=E6=BB=9Asip=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sip/pom.xml | 52 ++ .../monitor/config/MediaFormatConfig.java | 30 + .../dite/znpt/monitor/constant/Constants.java | 37 + .../monitor/constant/IotCacheConstants.java | 37 + .../monitor/constant/IotDictConstants.java | 20 + .../znpt/monitor/constant/IotRespMessage.java | 113 +++ .../monitor/constant/dict/CameraType.java | 32 + .../monitor/constant/dict/DeviceStatus.java | 34 + .../constant/dict/SipTransferMode.java | 26 + .../constant/dict/StreamTransferMode.java | 27 + .../monitor/constant/dict/ValueAndLabel.java | 14 + .../znpt/monitor/constant/dict/YesOrNo.java | 32 + .../znpt/monitor/domain/CustomFunction.java | 14 + .../entity/DeviceVideoChannelEntity.java | 124 +++ .../domain/entity/DeviceVideoEntity.java | 96 ++ .../monitor/domain/entity/IpConfigEntity.java | 35 + .../domain/req/MonitorConfigAddReq.java | 14 + .../znpt/monitor/domain/req/VideoInfoReq.java | 23 + .../monitor/domain/resp/DeviceVideoResp.java | 19 + .../monitor/domain/resp/VideoInfoResp.java | 58 ++ .../vo/video/DeviceVideoChannelEditReq.java | 34 + .../vo/video/DeviceVideoChannelListResp.java | 55 ++ .../vo/video/DeviceVideoChannelResp.java | 43 + .../domain/vo/video/DeviceVideoEditReq.java | 29 + .../domain/vo/video/DeviceVideoListResp.java | 74 ++ .../domain/vo/video/DeviceVideoNumResp.java | 24 + .../domain/vo/video/StreamMediaFormat.java | 77 ++ .../domain/vo/video/StreamMediaFormatReq.java | 28 + .../vo/video/StreamMediaFormatResp.java | 31 + .../vo/video/StreamMediaServerConfigReq.java | 59 ++ .../vo/video/StreamMediaServerConfigResp.java | 60 ++ .../monitor/domain/vo/video/VideoPayResp.java | 24 + .../mapper/DeviceVideoChannelMapper.java | 55 ++ .../monitor/mapper/DeviceVideoMapper.java | 24 + .../znpt/monitor/mapper/IpConfigMapper.java | 13 + .../mapper/StreamMediaFormatMapper.java | 12 + .../dite/znpt/monitor/media/zlm/ZlmApi.java | 1 + .../dite/znpt/monitor/media/zlm/ZlmHook.java | 135 +++ .../znpt/monitor/media/zlm/ZlmService.java | 42 + .../media/zlm/cache/MediaServerCache.java | 38 + .../zlm/cache/MediaServerChannelCache.java | 54 ++ .../zlm/config/StreamMediaServerConfig.java | 46 + .../znpt/monitor/media/zlm/dto/MediaItem.java | 130 +++ .../monitor/media/zlm/dto/ServerConfig.java | 852 ++++++++++++++++++ .../monitor/media/zlm/dto/ServerInfo.java | 30 + .../monitor/media/zlm/dto/ServerItem.java | 108 +++ .../media/zlm/dto/event/BaseEventReq.java | 48 + .../media/zlm/dto/event/BaseEventResp.java | 42 + .../media/zlm/dto/event/FlowReportReq.java | 26 + .../media/zlm/dto/event/HttpAccessReq.java | 29 + .../media/zlm/dto/event/HttpAccessResp.java | 34 + .../monitor/media/zlm/dto/event/PlayReq.java | 14 + .../media/zlm/dto/event/PublishReq.java | 14 + .../media/zlm/dto/event/PublishResp.java | 81 ++ .../media/zlm/dto/event/RecordMp4Req.java | 48 + .../media/zlm/dto/event/RtspAuthReq.java | 30 + .../media/zlm/dto/event/RtspAuthResp.java | 30 + .../media/zlm/dto/event/RtspRealmReq.java | 14 + .../zlm/dto/event/ServerKeepaliveReq.java | 16 + .../media/zlm/dto/event/ShellLoginReq.java | 24 + .../media/zlm/dto/event/StreamChangedReq.java | 19 + .../zlm/dto/event/StreamNoneReaderReq.java | 14 + .../zlm/dto/event/StreamNotFoundReq.java | 14 + .../monitor/media/zlm/dto/req/BaseReq.java | 18 + .../media/zlm/dto/req/CloseStreamReq.java | 18 + .../media/zlm/dto/req/FFmpegSourceReq.java | 45 + .../media/zlm/dto/req/GetAllSessionReq.java | 25 + .../zlm/dto/req/GetMp4RecordFileReq.java | 24 + .../znpt/monitor/media/zlm/dto/req/IdReq.java | 20 + .../monitor/media/zlm/dto/req/KeyReq.java | 20 + .../monitor/media/zlm/dto/req/RecordReq.java | 29 + .../media/zlm/dto/req/RtpServerReq.java | 45 + .../monitor/media/zlm/dto/req/SendRtpReq.java | 53 ++ .../monitor/media/zlm/dto/req/SnapReq.java | 29 + .../media/zlm/dto/req/StreamIdReq.java | 22 + .../media/zlm/dto/req/StreamProxyReq.java | 89 ++ .../zlm/dto/req/StreamPusherProxyReq.java | 35 + .../monitor/media/zlm/dto/req/StreamReq.java | 32 + .../monitor/media/zlm/dto/resp/BaseResp.java | 64 ++ .../media/zlm/dto/resp/CloseStreamResp.java | 25 + .../monitor/media/zlm/dto/resp/MediaResp.java | 61 ++ .../media/zlm/dto/resp/Mp4RecordFileResp.java | 23 + .../media/zlm/dto/resp/OriginSock.java | 26 + .../media/zlm/dto/resp/RtpInfoResp.java | 39 + .../media/zlm/dto/resp/RtpServerResp.java | 22 + .../media/zlm/dto/resp/SessionResp.java | 41 + .../media/zlm/dto/resp/StatisticResp.java | 28 + .../media/zlm/dto/resp/ThreadsLoadResp.java | 22 + .../monitor/media/zlm/dto/resp/Track.java | 61 ++ .../media/zlm/enums/MediaFormatType.java | 43 + .../monitor/media/zlm/impl/ZlmApiImpl.java | 223 +++++ .../media/zlm/impl/ZlmHookService.java | 132 +++ .../media/zlm/impl/ZlmServiceImpl.java | 194 ++++ .../service/DeviceVideoChannelService.java | 120 +++ .../monitor/service/DeviceVideoService.java | 104 +++ .../znpt/monitor/service/IpConfigService.java | 27 + .../service/StreamMediaFormatService.java | 12 + .../impl/DeviceVideoChannelServiceImpl.java | 262 ++++++ .../service/impl/DeviceVideoServiceImpl.java | 225 +++++ .../service/impl/IpConfigServiceImpl.java | 80 ++ .../impl/StreamMediaFormatServiceImpl.java | 17 + .../com/dite/znpt/monitor/sip/SipLayer.java | 100 ++ .../znpt/monitor/sip/config/SipConfig.java | 38 + .../sip/session/StreamSessionManager.java | 26 + .../sip/transmit/SipProcessorFactoryI.java | 11 + .../sip/transmit/SipProcessorFactoryImpl.java | 99 ++ .../sip/transmit/cmd/ISipDeviceCommander.java | 50 + .../cmd/SipRequestHeaderProvider.java | 120 +++ .../cmd/impl/SipDeviceCommanderImpl.java | 226 +++++ .../request/ISipRequestProcessor.java | 12 + .../request/ISipRequestProcessorAbstract.java | 259 ++++++ .../request/impl/AckRequestProcessorImpl.java | 53 ++ .../request/impl/ByeRequestProcessorImpl.java | 63 ++ .../impl/MessageRequestProcessorImpl.java | 75 ++ .../impl/RegisterRequestProcessorImpl.java | 156 ++++ .../request/impl/message/IMessageHandler.java | 20 + .../message/query/CatalogHandlerImpl.java | 137 +++ .../message/query/DeviceInfoHandlerImpl.java | 68 ++ .../query/DeviceStatusHandlerImpl.java | 68 ++ .../message/query/KeepaliveHandlerImpl.java | 89 ++ .../response/ISipResponseProcessor.java | 13 + .../ISipResponseProcessorAbstract.java | 12 + .../impl/InviteResponseProcessorImpl.java | 1 + .../transmit/timeout/ITimeoutProcessor.java | 7 + .../timeout/impl/TimeoutProcessorImpl.java | 34 + .../DigestServerAuthenticationHelper.java | 210 +++++ .../dite/znpt/monitor/sip/utils/XmlUtil.java | 95 ++ .../znpt/monitor/sip/vo/DeviceAlarmVo.java | 49 + .../znpt/monitor/sip/vo/DeviceChannelVo.java | 129 +++ .../dite/znpt/monitor/sip/vo/DeviceVo.java | 56 ++ .../com/dite/znpt/monitor/sip/vo/HostVo.java | 12 + .../znpt/monitor/sip/vo/RecordItemVo.java | 31 + .../dite/znpt/monitor/sip/vo/RecordVo.java | 23 + .../dite/znpt/monitor/utils/DictUtils.java | 33 + .../mapper/iot/DeviceVideoChannelMapper.xml | 84 ++ .../mapper/iot/DeviceVideoMapper.xml | 45 + .../resources/mapper/iot/IpConfigMapper.xml | 17 + .../znpt/web/controller/VideoController.java | 188 ++++ 138 files changed, 8476 insertions(+) create mode 100644 sip/pom.xml create mode 100644 sip/src/main/java/com/dite/znpt/monitor/config/MediaFormatConfig.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/constant/Constants.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/constant/IotCacheConstants.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/constant/IotDictConstants.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/constant/IotRespMessage.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/constant/dict/CameraType.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/constant/dict/DeviceStatus.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/constant/dict/SipTransferMode.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/constant/dict/StreamTransferMode.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/constant/dict/ValueAndLabel.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/constant/dict/YesOrNo.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/CustomFunction.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoChannelEntity.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoEntity.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/entity/IpConfigEntity.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/req/MonitorConfigAddReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/req/VideoInfoReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/resp/DeviceVideoResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/resp/VideoInfoResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelEditReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelListResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoEditReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoListResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoNumResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormat.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/VideoPayResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoChannelMapper.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoMapper.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/mapper/IpConfigMapper.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/mapper/StreamMediaFormatMapper.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmApi.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmHook.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmService.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerCache.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerChannelCache.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/config/StreamMediaServerConfig.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/MediaItem.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerConfig.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerInfo.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerItem.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/FlowReportReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PlayReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RecordMp4Req.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspRealmReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ServerKeepaliveReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ShellLoginReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamChangedReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNoneReaderReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNotFoundReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/BaseReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/CloseStreamReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/FFmpegSourceReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetAllSessionReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetMp4RecordFileReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/IdReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/KeyReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RecordReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RtpServerReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SendRtpReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SnapReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamIdReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamProxyReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamPusherProxyReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamReq.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/BaseResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/CloseStreamResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/MediaResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/Mp4RecordFileResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/OriginSock.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/RtpInfoResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/RtpServerResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/SessionResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/StatisticResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/ThreadsLoadResp.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/Track.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/enums/MediaFormatType.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmApiImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmHookService.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmServiceImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/service/DeviceVideoChannelService.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/service/DeviceVideoService.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/service/IpConfigService.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/service/StreamMediaFormatService.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/service/impl/DeviceVideoChannelServiceImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/service/impl/DeviceVideoServiceImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/service/impl/IpConfigServiceImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/service/impl/StreamMediaFormatServiceImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/SipLayer.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/config/SipConfig.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/session/StreamSessionManager.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/SipProcessorFactoryI.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/SipProcessorFactoryImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/ISipDeviceCommander.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/SipRequestHeaderProvider.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/impl/SipDeviceCommanderImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/ISipRequestProcessor.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/ISipRequestProcessorAbstract.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/AckRequestProcessorImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/ByeRequestProcessorImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/MessageRequestProcessorImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/RegisterRequestProcessorImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/IMessageHandler.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/CatalogHandlerImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/DeviceInfoHandlerImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/DeviceStatusHandlerImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/KeepaliveHandlerImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/ISipResponseProcessor.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/ISipResponseProcessorAbstract.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/impl/InviteResponseProcessorImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/timeout/ITimeoutProcessor.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/transmit/timeout/impl/TimeoutProcessorImpl.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/utils/DigestServerAuthenticationHelper.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/utils/XmlUtil.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceAlarmVo.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceChannelVo.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceVo.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/vo/HostVo.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/vo/RecordItemVo.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/sip/vo/RecordVo.java create mode 100644 sip/src/main/java/com/dite/znpt/monitor/utils/DictUtils.java create mode 100644 sip/src/main/resources/mapper/iot/DeviceVideoChannelMapper.xml create mode 100644 sip/src/main/resources/mapper/iot/DeviceVideoMapper.xml create mode 100644 sip/src/main/resources/mapper/iot/IpConfigMapper.xml create mode 100644 web/src/main/java/com/dite/znpt/web/controller/VideoController.java diff --git a/sip/pom.xml b/sip/pom.xml new file mode 100644 index 0000000..7e62790 --- /dev/null +++ b/sip/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + com.dite.znpt + parent + 1.0.0-SNAPSHOT + + sip + 1.0.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + com.dite.znpt + core + 1.0.0-SNAPSHOT + + + + org.projectlombok + lombok + + + + + javax.sip + jain-sip-ri + 1.3.0-91 + + + + + org.dom4j + dom4j + 2.1.3 + + + org.slf4j + log4j-over-slf4j + 1.7.36 + + + + \ No newline at end of file diff --git a/sip/src/main/java/com/dite/znpt/monitor/config/MediaFormatConfig.java b/sip/src/main/java/com/dite/znpt/monitor/config/MediaFormatConfig.java new file mode 100644 index 0000000..2c0dd0c --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/config/MediaFormatConfig.java @@ -0,0 +1,30 @@ +package com.dite.znpt.monitor.config; + +import cn.hutool.core.collection.CollUtil; +import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * 媒体格式配置 + * + * @author huise23 + * @since 2023-07-31 09:08:05 + */ +@Configuration +public class MediaFormatConfig { + + @Bean + public static List streamMediaFormatList() { + List formatList = CollUtil.newArrayList(); + formatList.add(new StreamMediaFormat("flv",null,"1")); + formatList.add(new StreamMediaFormat("mp4",null,"0")); + formatList.add(new StreamMediaFormat("hls",null,"0")); + formatList.add(new StreamMediaFormat("webrtc",null,"1")); + return formatList; + } + + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/Constants.java b/sip/src/main/java/com/dite/znpt/monitor/constant/Constants.java new file mode 100644 index 0000000..d1ee938 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/constant/Constants.java @@ -0,0 +1,37 @@ +package com.dite.znpt.monitor.constant; + +/** + * @author yunp + * @since 2022/7/14 + */ +public class Constants { + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * 路径拼接符 / + */ + public static final String File_SEPARATOR = "/"; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 默认字符串分隔符 + */ + public static final String DEFAULT_DELIMITER = ","; +} \ No newline at end of file diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/IotCacheConstants.java b/sip/src/main/java/com/dite/znpt/monitor/constant/IotCacheConstants.java new file mode 100644 index 0000000..743494a --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/constant/IotCacheConstants.java @@ -0,0 +1,37 @@ +package com.dite.znpt.monitor.constant; + +/** + * @author yunp + * @since 2022/8/4 + * @description 缓存key定义:key全部以iot开头 + */ +public class IotCacheConstants { + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict:"; + + /** + * 图标库 cache key + */ + public static final String SYS_ICON_KEY = "sys_icon"; + + private static final String IOT_DEVICE_VIDEO_PREFIX = "vs_device_video:"; + + public static String getIotDeviceVideoKey(String deviceCode){ + return IOT_DEVICE_VIDEO_PREFIX + deviceCode; + } + + private final static String CLIENT_TRANSACTION_CACHE_PREFIX = "IOT_CLIENT_TRANSACTION_CACHE:"; + + public static String getClientTransactionCacheKey(String ssrc){ + return CLIENT_TRANSACTION_CACHE_PREFIX + ssrc; + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/IotDictConstants.java b/sip/src/main/java/com/dite/znpt/monitor/constant/IotDictConstants.java new file mode 100644 index 0000000..4e3d10d --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/constant/IotDictConstants.java @@ -0,0 +1,20 @@ +package com.dite.znpt.monitor.constant; + +/** + * @author yunp + * @since 2022/8/4 + * @description 字典类型定义 + */ +public class IotDictConstants { + + /** + * 设备状态-在线 + */ + public static final String IOT_DEVICE_STATUS_ONLINE = "2"; + + /** + * 设备状态-离线 + */ + public static final String IOT_DEVICE_STATUS_OFFLINE = "3"; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/IotRespMessage.java b/sip/src/main/java/com/dite/znpt/monitor/constant/IotRespMessage.java new file mode 100644 index 0000000..7cdf9eb --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/constant/IotRespMessage.java @@ -0,0 +1,113 @@ +package com.dite.znpt.monitor.constant; + +/** + * @author yunp + * @since 2022/8/4 + * @description 响应文案定义 + */ +public class IotRespMessage { + + public static final String UNKNOWN_FAIL = "未知错误"; + + public static final String ID_NOT_FOUND = "数据不存在"; + + public static final String PARAMETER_ERROR = "参数有误"; + + public static final String NO_PERMISSION = "没有访问权限"; + + public static final String PARENT_AREA_NOT_FOUND = "上级分区不存在"; + + public static final String CAR_TYPE_HAS_CARS = "该车型下仍有车辆,无法删除"; + + public static final String CAR_BRAND_HAS_CARS = "该品牌下仍有车辆,无法删除"; + + public static final String CAR_PLATE_NUMBER_EXIST = "该车牌号车辆已存在"; + + public static final String CAR_PLATE_NUMBER_NOT_EXIST = "该车牌号车辆不存在"; + + public static final String CAR_HAS_BIND_TAG_OR_SPEED_MONITOR = "车辆已绑定车载标签或速度检测仪,禁止删除"; + + public static final String CAR_FREIGHT_NOT_EXIST = "货物不存在"; + + public static final String CAR_HAS_BIND_ALARM_TEMPLATE = "该车辆已绑定其他告警模板"; + + public static final String USER_HAS_BIND_ALARM_TEMPLATE = "该人员已绑定其他告警模板"; + + public static final String REPETITION_ALARM_FOR_AREA = "该厂区分区下已有同类型告警,无法重复添加"; + + public static final String REPETITION_ALARM_NOTIFY_CONFIG = "已存在相同部门层级的告警消息推送配置"; + + public static final String DEVICE_TERMINAL_HAS_BEEN_BIND = "该标签卡已被其他人绑定"; + + public static final String DEVICE_TERMINAL_TYPE_ERROR = "标签卡类型有误"; + + public static final String DEVICE_TERMINAL_NOT_FOUND_OR_STATUS_ERROR = "标签卡未找到或状态异常"; + + public static final String DEVICE_CANNOT_EDIT = "设备未启用或已停用才可编辑"; + + public static final String VIDEO_DEVICE_CANNOT_DELETE = "视频设备禁止删除"; + + public static final String DEVICE_CANNOT_DELETE = "未启用的设备才可删除"; + + public static final String DEVICE_HAS_BIND_TO_GROUP = "设备已经绑定至该分组"; + + public static final String DEVICE_VIDEO_CANNOT_DELETE = "禁止删除在线视频设备"; + + public static final String DEVICE_VIDEO_CANNOT_SYNC = "禁止更新离线视频设备"; + + public static final String DEVICE_INACTIVE = "设备未启用"; + + public static final String CODE_HAS_BEEN_USED = "编号已被占用"; + + public static final String NAME_HAS_BEEN_USED = "名称已被占用"; + + public static final String FLAG_HAS_BEEN_USED = "标识已被占用"; + + public static final String GROUP_HAS_CHILD_CANNOT_REMOVE = "分组有下级分组,无法删除"; + + public static final String GROUP_HAS_DEVICE_CANNOT_REMOVE = "分组下有绑定设备,无法删除"; + + public static final String PRODUCT_PUBLISH_CANNOT_DELETE = "该产品已发布,不可删除"; + + public static final String PRODUCT_HAS_DEVICE_CANNOT_REMOVE = "产品下存在设备关联,需删除设备后进行操作"; + + public static final String MSG_PROTOCOL_PUBLISH_CANNOT_DELETE = "消息协议未发布,发布协议后操作"; + + public static final String CATEGORY_CANNOT_DELETE = "该产品分类信息已被产品关联,不可删除"; + + public static final String SCENE_CANNOT_DELETE = "该场景信息已被产品关联,不可删除"; + + public static final String SWITCH_PARAM_HAS_BEEN_USED = "该参数在设备服务中只能定义一个"; + + public static final String CONFIG_CANNOT_DELETE = "该配置已被通知模板关联,不可删除, 请取消关联后重试。"; + + public static final String TEMPLATE_CANNOT_DELETE = "该通知模板已被场景联动关联,不可删除, 请取消关联后操作。"; + + public static final String PROTOCOL_CANNOT_DELETE_BY_PUBLISHED = "当前协议状态为已发布,不可删除."; + + public static final String PROTOCOL_CANNOT_DELETE_WITH_PRODUCT = "该协议已被产品关联,不可删除,请删除关联后操作!"; + + public static final String PROTOCOL_CANNOT_UN_PUBLISH = "协议已被产品发布,不可取消!"; + + public static final String PROTOCOL_TYPE_CANNOT_NULL = "协议类型不能为空"; + + public static final String PROTOCOL_CLASS_NAME_CANNOT_EMPTY = "协议类型为Jar或者Local类型时,必须指定协议类名."; + + public static final String PROTOCOL_FILE_PATH_CANNOT_EMPTY = "协议类型为Jar或者Local类型时,必须上传或者指定协议文件路径."; + + public static final String DATA_ILLEGAL = "数据非法"; + + public static final String IMPORT_ERROR = "导入出错"; + + public static final String COMMON_DEVICE_ATTR_DUPLICATE_ERROR = "订阅的通用设备点位全局不唯一"; + + public static final String PROTOCOL_NOT_EXISTS = "协议不存在"; + + public static final String RULE_NOT_EXISTS = "上报规则不存在"; + + public static final String DEVICE_NOT_EXISTS = "设备不存在"; + + public static final String DATA_DATA_TREND_TIME_TYPE_NOT_EXISTS = "设备数据趋势时间类型不存在"; + public static final String DATA_DATA_TREND_DATA_TYPE_NOT_EXISTS = "设备数据趋势数据类型不存在"; + public static final String CRON_ERROR = "cron表达式输入错误:"; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/CameraType.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/CameraType.java new file mode 100644 index 0000000..a05e191 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/CameraType.java @@ -0,0 +1,32 @@ +package com.dite.znpt.monitor.constant.dict; + +/** + * 摄像头类型 + * + * @author huise23 + * @since 2023-07-28 15:30:10 + */ +public enum CameraType implements ValueAndLabel { + + UNKNOWN("0","未知"), + BALLHEAD("1","球机"); + + private final String value; + private final String label; + + CameraType(String value, String label) { + this.value = value; + this.label = label; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getLabel() { + return label; + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/DeviceStatus.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/DeviceStatus.java new file mode 100644 index 0000000..2101f2b --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/DeviceStatus.java @@ -0,0 +1,34 @@ +package com.dite.znpt.monitor.constant.dict; + +/** + * 设备状态 + * + * @author huise23 + * @since 2023-07-28 15:30:10 + */ +public enum DeviceStatus implements ValueAndLabel { + + INACTIV("1", "未启用"), + ONLINE("2", "在线"), + OFFLINE("3", "离线"), + STOP("4", "停用"); + + private final String value; + private final String label; + + DeviceStatus(String value, String label) { + this.value = value; + this.label = label; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getLabel() { + return label; + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/SipTransferMode.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/SipTransferMode.java new file mode 100644 index 0000000..e621c06 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/SipTransferMode.java @@ -0,0 +1,26 @@ +package com.dite.znpt.monitor.constant.dict; + +public enum SipTransferMode implements ValueAndLabel { + + UDP("UDP","UDP"), + TCP("TCP","TCP"); + + private final String value; + private final String label; + + SipTransferMode(String value, String label) { + this.value = value; + this.label = label; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getLabel() { + return label; + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/StreamTransferMode.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/StreamTransferMode.java new file mode 100644 index 0000000..fea526a --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/StreamTransferMode.java @@ -0,0 +1,27 @@ +package com.dite.znpt.monitor.constant.dict; + +public enum StreamTransferMode implements ValueAndLabel { + + UDP("UDP","UDP"), + TCP_ACTIVE("TCP-ACTIVE","TCP主动"), + TCP_PASSIVE("TCP-PASSIVE", "TCP被动"); + + private final String value; + private final String label; + + StreamTransferMode(String value, String label) { + this.value = value; + this.label = label; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getLabel() { + return label; + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/ValueAndLabel.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/ValueAndLabel.java new file mode 100644 index 0000000..88b5b1b --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/ValueAndLabel.java @@ -0,0 +1,14 @@ +package com.dite.znpt.monitor.constant.dict; + + +/** + * 字典常量接口 + * + * @author huise23 + * @since 2023-07-28 15:28:55 + */ +public interface ValueAndLabel { + String getValue(); + String getLabel(); + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/constant/dict/YesOrNo.java b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/YesOrNo.java new file mode 100644 index 0000000..62889fd --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/constant/dict/YesOrNo.java @@ -0,0 +1,32 @@ +package com.dite.znpt.monitor.constant.dict; + +/** + * YesOrNo + * + * @author huise23 + * @since 2023-07-28 15:30:10 + */ +public enum YesOrNo implements ValueAndLabel { + + YES("Y","是"), + NO("N", "否"); + + private final String value; + private final String label; + + YesOrNo(String value, String label) { + this.value = value; + this.label = label; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getLabel() { + return label; + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/CustomFunction.java b/sip/src/main/java/com/dite/znpt/monitor/domain/CustomFunction.java new file mode 100644 index 0000000..bf145cc --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/CustomFunction.java @@ -0,0 +1,14 @@ +package com.dite.znpt.monitor.domain; + +/** + * @Author: cuizhibin + * @Date: 2023/1/16 14:36:36 + * @Description: + */ +public interface CustomFunction { + /** + * 执行的方法 + * @return + */ + T get(); +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoChannelEntity.java b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoChannelEntity.java new file mode 100644 index 0000000..4b263aa --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoChannelEntity.java @@ -0,0 +1,124 @@ +package com.dite.znpt.monitor.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @Author: huise23 + * @Date: 2022/8/11 17:29 + * @Description: + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("vs_device_video_channel") +@ApiModel(value="DeviceVideoChannelEntity", description="视频通道表") +public class DeviceVideoChannelEntity implements Serializable { + + private static final long serialVersionUID = -4175177624487756818L; + + @ApiModelProperty(value = "主键id") + @TableId(value = "channel_id", type = IdType.AUTO) + private Long channelId; + + @ApiModelProperty(value = "视频设备id") + private Long videoId; + + @ApiModelProperty(value = "通道国标编号") + private String channelCode; + + @ApiModelProperty(value = "通道名") + private String channelName; + + @ApiModelProperty(value = "生产厂商") + private String manufacture; + + @ApiModelProperty(value = "型号") + private String model; + + @ApiModelProperty(value = "设备归属") + private String owner; + + @ApiModelProperty(value = "行政区域") + private String civilCode; + + @ApiModelProperty(value = "警区") + private String block; + + @ApiModelProperty(value = "安装位置") + private String address; + + @ApiModelProperty(value = "是否有子设备 1有, 0没有") + private int parental; + + @ApiModelProperty(value = "父级id") + private String parentId; + + @ApiModelProperty(value = "信令安全模式 缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式") + private int safetyWay; + + @ApiModelProperty(value = "注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式") + private int registerWay; + + @ApiModelProperty("证书序列号") + private String certNum; + + @ApiModelProperty("证书有效标识 缺省为0;证书有效标识:0:无效1: 有效") + private int certifiable; + + @ApiModelProperty("证书无效原因码") + private int errCode; + + @ApiModelProperty( "证书终止有效期") + private String endTime; + + @ApiModelProperty("保密属性 缺省为0; 0:不涉密, 1:涉密") + private String secrecy; + + @ApiModelProperty("IP地址") + private String ipAddress; + + @ApiModelProperty("端口号") + private int port; + + @ApiModelProperty("密码") + private String password; + + @ApiModelProperty("摄像头类型") + private String cameraType; + + @ApiModelProperty("云台控制") + private String ptzControl; + + @ApiModelProperty(value = "状态") + private String status; + + @ApiModelProperty("经度") + private double longitude; + + @ApiModelProperty("纬度") + private double latitude; + + @ApiModelProperty(value = "备注") + private String remark; + + @ApiModelProperty("创建时间") + private LocalDateTime createTime; + + @ApiModelProperty("创建人id") + @TableField(fill = FieldFill.INSERT) + private Long createBy; + + @ApiModelProperty("更新时间") + private LocalDateTime updateTime; + + @ApiModelProperty("更新人id") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateBy; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoEntity.java b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoEntity.java new file mode 100644 index 0000000..9b35a56 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/DeviceVideoEntity.java @@ -0,0 +1,96 @@ +package com.dite.znpt.monitor.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @Author: huise23 + * @Date: 2022/8/11 10:24 + * @Description: + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("vs_device_video") +@ApiModel(value="DeviceVideoEntity", description="视频设备表") +public class DeviceVideoEntity implements Serializable { + + private static final long serialVersionUID = -182441901641147882L; + + @ApiModelProperty(value = "主键id") + @TableId(value = "video_id", type = IdType.AUTO) + private Long videoId; + + @ApiModelProperty(value = "视频设备国标编码") + private String videoCode; + + @ApiModelProperty(value = "视频设备名称") + private String videoName; + + @ApiModelProperty(value = "生产厂商") + private String manufacturer; + + @ApiModelProperty(value = "型号") + private String model; + + @ApiModelProperty(value = "固件版本") + private String firmware; + + @ApiModelProperty(value = "传输协议(UDP/TCP),默认UDP") + private String transport; + + @ApiModelProperty(value = "数据流传输模式(默认UDP)") + private String streamMode; + + @ApiModelProperty(value = "设备状态") + private String status; + + @ApiModelProperty(value = "注册时间") + private LocalDateTime registerTime; + + @ApiModelProperty(value = "心跳时间") + private LocalDateTime KeepaliveTime; + + @ApiModelProperty("通道个数") + private int channelCount; + + @ApiModelProperty(value = "ip") + private String ip; + + @ApiModelProperty(value = "端口") + private Integer port; + + @ApiModelProperty(value = "地址") + private String hostAddress; + + @ApiModelProperty(value = "注册有效期") + private Integer expires; + + @ApiModelProperty(value = "符集, 支持 UTF-8 与 GB2312") + private String charset; + + @ApiModelProperty(value = "产品id") + private Long productId; + + @ApiModelProperty(value = "备注") + private String remark; + + @ApiModelProperty("创建时间") + private LocalDateTime createTime; + + @ApiModelProperty("创建人id") + @TableField(fill = FieldFill.INSERT) + private Long createBy; + + @ApiModelProperty("更新时间") + private LocalDateTime updateTime; + + @ApiModelProperty("更新人id") + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateBy; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/entity/IpConfigEntity.java b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/IpConfigEntity.java new file mode 100644 index 0000000..04c67b4 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/entity/IpConfigEntity.java @@ -0,0 +1,35 @@ +package com.dite.znpt.monitor.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + * @Date: 2023/09/05 16:39 + * @Description: 监控设备IP配置表实体类 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("vs_ip_config") +@ApiModel(value="VsIpConfigEntity对象", description="监控设备IP配置表") +public class IpConfigEntity implements Serializable { + + @ApiModelProperty("${column.comment}") + @TableId(type = IdType.AUTO) + private Long configId; + + @ApiModelProperty("ip地址") + private String ip; + + @ApiModelProperty("ip地址前三位") + private String ipTopThree; + + +} + diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/req/MonitorConfigAddReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/req/MonitorConfigAddReq.java new file mode 100644 index 0000000..be2a92e --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/req/MonitorConfigAddReq.java @@ -0,0 +1,14 @@ +package com.dite.znpt.monitor.domain.req; + +import lombok.Data; + +import java.util.List; + +/** + * @Date:2023/9/5 16:19 + * @Description: 视频服务配置新增对象 + */ +@Data +public class MonitorConfigAddReq { + private List ipAddresses; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/req/VideoInfoReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/req/VideoInfoReq.java new file mode 100644 index 0000000..0dc809c --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/req/VideoInfoReq.java @@ -0,0 +1,23 @@ +package com.dite.znpt.monitor.domain.req; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 查询视频信息参数 + * + * @author huise23 + * @since 2024-12-03 14:03:29 + */ +@Data +public class VideoInfoReq implements Serializable { + + @ApiModelProperty(value = "视频对接方式 1.摄像头直连 2.级联") + private Integer videoConnection; + + @ApiModelProperty(value = "级联分隔符(默认/)") + private String cascadeSeparator = "/"; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/resp/DeviceVideoResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/resp/DeviceVideoResp.java new file mode 100644 index 0000000..f8a76ae --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/resp/DeviceVideoResp.java @@ -0,0 +1,19 @@ +package com.dite.znpt.monitor.domain.resp; + +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @Date:2023/9/7 10:26 + * @Description: + */ +@Data +public class DeviceVideoResp extends DeviceVideoEntity { + @ApiModelProperty(value = "设备状态label") + private String statusLabel; + @ApiModelProperty(value = "流传输模式label") + private String streamModeLabel; + @ApiModelProperty(value = "传输模式label") + private String transportLabel; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/resp/VideoInfoResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/resp/VideoInfoResp.java new file mode 100644 index 0000000..7ba594c --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/resp/VideoInfoResp.java @@ -0,0 +1,58 @@ +package com.dite.znpt.monitor.domain.resp; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 查询视频信息返回信息 + * + * @author huise23 + * @since 2024-12-03 14:03:29 + */ +@Data +public class VideoInfoResp implements Serializable { + + @ApiModelProperty(value = "视频设备id") + private Long videoId; + + @ApiModelProperty(value = "视频设备名称") + private String videoName; + + @ApiModelProperty(value = "通道id") + private Long channelId; + + @ApiModelProperty(value = "国标编码") + private String channelCode; + + @ApiModelProperty(value = "通道名称") + private String channelName; + + @ApiModelProperty(value = "生产厂商") + private String manufacture; + + @ApiModelProperty(value = "型号") + private String model; + + @ApiModelProperty(value = "安装位置") + private String address; + + @ApiModelProperty("IP地址") + private String ipAddress; + + @ApiModelProperty("端口号") + private int port; + + @ApiModelProperty(value = "云台控制label") + private String ptzControl; + + @ApiModelProperty("经度") + private double longitude; + + @ApiModelProperty("纬度") + private double latitude; + + @ApiModelProperty(value = "状态 DeviceStatus") + private String status; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelEditReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelEditReq.java new file mode 100644 index 0000000..1ec735a --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelEditReq.java @@ -0,0 +1,34 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:12 + * @Description: + */ +@Data +@ApiModel("视频通道编辑请求") +public class DeviceVideoChannelEditReq implements Serializable { + + private static final long serialVersionUID = 719557164910393807L; + + @ApiModelProperty(value = "通道名称") + private String channelName; + + @ApiModelProperty(value = "安装位置") + private String address; + + @ApiModelProperty(value = "摄像头类型") + private String cameraType; + + @ApiModelProperty(value = "云台控制,Y表示是,N表示否") + private String ptzControl; + + @ApiModelProperty(value = "描述") + private String remark; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelListResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelListResp.java new file mode 100644 index 0000000..31234fb --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelListResp.java @@ -0,0 +1,55 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:12 + * @Description: + */ +@Data +@ApiModel("视频通道列表响应") +public class DeviceVideoChannelListResp implements Serializable { + + private static final long serialVersionUID = -8053965410352257803L; + + @ApiModelProperty(value = "主键id") + private Long channelId; + + @ApiModelProperty(value = "所属产品id") + private Long productId; + + @ApiModelProperty(value = "通道国标编码") + private String channelCode; + + @ApiModelProperty(value = "通道名称") + private String channelName; + + @ApiModelProperty(value = "安装位置") + private String address; + + @ApiModelProperty(value = "摄像头类型label") + private String cameraType; + + @ApiModelProperty(value = "摄像头类型") + private String cameraTypeLabel; + + @ApiModelProperty(value = "云台控制label") + private String ptzControl; + + @ApiModelProperty(value = "云台控制,Y表示是,N表示否") + private String ptzControlLabel; + + @ApiModelProperty(value = "状态") + private String status; + + @ApiModelProperty(value = "状态label") + private String statusLabel; + + @ApiModelProperty(value = "描述") + private String remark; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelResp.java new file mode 100644 index 0000000..1f22085 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoChannelResp.java @@ -0,0 +1,43 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:12 + * @Description: + */ +@Data +@ApiModel("视频通道响应") +public class DeviceVideoChannelResp implements Serializable { + + private static final long serialVersionUID = 1140851083577845760L; + + @ApiModelProperty(value = "主键id") + private Long channelId; + + @ApiModelProperty(value = "通道国标编码") + private String channelCode; + + @ApiModelProperty(value = "通道名称") + private String channelName; + + @ApiModelProperty(value = "安装位置") + private String address; + + @ApiModelProperty(value = "摄像头类型") + private String cameraType; + + @ApiModelProperty(value = "云台控制,Y表示是,N表示否") + private String ptzControl; + + @ApiModelProperty(value = "状态") + private String status; + + @ApiModelProperty(value = "描述") + private String remark; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoEditReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoEditReq.java new file mode 100644 index 0000000..a1f2b8e --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoEditReq.java @@ -0,0 +1,29 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:13 + * @Description: + */ +@Data +@ApiModel("视频设备编辑请求参数") +public class DeviceVideoEditReq implements Serializable { + + private static final long serialVersionUID = -3387666090991548317L; + + @ApiModelProperty(value = "设备名称") + private String videoName; + + @ApiModelProperty(value = "所属产品") + private Long productId; + + @ApiModelProperty(value = "说明") + private String remark; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoListResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoListResp.java new file mode 100644 index 0000000..026f873 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoListResp.java @@ -0,0 +1,74 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:13 + * @Description: + */ +@Data +@ApiModel("视频设备列表响应") +public class DeviceVideoListResp implements Serializable { + + private static final long serialVersionUID = -5568664011265192343L; + + @ApiModelProperty(value = "主键id") + private Long videoId; + + @ApiModelProperty(value = "视频设备国标编码") + private String videoCode; + + @ApiModelProperty(value = "视频设备名称") + private String videoName; + + @ApiModelProperty(value = "传输模式") + private String transport; + + @ApiModelProperty(value = "传输模式label") + private String transportLabel; + + @ApiModelProperty(value = "流传输模式") + private String streamMode; + + @ApiModelProperty(value = "流传输模式label") + private String streamModeLabel; + + @ApiModelProperty(value = "通道数量") + private Integer channelCount; + + @ApiModelProperty(value = "设备状态") + private String status; + + @ApiModelProperty(value = "设备状态label") + private String statusLabel; + + @ApiModelProperty(value = "设备ip") + private String ip; + + @ApiModelProperty(value = "设备端口") + private String port; + + @ApiModelProperty(value = "设备地址(ip+端口)") + private String hostAddress; + + @ApiModelProperty(value = "生产厂商") + private String manufacturer; + + @ApiModelProperty(value = "备注") + private String remark; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "创建时间") + private LocalDateTime createTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "心跳时间") + private LocalDateTime keepAliveTime; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoNumResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoNumResp.java new file mode 100644 index 0000000..f56b8c1 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/DeviceVideoNumResp.java @@ -0,0 +1,24 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @Author: huise23 + * @Date: 2022/8/16 9:34 + * @Description: + */ +@Data +@ApiModel("视频设备数量响应") +public class DeviceVideoNumResp { + + @ApiModelProperty(value = "设备总数量") + private Long allDevice; + + @ApiModelProperty(value = "设备在线数量") + private Long onlineDevice; + + @ApiModelProperty(value = "设备离线数量") + private Long offlineDevice; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormat.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormat.java new file mode 100644 index 0000000..5c5f457 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormat.java @@ -0,0 +1,77 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.TableField; +import com.dite.znpt.monitor.media.zlm.dto.ServerConfig; +import com.dite.znpt.monitor.media.zlm.enums.MediaFormatType; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/11 10:25 + * @Description: + */ +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "StreamMediaFormat对象", description = "流媒体格式") +public class StreamMediaFormat implements Serializable { + + private static final long serialVersionUID = -4177962876536716643L; + + @ApiModelProperty(value = "流媒体格式") + private String mediaFormat; + + @ApiModelProperty(value = "端口") + private Integer port; + + @ApiModelProperty(value = "是否开启tls,1表示ture,0表示false") + private String openTls; + + @TableField(exist = false) + @ApiModelProperty(value = "WebSocket播放地址") + private String wsUrl; + + @TableField(exist = false) + @ApiModelProperty(value = "Http播放地址") + private String httpUrl; + + @TableField(exist = false) + @ApiModelProperty(value = "WebSocket播放地址") + private String wssUrl; + + @TableField(exist = false) + @ApiModelProperty(value = "Http播放地址") + private String httpsUrl; + + @TableField(exist = false) + @ApiModelProperty(value = "相对播放地址") + private String relativePath; + + public StreamMediaFormat(String mediaFormat,Integer port,String openTls){ + this.mediaFormat = mediaFormat; + this.port = port; + this.openTls = openTls; + } + + public void generateUrl(String host, String streamId, ServerConfig config, String mediaRouter) { + if("webrtc".equals(this.mediaFormat)){ + this.httpUrl = StrUtil.format("http://{}:{}/index/api/webrtc?app=rtp&stream={}&type=play", host, config.getHttpPort(), streamId); + this.httpsUrl = StrUtil.format("https://{}:{}/index/api/webrtc?app=rtp&stream={}&type=play", host, config.getHttpSslPort(), streamId); + this.relativePath = StrUtil.format("{}/index/api/webrtc?app=rtp&stream={}&type=play", mediaRouter, streamId); + return; + } + String suffix = MediaFormatType.getSuffix(this.mediaFormat); + if (config.getHttpSslPort() != null && config.getHttpSslPort() > 0) { + this.wssUrl = StrUtil.format("wss://{}:{}/rtp/{}{}", host, config.getHttpSslPort(), streamId, suffix); + this.httpsUrl = StrUtil.format("https://{}:{}/rtp/{}{}", host, config.getHttpSslPort(), streamId, suffix); + } + this.wsUrl = StrUtil.format("ws://{}:{}/rtp/{}{}", host, config.getHttpPort(), streamId, suffix); + this.httpUrl = StrUtil.format("http://{}:{}/rtp/{}{}", host, config.getHttpPort(), streamId, suffix); + this.relativePath = StrUtil.format("{}/rtp/{}{}", mediaRouter, streamId, suffix); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatReq.java new file mode 100644 index 0000000..d3ce278 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatReq.java @@ -0,0 +1,28 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/11 14:40 + * @Description: + */ +@Data +@ApiModel("流媒体格式请求") +public class StreamMediaFormatReq implements Serializable { + + private static final long serialVersionUID = 6627383994019834279L; + + @ApiModelProperty(value = "流媒体格式") + private String mediaFormat; + + @ApiModelProperty(value = "端口") + private Integer port; + + @ApiModelProperty(value = "是否开启TLS,1表示true,0表示false") + private String openTls; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatResp.java new file mode 100644 index 0000000..ad9304b --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaFormatResp.java @@ -0,0 +1,31 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/11 14:40 + * @Description: + */ +@Data +@ApiModel("流媒体格式响应") +public class StreamMediaFormatResp implements Serializable { + + private static final long serialVersionUID = -5714327034173930078L; + + @ApiModelProperty(value = "流媒体格式主键") + private Long formatId; + + @ApiModelProperty(value = "流媒体格式") + private String mediaFormat; + + @ApiModelProperty(value = "端口") + private Integer port; + + @ApiModelProperty(value = "是否开启TLS,1表示true,0表示false") + private String openTls; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigReq.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigReq.java new file mode 100644 index 0000000..36c15fd --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigReq.java @@ -0,0 +1,59 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/11 14:16 + * @Description: + */ +@Data +@ApiModel("流媒体服务配置请求") +public class StreamMediaServerConfigReq implements Serializable { + + private static final long serialVersionUID = -1228005085084886474L; + + @NotNull(message = "流媒体名称不能为空") + @ApiModelProperty(value = "流媒体名称") + private String mediaName; + + @ApiModelProperty(value = "流媒体服务") + private String mediaService; + + @ApiModelProperty(value = "公网 HOST") + private String publicHost; + + @ApiModelProperty(value = "API HOST") + private String apiHost; + + @ApiModelProperty(value = "API 端口") + private Integer apiPort; + + @ApiModelProperty(value = "密钥") + private Integer secretKey; + + @ApiModelProperty(value = "流ID前缀") + private String streamPrefix; + + @ApiModelProperty(value = "RTP IP") + private String rtpHost; + + @ApiModelProperty(value = "RTP 端口") + private Integer rtpPort; + + @ApiModelProperty(value = "动态端口起始值") + private Integer dynamicPortStart; + + @ApiModelProperty(value = "动态端口结束值") + private Integer dynamicPortEnd; + + @ApiModelProperty(value = "流媒体格式") + private List streamMediaFormatReqList; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigResp.java new file mode 100644 index 0000000..6e3b185 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/StreamMediaServerConfigResp.java @@ -0,0 +1,60 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/11 14:16 + * @Description: + */ +@Data +@ApiModel("流媒体服务配置响应") +public class StreamMediaServerConfigResp implements Serializable { + + private static final long serialVersionUID = 3464085768355214710L; + + @ApiModelProperty(value = "'流媒体配置主键'") + private Long configId; + + @ApiModelProperty(value = "'流媒体名称'") + private String mediaName; + + @ApiModelProperty(value = "流媒体服务") + private String mediaService; + + @ApiModelProperty(value = "公网 HOST") + private String publicHost; + + @ApiModelProperty(value = "API HOST") + private String apiHost; + + @ApiModelProperty(value = "API 端口") + private Integer apiPort; + + @ApiModelProperty(value = "密钥") + private String secretKey; + + @ApiModelProperty(value = "流ID前缀") + private String streamPrefix; + + @ApiModelProperty(value = "RTP IP") + private String rtpHost; + + @ApiModelProperty(value = "RTP 端口") + private Integer rtpPort; + + @ApiModelProperty(value = "动态端口起始值") + private Integer dynamicPortStart; + + @ApiModelProperty(value = "动态端口结束值") + private Integer dynamicPortEnd; + + @ApiModelProperty(value = "流媒体格式") + private List streamMediaFormatRespList; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/VideoPayResp.java b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/VideoPayResp.java new file mode 100644 index 0000000..26809bc --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/domain/vo/video/VideoPayResp.java @@ -0,0 +1,24 @@ +package com.dite.znpt.monitor.domain.vo.video; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +/** + * 视频播放响应 + * @author huise23 + * @since 2024-11-26 14:03:41 + */ +@Data +@Builder +public class VideoPayResp { + + @ApiModelProperty(value = "播放方式") + private String mediaType; + + @ApiModelProperty(value = "流媒体播放地址") + private List streamMediaFormatList; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoChannelMapper.java b/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoChannelMapper.java new file mode 100644 index 0000000..2c211e8 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoChannelMapper.java @@ -0,0 +1,55 @@ +package com.dite.znpt.monitor.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dite.znpt.monitor.domain.entity.DeviceVideoChannelEntity; +import com.dite.znpt.monitor.domain.req.VideoInfoReq; +import com.dite.znpt.monitor.domain.resp.VideoInfoResp; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelListResp; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelResp; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:08 + * @Description: + */ +public interface DeviceVideoChannelMapper extends BaseMapper { + + /** + * 查询视频通道列表 + * + * @param videoId 视频id + * @param keyword 插叙条件 + * @return {@link List< DeviceVideoChannelListResp>} + */ + List selectDeviceVideoChannel(@Param("videoId") Long videoId, @Param("keyword") String keyword); + + /** + * 查询所有视频通道列表 + * + * @param keyword 插叙条件 + * @return {@link List< DeviceVideoChannelListResp>} + */ + List selectAllDeviceVideoChannel(@Param("keyword") String keyword); + + /** + * 查询视频通道详情 + * + * @param channelCode 通道code + * @return {@link DeviceVideoChannelResp} + */ + DeviceVideoChannelResp getDeviceVideoChannelDetail(@Param("channelCode") String channelCode); + + /** + * 查询通道及视频信息 + * + * @param videoInfoReq 查询参数 + * @return {@link VideoInfoResp } + * @author huise23 + * @since 2024-12-03 13:54:52 + */ + List selectVideoInfoList(VideoInfoReq videoInfoReq); + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoMapper.java b/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoMapper.java new file mode 100644 index 0000000..cc14697 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/mapper/DeviceVideoMapper.java @@ -0,0 +1,24 @@ +package com.dite.znpt.monitor.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoListResp; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:09 + * @Description: + */ +public interface DeviceVideoMapper extends BaseMapper { + /** + * 条件查询视频设备列表 + * @param status 是否在线 + * @param keyword 设备名称或者编码 + * @return {@link List< DeviceVideoListResp>} + */ + List selectDeviceVideoList(@Param("status") String status, @Param("keyword") String keyword, @Param("hostAddress") String hostAddress); + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/mapper/IpConfigMapper.java b/sip/src/main/java/com/dite/znpt/monitor/mapper/IpConfigMapper.java new file mode 100644 index 0000000..1332af0 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/mapper/IpConfigMapper.java @@ -0,0 +1,13 @@ +package com.dite.znpt.monitor.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dite.znpt.monitor.domain.entity.IpConfigEntity; + +/** + * @Date: 2023/09/05 16:39 + * @Description: 监控设备IP配置表数据库访问层 + */ +public interface IpConfigMapper extends BaseMapper { +} + diff --git a/sip/src/main/java/com/dite/znpt/monitor/mapper/StreamMediaFormatMapper.java b/sip/src/main/java/com/dite/znpt/monitor/mapper/StreamMediaFormatMapper.java new file mode 100644 index 0000000..a3ce77a --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/mapper/StreamMediaFormatMapper.java @@ -0,0 +1,12 @@ +package com.dite.znpt.monitor.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat; + +/** + * @Author: huise23 + * @Date: 2022/8/11 15:00 + * @Description: + */ +public interface StreamMediaFormatMapper extends BaseMapper { +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmApi.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmApi.java new file mode 100644 index 0000000..f8604da --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmApi.java @@ -0,0 +1 @@ +package com.dite.znpt.monitor.media.zlm; import com.dite.znpt.monitor.media.zlm.dto.ServerConfig; import com.dite.znpt.monitor.media.zlm.dto.ServerInfo; import com.dite.znpt.monitor.media.zlm.dto.req.*; import com.dite.znpt.monitor.media.zlm.dto.resp.*; import java.io.IOException; import java.util.List; /** * @Author: huise23 * @Date: 2022/8/29 10:14 * @Description: Zlm客户端启动类 */ public interface ZlmApi { /** * 获取API列表 * api: /index/api/getApiList * * @return Api列表 */ List getApiList(ServerInfo server); /** * 获取各epoll(或select)线程负载以及延时 * api: /index/api/getThreadsLoad * * @return 各epoll(或select)线程负载以及延时 */ List getThreadsLoad(ServerInfo server); /** * 获取各后台epoll(或select)线程负载以及延时 * api: /index/api/getWorkThreadsLoad * * @return 各后台epoll(或select)线程负载以及延时 */ List getWorkThreadsLoad(ServerInfo server); /** * 获取服务器配置 * api: /index/api/getServerConfig * * @return 服务器配置 */ List getServerConfig(ServerInfo server); /** * 设置服务器配置 * api: /index/api/setServerConfig * * @param config 服务器配置 * @return 操作结果 */ Integer setServerConfig(ServerInfo server, ServerConfig config); /** * 重启服务器,只有Daemon方式才能重启,否则是直接关闭! * api: /index/api/restartServer * * @return 操作结果 */ Boolean restartServer(ServerInfo server); /** * 获取流列表,可选筛选参数 * api: /index/api/getMediaList * * @param req 请求参数 * @return 操作结果 */ List getMediaList(ServerInfo server, StreamReq req); /** * 关闭流(目前所有类型的流都支持关闭) * api: /index/api/close_streams * * @param req 请求参数 * @return 操作结果 */ CloseStreamResp closeStreams(ServerInfo server, CloseStreamReq req); /** * 获取所有TcpSession列表(获取所有tcp客户端相关信息) * api: /index/api/getAllSession * * @param req 请求参数 * @return 所有TcpSession列表 */ List getAllSession(ServerInfo server, GetAllSessionReq req); /** * 断开tcp连接,比如说可以断开rtsp、rtmp播放器等 * api: /index/api/kick_session * * @param id 客户端唯一id,可以通过getAllSession接口获取 * @return 操作结果 */ Boolean kickSession(ServerInfo server, Long id); /** * 断开tcp连接,比如说可以断开rtsp、rtmp播放器等 * api: /index/api/kick_sessions * * @param req 请求参数 * @return 操作结果 */ Integer kickSession(ServerInfo server, GetAllSessionReq req); /** * 动态添加rtsp/rtmp/hls拉流代理(只支持H264/H265/aac/G711负载) * api: /index/api/addStreamProxy * * @param req 请求参数 * @return 唯一Key */ String addStreamProxy(ServerInfo server, StreamProxyReq req); /** * 关闭拉流代理 * api: /index/api/delStreamProxy * * @param key addStreamProxy接口返回的key * @return 操作结果 */ Boolean delStreamProxy(ServerInfo server, String key); /** * 通过fork FFmpeg进程的方式拉流代理,支持任意协议 * api: /index/api/addFFmpegSource * * @param req 请求参数 * @return 唯一Key */ String addFfMpegSource(ServerInfo server, FFmpegSourceReq req); /** * 关闭ffmpeg拉流代理 * api: /index/api/delFFmpegSource * * @param key addFFmpegSource接口返回的key * @return 操作结果 */ Boolean delFfMpegSource(ServerInfo server, String key); /** * 获取rtp代理时的某路ssrc rtp信息 * api: /index/api/getRtpInfo * * @param streamId RTP的ssrc,16进制字符串或者是流的id(openRtpServer接口指定) * @return 操作结果 */ RtpInfoResp getRtpInfo(ServerInfo server, String streamId); /** * 搜索文件系统,获取流对应的录像文件列表或日期文件夹列表 * api: /index/api/getMp4RecordFile * * @param req 请求参数 * @return 操作结果 */ Mp4RecordFileResp getMp4RecordFile(ServerInfo server, GetMp4RecordFileReq req); /** * 开始录制hls或MP4 * api: /index/api/startRecord * * @param req 请求参数 * @return 操作结果 */ Boolean startRecord(ServerInfo server, RecordReq req); /** * 停止录制流 * api: /index/api/stopRecord * * @param req 请求参数 * @return 操作结果 */ Boolean stopRecord(ServerInfo server, RecordReq req); /** * 获取流录制状态 * api: /index/api/isRecording * * @param req 请求参数 * @return 操作结果 */ Boolean isRecording(ServerInfo server, RecordReq req); /** * 获取截图或生成实时截图并返回 * api: /index/api/getSnap * * @param req 请求参数 * @return jpeg格式的图片,可以在浏览器直接打开 */ void getSnap(ServerInfo server, SnapReq req) throws IOException; /** * 创建GB28181 RTP接收端口,如果该端口接收数据超时,则会自动被回收(不用调用closeRtpServer接口) * api: /index/api/openRtpServer * * @param req 请求参数 * @return 接收端口,方便获取随机端口号 */ Integer openRtpServer(ServerInfo server, RtpServerReq req); /** * 关闭GB28181 RTP接收端口 * api: /index/api/closeRtpServer * * @param streamId 该端口绑定的流ID,该端口只能创建这一个流(而不是根据ssrc创建多个) * @return 是否找到记录并关闭 */ Boolean closeRtpServer(ServerInfo server, String streamId); /** * 获取openRtpServer接口创建的所有RTP服务器 * api: /index/api/listRtpServer * * @return 是否找到记录并关闭 */ List listRtpServer(ServerInfo server); /** * 作为GB28181客户端,启动ps-rtp推流,支持rtp/udp方式; * 该接口支持rtsp/rtmp等协议转ps-rtp推流。第一次推流失败会直接返回错误,成功一次后,后续失败也将无限重试。 * api: /index/api/startSendRtp * * @param req 请求参数 * @return 使用的本地端口号 */ Integer startSendRtp(ServerInfo server, SendRtpReq req); /** * 作为GB28181 Passive TCP服务器; * 该接口支持rtsp/rtmp等协议转ps-rtp被动推流。 * 调用该接口,zlm会启动tcp服务器等待连接请求, * 连接建立后,zlm会关闭tcp服务器,然后源源不断的往客户端推流。 * 第一次推流失败会直接返回错误,成功一次后,后续失败也将无限重试(不停地建立tcp监听,超时后再关闭)。 * api: /index/api/startSendRtpPassive * * @param req 请求参数 * @return 使用的本地端口号 */ Integer startSendRtpPassive(ServerInfo server, SendRtpReq req); /** * 停止GB28181 ps-rtp推流 * api: /index/api/stopSendRtp * * @param req 请求参数 * @return 操作结果 */ Boolean stopSendRtp(ServerInfo server, SendRtpReq req); /** * 获取主要对象个数统计,主要用于分析内存性能 * api: /index/api/getStatistic * * @return 操作结果 */ StatisticResp getStatistic(ServerInfo server); /** * 添加rtsp/rtmp主动推流(把本服务器的直播流推送到其他服务器去) * api: /index/api/addStreamPusherProxy * * @param req 请求参数 * @return 流的唯一标识 */ String addStreamPusherProxy(ServerInfo server, StreamPusherProxyReq req); /** * 关闭推流 * api: /index/api/delStreamPusherProxy * * @param key 流的唯一标识 * @return 操作结果 */ Boolean delStreamPusherProxy(ServerInfo server, String key); } \ No newline at end of file diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmHook.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmHook.java new file mode 100644 index 0000000..a9c1b11 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmHook.java @@ -0,0 +1,135 @@ +package com.dite.znpt.monitor.media.zlm; + +import com.dite.znpt.monitor.media.zlm.dto.ServerConfig; +import com.dite.znpt.monitor.media.zlm.dto.event.*; +import com.dite.znpt.monitor.media.zlm.impl.ZlmHookService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @Author: huise23 + * @Date: 2022/8/29 10:22 + * @Description: + */ +@Slf4j +@RestController +@RequestMapping("/index/hook") +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ZlmHook { + private final ZlmHookService service; + + /** + * 流量统计事件,播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件, + * 阈值通过配置文件general.flowThreshold配置;此事件对回复不敏感。 + */ + @PostMapping(value = "/on_flow_report") + public BaseEventResp onFlowReport(@RequestBody FlowReportReq req) { + return service.onFlowReport(req); + } + + /** + * 访问http文件服务器上hls之外的文件时触发。 + */ + @PostMapping(value = "/on_http_access") + public HttpAccessResp onHttpAccess(@RequestBody HttpAccessReq req) { + return service.onHttpAccess(req); + } + + /** + * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件; + * 如果流不存在,那么先触发on_play事件然后触发on_stream_not_found事件。 + * 播放rtsp流时,如果该流启动了rtsp专属鉴权(on_rtsp_realm)那么将不再触发on_play事件。 + */ + @PostMapping(value = "/on_play") + public BaseEventResp onPlay(@RequestBody PlayReq req) { + return service.onPlay(req); + } + + /** + * rtsp/rtmp/rtp推流鉴权事件。 + */ + @PostMapping(value = "/on_publish") + public PublishResp onPublish(@RequestBody PublishReq req) { + return service.onPublish(req); + } + + + /** + * 录制mp4完成后通知事件;此事件对回复不敏感。 + */ + @PostMapping(value = "/on_record_mp4") + public BaseEventResp onRecordMp4(@RequestBody RecordMp4Req req) { + return service.onRecordMp4(req); + } + + /** + * 该rtsp流是否开启rtsp专用方式的鉴权事件,开启后才会触发on_rtsp_auth事件。 + * 需要指出的是rtsp也支持url参数鉴权,它支持两种方式鉴权。 + */ + @PostMapping(value = "/on_rtsp_realm") + public BaseEventResp onRtspRealm(@RequestBody RtspRealmReq req) { + return service.onRtspRealm(req); + } + + /** + * rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。 + */ + @PostMapping(value = "/on_rtsp_auth") + public RtspAuthResp onRtspAuth(@RequestBody RtspAuthReq req) { + return service.onRtspAuth(req); + } + + /** + * shell登录鉴权,ZLMediaKit提供简单的telnet调试方式 + * 使用telnet 127.0.0.1 9000能进入MediaServer进程的shell界面。 + */ + @PostMapping(value = "/on_shell_login") + public BaseEventResp onShellLogin(@RequestBody ShellLoginReq req) { + return service.onShellLogin(req); + } + + /** + * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 + */ + @PostMapping(value = "/on_stream_changed") + public BaseEventResp onStreamChanged(@RequestBody StreamChangedReq req) { + return service.onStreamChanged(req); + } + + /** + * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。 + */ + @PostMapping(value = "/on_stream_none_reader") + public BaseEventResp onStreamNoneReader(@RequestBody StreamNoneReaderReq req) { + return service.onStreamNoneReader(req); + } + + /** + * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。 + */ + @PostMapping(value = "/on_stream_not_found") + public BaseEventResp onStreamNotFound(@RequestBody StreamNotFoundReq req) { + return service.onStreamNotFound(req); + } + + /** + * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。 + */ + @PostMapping(value = "/on_server_started") + public BaseEventResp onServerStarted(@RequestBody ServerConfig req) { + return service.onServerStarted(req); + } + + /** + * 服务器定时上报时间,上报间隔可配置,默认10s上报一次 + */ + @PostMapping(value = "/on_server_keepalive") + public BaseEventResp onServerKeepalive(@RequestBody ServerKeepaliveReq req) { + return service.onServerKeepalive(req); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmService.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmService.java new file mode 100644 index 0000000..b56e1e1 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/ZlmService.java @@ -0,0 +1,42 @@ +package com.dite.znpt.monitor.media.zlm; + +import com.dite.znpt.monitor.media.zlm.dto.MediaItem; + +/** + * @Author: huise23 + * @Date: 2022/8/30 10:39 + * @Description: 流媒体服务管理主业务 + */ +public interface ZlmService { + /** + * 点播视频 + * + * @param deviceCode 设备编码 + * @param channelCode 通道编码 + * @return 流信息 + */ + MediaItem play(String deviceCode, String channelCode); + + /** + * 失败的时候释放流媒体资源 + * + * @param deviceCode 设备编码 + * @param channelCode 通道编码 + */ + void release(String deviceCode, String channelCode); + + /** + * 失败的时候释放流媒体资源 + * + * @param media 流媒体信息 + */ + void release(MediaItem media); + + /** + * 停止点播 + * + * @param mediaServerId 流媒体服务器id,通过配置文件设置 + * @param streamId 流ID + */ + void display(String mediaServerId, String streamId); +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerCache.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerCache.java new file mode 100644 index 0000000..4f13dad --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerCache.java @@ -0,0 +1,38 @@ +package com.dite.znpt.monitor.media.zlm.cache; + +import com.dite.znpt.monitor.media.zlm.dto.ServerItem; +import com.dite.znpt.service.impl.RedisService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @Author: huise23 + * @Date: 2022/8/30 15:46 + * @Description: + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class MediaServerCache { + private final RedisService redisService; + private final String zlm_key = "zlm_media_server"; + + + public void putLoad(ServerItem serverItem) { + redisService.setCacheObject(zlm_key, serverItem); + } + + /** + * 获取zlm节点 + */ + public ServerItem getLoad() { + return redisService.getCacheObject(zlm_key); + } + + public void releaseSsrc(String ssrc) { + ServerItem item = getLoad(); + item.releaseSsrc(ssrc); + putLoad(item); + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerChannelCache.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerChannelCache.java new file mode 100644 index 0000000..27b7fbb --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/cache/MediaServerChannelCache.java @@ -0,0 +1,54 @@ +package com.dite.znpt.monitor.media.zlm.cache; + +import cn.hutool.core.util.StrUtil; +import com.dite.znpt.monitor.media.zlm.dto.MediaItem; +import com.dite.znpt.service.impl.RedisService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @Author: huise23 + * @Date: 2022/8/30 15:46 + * @Description: + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class MediaServerChannelCache { + private final RedisService redisService; + + private String getKey(String deviceCode, String channelCode) { + return StrUtil.format("zlm_media_server_channel:{}:{}", deviceCode, channelCode); + } + + private String getStreamKey(String mediaServerId, String streamId) { + return StrUtil.format("zlm_media_server_channel_stream_key:{}:{}", mediaServerId, streamId); + } + + public boolean has(String deviceCode, String channelCode) { + return redisService.hasKey(getKey(deviceCode, channelCode)); + } + + public MediaItem get(String deviceCode, String channelCode) { + return redisService.getCacheObject(getKey(deviceCode, channelCode)); + } + + public void put(String deviceCode, String channelCode, MediaItem media) { + String key = getKey(deviceCode, channelCode); + redisService.setCacheObject(key, media); + redisService.setCacheObject(getStreamKey(media.getConfig().getGeneralMediaServerId(), media.getStreamId()), key); + } + + public void delete(MediaItem media) { + redisService.deleteObject(getKey(media.getDeviceCode(), media.getChannelCode())); + redisService.deleteObject(getStreamKey(media.getConfig().getGeneralMediaServerId(), media.getStreamId())); + } + + public MediaItem getByStream(String mediaServerId, String streamId) { + String key = redisService.getCacheObject(getStreamKey(mediaServerId, streamId)); + if (StrUtil.isNotBlank(key)) { + return redisService.getCacheObject(key); + } + return null; + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/config/StreamMediaServerConfig.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/config/StreamMediaServerConfig.java new file mode 100644 index 0000000..e2a7d07 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/config/StreamMediaServerConfig.java @@ -0,0 +1,46 @@ +package com.dite.znpt.monitor.media.zlm.config; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "zlm-config") +public class StreamMediaServerConfig { + + @ApiModelProperty(value = "'流媒体名称'") + private String mediaName; + + @ApiModelProperty(value = "流媒体服务商") + private String mediaService; + + @ApiModelProperty(value = "公网ip") + private String publicHost; + + @ApiModelProperty(value = "接口ip") + private String apiHost; + + @ApiModelProperty(value = "接口端口") + private Integer apiPort; + + @ApiModelProperty(value = "密钥") + private String secretKey; + + @ApiModelProperty(value = "流id前缀") + private String streamPrefix; + + @ApiModelProperty(value = "rtp ip") + private String rtpHost; + + @ApiModelProperty(value = "rtp 端口") + private Integer rtpPort; + + @ApiModelProperty(value = "动态端口起始值") + private String dynamicPortStart; + + @ApiModelProperty(value = "动态端口结束值") + private String dynamicPortEnd; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/MediaItem.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/MediaItem.java new file mode 100644 index 0000000..a39c8c4 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/MediaItem.java @@ -0,0 +1,130 @@ +package com.dite.znpt.monitor.media.zlm.dto; + +import cn.hutool.core.util.StrUtil; +import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat; +import com.dite.znpt.monitor.media.zlm.dto.resp.RtpInfoResp; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/29 15:41 + * @Description: + */ +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class MediaItem implements Serializable { + private static final long serialVersionUID = -6679610697837602559L; + /** + * 设备编码 + */ + private String deviceCode; + /** + * 通道编码 + */ + private String channelCode; + /** + * 节点信息ID + */ + private String configId; + /** + * 节点信息ID + */ + private ServerInfo server; + /** + * 节点格式信息 + */ + private List formatList; + /** + * 节点配置信息 + */ + private ServerConfig config; + /** + * 流ID + */ + private String streamId; + /** + * 播放流信息 + */ + private RtpInfoResp rtp; + /** + * Rtp服务监听端口 + */ + private Integer rtpPort; + /** + * SSRC源地址 + */ + private String ssrc; + /** + * rtmp播放地址 + */ + private String rtmpUrl; + /** + * rtmpSsl播放地址 + */ + private String rtmpSslUrl; + /** + * rtsp播放地址 + */ + private String rtspUrl; + /** + * rtspSsl播放地址 + */ + private String rtspSslUrl; + + /** + * rtc流地址 + */ + private String rtc; + + /** + * rtcs流地址 + */ + private String rtcs; + + /** + * 是否缓存 + */ + private Boolean isCache; + + public List getFormatList(String mediaRouter) { + if (StrUtil.isNotBlank(streamId)) { + formatList.forEach(item -> item.generateUrl(server.getApiHost(), streamId, config, mediaRouter)); + } + return formatList; + } + + public String getRtmpUrl() { + if (StrUtil.isBlank(streamId)) { + return ""; + } + return StrUtil.format("rtmp://{}:{}/rtp/{}", server.getApiHost(), config.getRtmpPort(), streamId); + } + + public String getRtmpSslUrl() { + if (StrUtil.isBlank(streamId)) { + return ""; + } + return config.getRtspSslPort() > 0 ? StrUtil.format("rtmps://{}:{}/rtp/{}", server.getApiHost(), config.getRtspSslPort(), streamId) : ""; + } + + + public String getRtspUrl() { + if (StrUtil.isBlank(streamId)) { + return ""; + } + return StrUtil.format("rtsp://{}:{}/rtp/{}", server.getApiHost(), config.getRtspPort(), streamId); + } + + public String getRtspSslUrl() { + if (StrUtil.isBlank(streamId)) { + return ""; + } + return config.getRtspSslPort() > 0 ? StrUtil.format("rtsps://{}:{}/rtp/{}", server.getApiHost(), config.getRtspSslPort(), streamId) : ""; + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerConfig.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerConfig.java new file mode 100644 index 0000000..62b51d7 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerConfig.java @@ -0,0 +1,852 @@ +package com.dite.znpt.monitor.media.zlm.dto; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.annotation.JSONField; +import com.dite.znpt.monitor.media.zlm.config.StreamMediaServerConfig; +import com.dite.znpt.monitor.media.zlm.dto.req.BaseReq; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * @Author: huise23 + * @Date: 2022/8/29 10:54 + * @Description: 服务器配置 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +public class ServerConfig extends BaseReq { + // ----------------------------------------------- api ----------------------------------------------- + /** + * 是否调试http api,启用调试后,会打印每次http请求的内容和回复 + * apiDebug=1 + */ + @JSONField(name = "api.apiDebug") + private Integer apiDebug; + /** + * 一些比较敏感的http api在访问时需要提供secret,否则无权限调用 + * 如果是通过127.0.0.1访问,那么可以不提供secret + * secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc + */ + @JSONField(name = "api.secret") + private String apiSecret; + /** + * 截图保存路径根目录,截图通过http api(/index/api/getSnap)生成和获取 + * snapRoot=./www/snap/ + */ + @JSONField(name = "api.snapRoot") + private String apiSnapRoot; + /** + * 默认截图图片,在启动FFmpeg截图后但是截图还未生成时,可以返回默认的预设图片 + * defaultSnap=./www/logo.png + */ + @JSONField(name = "api.defaultSnap") + private String apiDefaultSnap; + // ----------------------------------------------- ffmpeg ----------------------------------------------- + /** + * FFmpeg可执行程序路径,支持相对路径/绝对路径 + * bin=/usr/bin/ffmpeg + */ + @JSONField(name = "ffmpeg.bin") + private String ffmpegBin; + /** + * FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数 + * cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s + */ + @JSONField(name = "ffmpeg.cmd") + private String ffmpegCmd; + /** + * FFmpeg生成截图的命令,可以通过修改该配置改变截图分辨率或质量 + * snap=%s -i %s -y -f mjpeg -t 0.001 %s + */ + @JSONField(name = "ffmpeg.snap") + private String ffmpegSnap; + /** + * FFmpeg日志的路径,如果置空则不生成FFmpeg日志 + * 可以为相对(相对于本可执行程序目录)或绝对路径 + * log=./ffmpeg/ffmpeg.log + */ + @JSONField(name = "ffmpeg.log") + private String ffmpegLog; + /** + * 自动重启的时间(秒), 默认为0, 也就是不自动重启. 主要是为了避免长时间ffmpeg拉流导致的不同步现象 + * restart_sec=0 + */ + @JSONField(name = "ffmpeg.restart_sec") + private String ffmpegRestartSec; + // ----------------------------------------------- general ----------------------------------------------- + /** + * 是否启用虚拟主机 + * enableVhost=0 + */ + @JSONField(name = "general.enableVhost") + private Integer enableVhost; + /** + * 播放器或推流器在断开后会触发hook.on_flow_report事件(使用多少流量事件), + * flowThreshold参数控制触发hook.on_flow_report事件阈值,使用流量超过该阈值后才触发,单位KB + * flowThreshold=1024 + */ + @JSONField(name = "general.flowThreshold") + private Integer generalFlowThreshold; + /** + * 播放最多等待时间,单位毫秒 + * 播放在播放某个流时,如果该流不存在, + * ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒 + * 如果在这个时间内,该流注册成功,那么会立即返回播放器播放成功 + * 否则返回播放器未找到该流,该机制的目的是可以先播放再推流 + * maxStreamWaitMS=15000 + */ + @JSONField(name = "general.maxStreamWaitMS") + private Integer generalMaxStreamWaitMs; + /** + * 某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒 + * 在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流 + * streamNoneReaderDelayMS=20000 + */ + @JSONField(name = "general.streamNoneReaderDelayMS") + private Integer generalStreamNoneReaderDelayMs; + /** + * 是否全局添加静音aac音频,转协议时有效 + * 有些播放器在打开单视频流时不能秒开,添加静音音频可以加快秒开速度 + * addMuteAudio=1 + */ + @JSONField(name = "general.addMuteAudio") + private Integer generalAddMuteAudio; + /** + * 拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始, + * 如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写) + * resetWhenRePlay=1 + */ + @JSONField(name = "general.resetWhenRePlay") + private Integer generalResetWhenRePlay; + /** + * 是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置 + * publishToHls=1 + */ + @JSONField(name = "general.publishToHls") + private Integer generalPublishToHls; + /** + * 是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置 + * publishToMP4=0 + */ + @JSONField(name = "general.publishToMP4") + private Integer generalPublishToMP4; + /** + * 合并写缓存大小(单位毫秒),合并写指服务器缓存一定的数据后才会一次性写入socket,这样能提高性能,但是会提高延时 + * 开启后会同时关闭TCP_NODELAY并开启MSG_MORE + * mergeWriteMS=0 + */ + @JSONField(name = "general.mergeWriteMS") + private Integer generalMergeWriteMS; + /** + * 全局的时间戳覆盖开关,在转协议时,对frame进行时间戳覆盖 + * 该开关对rtsp/rtmp/rtp推流、rtsp/rtmp/hls拉流代理转协议时生效 + * 会直接影响rtsp/rtmp/hls/mp4/flv等协议的时间戳 + * 同协议情况下不影响(例如rtsp/rtmp推流,那么播放rtsp/rtmp时不会影响时间戳) + * modifyStamp=0 + */ + @JSONField(name = "general.modifyStamp") + private Integer generalModifyStamp; + /** + * 服务器唯一id,用于触发hook时区别是哪台服务器 + * mediaServerId=your_server_id + */ + @JSONField(name = "general.mediaServerId") + private String generalMediaServerId; + /** + * 转协议是否全局开启或关闭音频 + * enable_audio=1 + */ + @JSONField(name = "general.enable_audio") + private Integer generalEnableAudio; + // ###### 以下是按需转协议的开关,在测试ZLMediaKit的接收推流性能时,请把下面开关置1 + // ###### 如果某种协议你用不到,你可以把以下开关置1以便节省资源(但是还是可以播放,只是第一个播放者体验稍微差点), + // ###### 如果某种协议你想获取最好的用户体验,请置0(第一个播放者可以秒开,且不花屏) + /** + * hls协议是否按需生成,如果hls.segNum配置为0(意味着hls录制),那么hls将一直生成(不管此开关) + * hls_demand=0 + */ + @JSONField(name = "general.hls_demand") + private Integer generalHlsDemand; + /** + * rtsp[s]协议是否按需生成 + * rtsp_demand=0 + */ + @JSONField(name = "general.rtsp_demand") + private Integer generalRtspDemand; + /** + * rtmp[s]、http[s]-flv、ws[s]-flv协议是否按需生成 + * rtmp_demand=0 + */ + @JSONField(name = "general.rtmp_demand") + private Integer generalRtmpDemand; + /** + * http[s]-ts协议是否按需生成 + * ts_demand=0 + */ + @JSONField(name = "general.ts_demand") + private Integer generalTsDemand; + /** + * http[s]-fmp4、ws[s]-fmp4协议是否按需生成 + * fmp4_demand=0 + */ + @JSONField(name = "general.fmp4_demand") + private Integer generalFmp4Demand; + /** + * 最多等待未初始化的Track时间,单位毫秒,超时之后会忽略未初始化的Track + * wait_track_ready_ms=10000 + */ + @JSONField(name = "general.wait_track_ready_ms") + private Integer generalWaitTrackReadyMs; + /** + * 如果流只有单Track,最多等待若干毫秒,超时后未收到其他Track的数据,则认为是单Track + * 如果协议元数据有声明特定track数,那么无此等待时间 + * wait_add_track_ms=3000 + */ + @JSONField(name = "general.wait_add_track_ms") + private Integer generalWaitAddTrackMs; + /** + * 如果track未就绪,我们先缓存帧数据,但是有最大个数限制,防止内存溢出 + * unready_frame_cache=100 + */ + @JSONField(name = "general.unready_frame_cache") + private Integer generalUnreadyFrameCache; + /** + * 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 + * 置0关闭此特性(推流断开会导致立即断开播放器) + * 此参数不应大于播放器超时时间 + * continue_push_ms=15000 + */ + @JSONField(name = "general.continue_push_ms") + private Integer generalContinuePushMs; + // ----------------------------------------------- hls ----------------------------------------------- + /** + * hls写文件的buf大小,调整参数可以提高文件io性能 + * fileBufSize=65536 + */ + @JSONField(name = "hls.fileBufSize") + private Integer hlsFileBufSize; + /** + * hls保存文件路径 + * 可以为相对(相对于本可执行程序目录)或绝对路径 + * filePath=./www + */ + @JSONField(name = "hls.filePath") + private String hlsFilePath; + /** + * hls最大切片时间 + * segDur=2 + */ + @JSONField(name = "hls.segDur") + private Integer hlsSegDur; + /** + * m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个) + * 如果设置为0,则不删除切片,而是保存为点播 + * segNum=3 + */ + @JSONField(name = "hls.segNum") + private Integer hlsSegNum; + /** + * HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 + * segRetain=5 + */ + @JSONField(name = "hls.segRetain") + private Integer hlsSegRetain; + /** + * 是否广播 ts 切片完成通知 + * broadcastRecordTs=0 + */ + @JSONField(name = "hls.broadcastRecordTs") + private Integer hlsBroadcastRecordTs; + /** + * 直播hls文件删除延时,单位秒,issue: #913 + * deleteDelaySec=0 + */ + @JSONField(name = "hls.deleteDelaySec") + private Integer hlsDeleteDelaySec; + /** + * 是否保留hls文件,此功能部分等效于segNum=0的情况 + * 不同的是这个保留不会在m3u8文件中体现 + * 0为不保留,不起作用 + * 1为保留,则不删除hls文件,如果开启此功能,注意磁盘大小,或者定期手动清理hls文件 + * segKeep=0 + */ + @JSONField(name = "hls.segKeep") + private Integer hlsSegKeep; + // ----------------------------------------------- hook ----------------------------------------------- + /** + * 在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然 + * 该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患 + * admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc + */ + @JSONField(name = "hook.admin_params") + private String hookAdminParams; + /** + * 是否启用hook事件,启用后,推拉流都将进行鉴权 + * enable=0 + */ + @JSONField(name = "hook.enable") + private Integer hookHookEnable; + /** + * 播放器或推流器使用流量事件,置空则关闭 + * on_flow_report=https://127.0.0.1/index/hook/on_flow_report + */ + @JSONField(name = "hook.on_flow_report") + private String hookOnFlowReport; + /** + * 访问http文件鉴权事件,置空则关闭鉴权 + * on_http_access=https://127.0.0.1/index/hook/on_http_access + */ + @JSONField(name = "hook.on_http_access") + private String hookOnHttpAccess; + /** + * 播放鉴权事件,置空则关闭鉴权 + * on_play=https://127.0.0.1/index/hook/on_play + */ + @JSONField(name = "hook.on_play") + private String hookOnPlay; + /** + * 推流鉴权事件,置空则关闭鉴权 + * on_publish=https://127.0.0.1/index/hook/on_publish + */ + @JSONField(name = "hook.on_publish") + private String hookOnPublish; + /** + * 录制mp4切片完成事件 + * on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4 + */ + @JSONField(name = "hook.on_record_mp4") + private String hookOnRecordMp4; + /** + * 录制 hls ts 切片完成事件 + * on_record_ts=https://127.0.0.1/index/hook/on_record_ts + */ + @JSONField(name = "hook.on_record_ts") + private String hookOnRecordTs; + /** + * rtsp播放鉴权事件,此事件中比对rtsp的用户名密码 + * on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth + */ + @JSONField(name = "hook.on_rtsp_auth") + private String hookOnRtspAuth; + /** + * rtsp播放是否开启专属鉴权事件,置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权 + * 建议开发者统一采用url参数方式鉴权,rtsp用户名密码鉴权一般在设备上用的比较多 + * 开启rtsp专属鉴权后,将不再触发on_play鉴权事件 + * on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm + */ + @JSONField(name = "hook.on_rtsp_realm") + private String hookOnRtspRealm; + /** + * 远程telnet调试鉴权事件 + * on_shell_login=https://127.0.0.1/index/hook/on_shell_login + */ + @JSONField(name = "hook.on_shell_login") + private String hookOnShellLogin; + /** + * 直播流注册或注销事件 + * on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed + */ + @JSONField(name = "hook.on_stream_changed") + private String hookOnStreamChanged; + /** + * 服务器启动报告,可以用于服务器的崩溃重启事件监听 + * on_server_started=https://127.0.0.1/index/hook/on_server_started + */ + @JSONField(name = "hook.on_server_started") + private String hookOnServerStarted; + /** + * server保活上报 + * on_server_keepalive=https://127.0.0.1/index/hook/on_server_keepalive + */ + @JSONField(name = "hook.on_server_keepalive") + private String hookOnServerKeepalive; + /** + * 无人观看流事件,通过该事件,可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用 + * on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader + */ + @JSONField(name = "hook.on_stream_none_reader") + private String hookOnStreamNoneReader; + /** + * 播放时,未找到流事件,通过配合hook.on_stream_none_reader事件可以完成按需拉流 + * on_stream_not_found=https://127.0.0.1/index/hook/on_stream_not_found + */ + @JSONField(name = "hook.on_stream_not_found") + private String hookOnStreamNotFound; + /** + * 发送rtp(startSendRtp)被动关闭时回调 + * on_send_rtp_stopped=https://127.0.0.1/index/hook/on_send_rtp_stopped + */ + @JSONField(name = "hook.on_send_rtp_stopped") + private String hookOnSendRtpStopped; + /** + * hook api最大等待回复时间,单位秒 + * timeoutSec=10 + */ + @JSONField(name = "hook.timeoutSec") + private Integer hookTimeoutSec; + /** + * keepalive hook触发间隔,单位秒,float类型 + * alive_interval=10.0 + */ + @JSONField(name = "hook.alive_interval") + private Float hookAliveInterval; + /** + * hook通知失败重试次数,正整数。为0不重试,1时重试一次,以此类推 + * retry=1 + */ + @JSONField(name = "hook.retry") + private Integer hookRetry; + /** + * hook通知失败重试延时,单位秒,float型 + * retry_delay=3.0 + */ + @JSONField(name = "hook.retry_delay") + private Float hookRetryDelay; + // ----------------------------------------------- cluster ----------------------------------------------- + /** + * 设置源站拉流url模板, 格式跟printf类似,第一个%s指定app,第二个%s指定stream_id, + * 开启集群模式后,on_stream_not_found和on_stream_none_reader hook将无效. + * 溯源模式支持以下类型: + * rtmp方式: rtmp://127.0.0.1:1935/%s/%s + * rtsp方式: rtsp://127.0.0.1:554/%s/%s + * hls方式: http://127.0.0.1:80/%s/%s/hls.m3u8 + * http-ts方式: http://127.0.0.1:80/%s/%s.live.ts + * 支持多个源站,不同源站通过分号(;)分隔 + * origin_url= + */ + @JSONField(name = "cluster.origin_url") + private String clusterOriginUrl; + /** + * 溯源总超时时长,单位秒,float型;假如源站有3个,那么单次溯源超时时间为timeout_sec除以3 + * 单次溯源超时时间不要超过general.maxStreamWaitMS配置 + * timeout_sec=15 + */ + @JSONField(name = "cluster.timeout_sec") + private Integer clusterTimeoutSec; + /** + * 溯源失败尝试次数,-1时永久尝试 + * retry_count=3 + */ + @JSONField(name = "cluster.retry_count") + private Integer clusterRetryCount; + // ----------------------------------------------- http ----------------------------------------------- + /** + * http服务器字符编码,windows上默认gb2312 + * charSet=utf-8 + */ + @JSONField(name = "http.charSet") + private String httpCharSet; + /** + * http链接超时时间 + * keepAliveSecond=30 + */ + @JSONField(name = "http.keepAliveSecond") + private Integer httpKeepAliveSecond; + /** + * http请求体最大字节数,如果post的body太大,则不适合缓存body在内存 + * maxReqSize=40960 + */ + @JSONField(name = "http.maxReqSize") + private Integer httpMaxReqSize; + /** + * 404网页内容,用户可以自定义404网页 + * notFound=404 Not Found

您访问的资源不存在!


ZLMediaKit-4.0
+ */ + @JSONField(name = "http.notFound") + private String httpNotFound; + /** + * http服务器监听端口 + * port=80 + */ + @JSONField(name = "http.port") + private Integer httpPort; + /** + * http文件服务器根目录 + * 可以为相对(相对于本可执行程序目录)或绝对路径 + * rootPath=./www + */ + @JSONField(name = "http.rootPath") + private String httpRootPath; + /** + * http文件服务器读文件缓存大小,单位BYTE,调整该参数可以优化文件io性能 + * sendBufSize=65536 + */ + @JSONField(name = "http.sendBufSize") + private Integer httpSendBufSize; + /** + * https服务器监听端口 + * sslport=443 + */ + @JSONField(name = "http.sslport") + private Integer httpSslPort; + /** + * 是否显示文件夹菜单,开启后可以浏览文件夹 + * dirMenu=1 + */ + @JSONField(name = "http.dirMenu") + private Integer httpDirMenu; + /** + * 虚拟目录, 虚拟目录名和文件路径使用","隔开,多个配置路径间用";"隔开 + * 例如赋值为 app_a,/path/to/a;app_b,/path/to/b 那么 + * 访问 http://127.0.0.1/app_a/file_a 对应的文件路径为 /path/to/a/file_a + * 访问 http://127.0.0.1/app_b/file_b 对应的文件路径为 /path/to/b/file_b + * 访问其他http路径,对应的文件路径还是在rootPath内 + * virtualPath= + */ + @JSONField(name = "http.virtualPath") + private String httpVirtualPath; + /** + * 禁止后缀的文件使用mmap缓存,使用“,”隔开 + * 例如赋值为 .mp4,.flv + * 那么访问后缀为.mp4与.flv 的文件不缓存 + * forbidCacheSuffix= + */ + @JSONField(name = "http.forbidCacheSuffix") + private String httpForbidCacheSuffix; + /** + * 可以把http代理前真实客户端ip放在http头中:https://github.com/ZLMediaKit/ZLMediaKit/issues/1388 + * 切勿暴露此key,否则可能导致伪造客户端ip + * forwarded_ip_header= + */ + @JSONField(name = "http.forwarded_ip_header") + private String httpForwardedIpHeader; + // ----------------------------------------------- multicast ----------------------------------------------- + /** + * rtp组播截止组播ip地址 + * addrMax=239.255.255.255 + */ + @JSONField(name = "multicast.addrMax") + private String multicastAddrMax; + /** + * rtp组播起始组播ip地址 + * addrMin=239.0.0.0 + */ + @JSONField(name = "multicast.addrMin") + private String multicastAddrMin; + /** + * 组播udp ttl + * udpTTL=64 + */ + @JSONField(name = "multicast.udpTTL") + private Integer multicastUdpTtl; + // ----------------------------------------------- record ----------------------------------------------- + /** + * mp4录制或mp4点播的应用名,通过限制应用名,可以防止随意点播 + * 点播的文件必须放置在此文件夹下 + * appName=record + */ + @JSONField(name = "record.appName") + private String recordAppName; + /** + * mp4录制写文件缓存,单位BYTE,调整参数可以提高文件io性能 + * fileBufSize=65536 + */ + @JSONField(name = "record.fileBufSize") + private Integer recordFileBufSize; + /** + * mp4录制保存、mp4点播根路径 + * 可以为相对(相对于本可执行程序目录)或绝对路径 + * filePath=./www + */ + @JSONField(name = "record.filePath") + private String recordFilePath; + /** + * mp4录制切片时间,单位秒 + * fileSecond=3600 + */ + @JSONField(name = "record.fileSecond") + private Integer recordFileSecond; + /** + * mp4点播每次流化数据量,单位毫秒, + * 减少该值可以让点播数据发送量更平滑,增大该值则更节省cpu资源 + * sampleMS=500 + */ + @JSONField(name = "record.sampleMS") + private Integer recordSampleMs; + /** + * mp4录制完成后是否进行二次关键帧索引写入头部 + * fastStart=0 + */ + @JSONField(name = "record.fastStart") + private Integer recordFastStart; + /** + * MP4点播(rtsp/rtmp/http-flv/ws-flv)是否循环播放文件 + * fileRepeat=0 + */ + @JSONField(name = "record.fileRepeat") + private Integer recordFileRepeat; + /** + * MP4录制是否当做播放器参与播放人数统计 + * mp4_as_player=0 + */ + @JSONField(name = "record.mp4_as_player") + private Integer recordMp4AsPlayer; + // ----------------------------------------------- rtmp ----------------------------------------------- + /** + * rtmp必须在此时间内完成握手,否则服务器会断开链接,单位秒 + * handshakeSecond=15 + */ + @JSONField(name = "rtmp.handshakeSecond") + private Integer rtmpHandshakeSecond; + /** + * rtmp超时时间,如果该时间内未收到客户端的数据, + * 或者tcp发送缓存超过这个时间,则会断开连接,单位秒 + * keepAliveSecond=15 + */ + @JSONField(name = "rtmp.keepAliveSecond") + private Integer rtmpKeepAliveSecond; + /** + * 在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂) + * modifyStamp=0 + */ + @JSONField(name = "rtmp.modifyStamp") + private Integer rtmpModifyStamp; + /** + * rtmp服务器监听端口 + * port=1935 + */ + @JSONField(name = "rtmp.port") + private Integer rtmpPort = 0; + /** + * rtmps服务器监听地址 + * sslport=0 + */ + @JSONField(name = "rtmp.sslport") + private Integer rtmpSslPort = 0; + // ----------------------------------------------- rtp ----------------------------------------------- + /** + * 音频mtu大小,该参数限制rtp最大字节数,推荐不要超过1400 + * 加大该值会明显增加直播延时 + * audioMtuSize=600 + */ + @JSONField(name = "rtp.audioMtuSize") + private Integer rtpAudioMtuSize; + /** + * 视频mtu大小,该参数限制rtp最大字节数,推荐不要超过1400 + * videoMtuSize=1400 + */ + @JSONField(name = "rtp.videoMtuSize") + private Integer rtpVideoMtuSize; + /** + * rtp包最大长度限制,单位KB,主要用于识别TCP上下文破坏时,获取到错误的rtp + * rtpMaxSize=10 + */ + @JSONField(name = "rtp.rtpMaxSize") + private Integer rtpMaxSize; + // ----------------------------------------------- rtp_proxy ----------------------------------------------- + /** + * 导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出 + * dumpDir= + */ + @JSONField(name = "rtp_proxy.dumpDir") + private String proxyDumpDir; + /** + * udp和tcp代理服务器,支持rtp(必须是ts或ps类型)代理 + * port=10000 + */ + @JSONField(name = "rtp_proxy.port") + private Integer proxyPort; + /** + * rtp超时时间,单位秒 + * timeoutSec=15 + */ + @JSONField(name = "rtp_proxy.timeoutSec") + private Integer proxyTimeoutSec; + /** + * 随机端口范围,最少确保36个端口 + * 该范围同时限制rtsp服务器udp端口范围 + * port_range=30000-35000 + */ + @JSONField(name = "rtp_proxy.port_range") + private String proxyPortRange; + /** + * rtp h264 负载的pt + * h264_pt=98 + */ + @JSONField(name = "rtp_proxy.h264_pt") + private Integer proxyH264Pt; + /** + * rtp h265 负载的pt + * h265_pt=99 + */ + @JSONField(name = "rtp_proxy.h265_pt") + private Integer proxyH265Pt; + /** + * rtp ps 负载的pt + * ps_pt=96 + */ + @JSONField(name = "rtp_proxy.ps_pt") + private Integer proxyPsPt; + /** + * rtp ts 负载的pt + * ts_pt=33 + */ + @JSONField(name = "rtp_proxy.ts_pt") + private Integer proxyTsPt; + /** + * rtp opus 负载的pt + * opus_pt=100 + */ + @JSONField(name = "rtp_proxy.opus_pt") + private Integer proxyOpusPt; + /** + * rtp g711u 负载的pt + * g711u_pt=0 + */ + @JSONField(name = "rtp_proxy.g711u_pt") + private Integer proxyG711UPt; + /** + * rtp g711a 负载的pt + * g711a_pt=8 + */ + @JSONField(name = "rtp_proxy.g711a_pt") + private Integer proxyG711APt; + // ----------------------------------------------- rtc ----------------------------------------------- + /** + * rtc播放推流、播放超时时间 + * timeoutSec=15 + */ + @JSONField(name = "rtc.timeoutSec") + private Integer rtcTimeoutSec; + /** + * 本机对rtc客户端的可见ip,作为服务器时一般为公网ip,可有多个,用','分开,当置空时,会自动获取网卡ip + * 同时支持环境变量,以$开头,如"$EXTERN_IP"; 请参考:https://github.com/ZLMediaKit/ZLMediaKit/pull/1786 + * externIP= + */ + @JSONField(name = "rtc.externIP") + private String rtcExternIp; + /** + * rtc udp服务器监听端口号,所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据, + * 该端口是多线程的,同时支持客户端网络切换导致的连接迁移 + * 需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致 + * port=8000 + */ + @JSONField(name = "rtc.port") + private Integer rtcPort; + /** + * 设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质 + * 目前已经实现twcc自动调整码率,关闭remb根据真实网络状况调整码率 + * rembBitRate=0 + */ + @JSONField(name = "rtc.rembBitRate") + private Integer rtcRembBitRate; + /** + * rtc支持的音频codec类型,在前面的优先级更高 + * 以下范例为所有支持的音频codec + * preferredCodecA=PCMU,PCMA,opus,mpeg4-generic + */ + @JSONField(name = "rtc.preferredCodecA") + private String rtcPreferredCodecA; + /** + * rtc支持的视频codec类型,在前面的优先级更高 + * 以下范例为所有支持的视频codec + * preferredCodecV=H264,H265,AV1X,VP9,VP8 + */ + @JSONField(name = "rtc.preferredCodecV") + private String rtcPreferredCodecV; + // ----------------------------------------------- srt ----------------------------------------------- + /** + * srt播放推流、播放超时时间,单位秒 + * timeoutSec=5 + */ + @JSONField(name = "srt.timeoutSec") + private Integer srtTimeoutSec; + /** + * srt udp服务器监听端口号,所有srt客户端将通过该端口传输srt数据, + * 该端口是多线程的,同时支持客户端网络切换导致的连接迁移 + * port=9000 + */ + @JSONField(name = "srt.port") + private Integer srtPort; + /** + * srt 协议中延迟缓存的估算参数,在握手阶段估算rtt ,然后latencyMul*rtt 为最大缓存时长,此参数越大,表示等待重传的时长就越大 + * latencyMul=4 + */ + @JSONField(name = "srt.latencyMul") + private Integer srtLatencyMul; + /** + * 包缓存的大小 + * pktBufSize=8192 + */ + @JSONField(name = "srt.pktBufSize") + private Integer srtPktBufSize; + // ----------------------------------------------- rtsp ----------------------------------------------- + /** + * rtsp专有鉴权方式是采用base64还是md5方式 + * authBasic=0 + */ + @JSONField(name = "rtsp.authBasic") + private Integer rtspAuthBasic; + /** + * rtsp拉流、推流代理是否是直接代理模式 + * 直接代理后支持任意编码格式,但是会导致GOP缓存无法定位到I帧,可能会导致开播花屏 + * 并且如果是tcp方式拉流,如果rtp大于mtu会导致无法使用udp方式代理 + * 假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理 + * 如果你是rtsp推拉流,但是webrtc播放,也建议关闭直接代理模式, + * 因为直接代理时,rtp中可能没有sps pps,会导致webrtc无法播放; 另外webrtc也不支持Single NAL Unit Packets类型rtp + * 默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的 + * directProxy=1 + */ + @JSONField(name = "rtsp.directProxy") + private Integer rtspDirectProxy; + /** + * rtsp必须在此时间内完成握手,否则服务器会断开链接,单位秒 + * handshakeSecond=15 + */ + @JSONField(name = "rtsp.handshakeSecond") + private Integer rtspHandshakeSecond; + /** + * rtsp超时时间,如果该时间内未收到客户端的数据, + * 或者tcp发送缓存超过这个时间,则会断开连接,单位秒 + * keepAliveSecond=15 + */ + @JSONField(name = "rtsp.keepAliveSecond") + private Integer rtspKeepAliveSecond; + /** + * rtsp服务器监听地址 + * port=554 + */ + @JSONField(name = "rtsp.port") + private Integer rtspPort = 0; + /** + * rtsps服务器监听地址 + * sslport=0 + */ + @JSONField(name = "rtsp.sslport") + private Integer rtspSslPort = 0; + // ----------------------------------------------- shell ----------------------------------------------- + /** + * 调试telnet服务器接受最大bufffer大小 + * maxReqSize=1024 + */ + @JSONField(name = "shell.maxReqSize") + private Integer shellMaxReqSize; + /** + * 调试telnet服务器监听端口 + * port=0 + */ + @JSONField(name = "shell.port") + private Integer shellPort; + + public void refreshHook(String ip, String port, StreamMediaServerConfig server) { + String host = ip + ":" + port; + this.hookOnFlowReport = StrUtil.format("http://{}/index/hook/on_flow_report", host); + this.hookOnHttpAccess = StrUtil.format("http://{}/index/hook/on_http_access", host); + this.hookOnPlay = StrUtil.format("http://{}/index/hook/on_play", host); + this.hookOnPublish = StrUtil.format("http://{}/index/hook/on_publish", host); + this.hookOnRecordMp4 = StrUtil.format("http://{}/index/hook/on_record_mp4", host); + this.hookOnRecordTs = StrUtil.format("http://{}/index/hook/on_record_ts", host); + this.hookOnRtspAuth = StrUtil.format("http://{}/index/hook/on_rtsp_auth", host); + this.hookOnRtspRealm = StrUtil.format("http://{}/index/hook/on_rtsp_realm", host); + this.hookOnShellLogin = StrUtil.format("http://{}/index/hook/on_shell_login", host); + this.hookOnStreamChanged = StrUtil.format("http://{}/index/hook/on_stream_changed", host); + this.hookOnStreamNoneReader = StrUtil.format("http://{}/index/hook/on_stream_none_reader", host); + this.hookOnStreamNotFound = StrUtil.format("http://{}/index/hook/on_stream_not_found", host); + this.hookOnServerStarted = StrUtil.format("http://{}/index/hook/on_server_started", host); + this.hookOnServerKeepalive = StrUtil.format("http://{}/index/hook/on_server_keepalive", host); +// this.hookOnSendRtpStopped = StrUtil.format("http://{}/index/hook/on_send_rtp_stopped", host); + this.hookOnSendRtpStopped = ""; + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerInfo.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerInfo.java new file mode 100644 index 0000000..143b1fc --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerInfo.java @@ -0,0 +1,30 @@ +package com.dite.znpt.monitor.media.zlm.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/30 10:50 + * @Description: 节点基础信息 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ServerInfo implements Serializable { + /** + * 节点地址 + */ + private String apiHost; + /** + * 节点端口 + */ + private Integer apiPort; + /** + * 节点秘钥 + */ + private String secretKey; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerItem.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerItem.java new file mode 100644 index 0000000..d590d3f --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/ServerItem.java @@ -0,0 +1,108 @@ +package com.dite.znpt.monitor.media.zlm.dto; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import com.dite.znpt.exception.ServiceException; +import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat; +import com.dite.znpt.monitor.media.zlm.config.StreamMediaServerConfig; +import com.dite.znpt.monitor.media.zlm.dto.resp.MediaResp; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @Author: huise23 + * @Date: 2022/8/30 10:50 + * @Description: 节点信息 + */ +@Data +@NoArgsConstructor +public class ServerItem implements Serializable { + + private static final long serialVersionUID = 2460404295026548536L; + /** + * 播流最大并发个数 + */ + public static final Integer MAX_PLAY_COUNT = 10000; + /** + * 节点信息ID + */ + private String configId; + /** + * 流ID前缀 + */ + private String streamPrefix; + /** + * 节点信息ID + */ + private ServerInfo server; + /** + * 节点格式信息 + */ + private List formatList; + /** + * 节点配置信息 + */ + private ServerConfig config; + /** + * 当前流信息 + */ + private List media; + /** + * 节点状态是否正常 + */ + private Boolean status; + /** + * 流媒体服务器已用的会话句柄 + */ + private Set usedSn; + + public ServerItem(StreamMediaServerConfig server, List formatList) { + this.streamPrefix = server.getStreamPrefix(); + this.server = new ServerInfo(server.getApiHost(), server.getApiPort(), server.getSecretKey()); + this.formatList = formatList; + this.status = false; + this.usedSn = new HashSet<>(); + } + + public String genPlaySsrc(String channelCode) { + if (this.usedSn.size() >= MAX_PLAY_COUNT) { + throw new ServiceException("ssrc已经用完!"); + } + int sn; + for (sn = 0; sn < MAX_PLAY_COUNT; sn++) { + if (!this.usedSn.contains(sn)) { + this.usedSn.add(sn); + break; + } + } + //return StrUtil.format("0{}{}", StrUtil.blankToDefault(streamPrefix, channelCode.substring(3, 8)), NumberUtil.decimalFormat("0000", sn)); + return channelCode; + } + + public void releaseSsrc(String ssrc) { + try { + Integer sn = NumberUtil.parseInt(ssrc.substring(6)); + usedSn.remove(sn); + } catch (Exception ignored) { + } + } + + public void setMedia(List media) { + this.media = media; + if (CollUtil.isNotEmpty(media)) { + media.forEach(item -> { + try { + Integer sn = NumberUtil.parseInt(StrUtil.isBlank(streamPrefix) ? item.getStream().substring(6) : item.getStream().replace("0" + streamPrefix, "")); + usedSn.add(sn); + } catch (Exception ignored) { + } + }); + } + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventReq.java new file mode 100644 index 0000000..a4125ae --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventReq.java @@ -0,0 +1,48 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import lombok.Data; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:24 + * @Description: + */ +@Data +public class BaseEventReq { + /** + * 服务器id,通过配置文件设置 + */ + private String mediaServerId; + /** + * 流应用名 + */ + private String app; + /** + * TCP链接唯一ID + */ + private String id; + /** + * 播放器ip + */ + private String ip; + /** + * 播放url参数 + */ + private String params; + /** + * 播放器端口号 + */ + private Integer port; + /** + * 播放的协议,可能是rtsp、rtmp、http + */ + private String schema; + /** + * 流ID + */ + private String stream; + /** + * 流虚拟主机 + */ + private String vhost; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventResp.java new file mode 100644 index 0000000..04b573f --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/BaseEventResp.java @@ -0,0 +1,42 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @Author: huise23 + * @Date: 2022/8/29 10:33 + * @Description: 请求响应基类 + */ +@Data +@Accessors(chain = true) +public class BaseEventResp { + /** + * code == 0时代表完全成功 + */ + private Integer code; + /** + * 失败提示 + */ + private String msg; + /** + * 失败具体原因 + */ + private String err; + /** + * 该rtsp流是否需要rtsp专有鉴权,空字符串代码不需要鉴权 + */ + private String realm; + /** + * 是否关闭推流或拉流 + */ + private Boolean close; + + public BaseEventResp setSuccess(){ + return this.setCode(0).setMsg("success").setErr(""); + } + + public static BaseEventResp success() { + return new BaseEventResp().setSuccess(); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/FlowReportReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/FlowReportReq.java new file mode 100644 index 0000000..1661a94 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/FlowReportReq.java @@ -0,0 +1,26 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:04 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class FlowReportReq extends BaseEventReq { + /** + * tcp链接维持时间,单位秒 + */ + private Integer duration; + /** + * true为播放器,false为推流器 + */ + private Boolean player; + /** + * 耗费上下行流量总和,单位字节 + */ + private Integer totalBytes; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessReq.java new file mode 100644 index 0000000..dfae6d6 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessReq.java @@ -0,0 +1,29 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import cn.hutool.core.lang.Dict; +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:10 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class HttpAccessReq extends BaseEventReq { + /** + * http客户端请求header + */ + private Dict header; + /** + * 访问路径是文件还是目录 + */ + @JSONField(name = "is_dir") + private Boolean isDir; + /** + * 请求访问的文件或目录 + */ + private String path; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessResp.java new file mode 100644 index 0000000..51424d8 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/HttpAccessResp.java @@ -0,0 +1,34 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:15 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +public class HttpAccessResp extends BaseEventResp { + /** + * 该客户端能访问或被禁止的顶端目录,如果为空字符串,则表述为当前目录 + */ + private String path; + /** + * 本次授权结果的有效期,单位秒 + */ + private Integer second; + /** + * 服务器id,通过配置文件设置 + */ + private String mediaServerId; + + public static HttpAccessResp success() { + HttpAccessResp resp = new HttpAccessResp(); + resp.setSuccess(); + return resp.setSecond(600).setPath(""); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PlayReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PlayReq.java new file mode 100644 index 0000000..2531052 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PlayReq.java @@ -0,0 +1,14 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:19 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class PlayReq extends BaseEventReq { +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishReq.java new file mode 100644 index 0000000..de324dc --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishReq.java @@ -0,0 +1,14 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:57 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class PublishReq extends BaseEventReq{ +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishResp.java new file mode 100644 index 0000000..f16d943 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/PublishResp.java @@ -0,0 +1,81 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:27 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class PublishResp extends BaseEventResp { + /** + * 是否转换成hls协议 + */ + @JSONField(name = "enable_hls") + private Boolean enableHls; + /** + * 是否允许mp4录制 + */ + @JSONField(name = "enable_mp4") + private Boolean enableMp4; + /** + * 是否转rtsp协议 + */ + @JSONField(name = "enable_rtsp") + private Boolean enableRtsp; + /** + * 是否转rtmp/flv协议 + */ + @JSONField(name = "enable_rtmp") + private Boolean enableRtmp; + /** + * 是否转http-ts/ws-ts协议 + */ + @JSONField(name = "enable_ts") + private Boolean enableTs; + /** + * 是否转http-fmp4/ws-fmp4协议 + */ + @JSONField(name = "enable_fmp4") + private Boolean enableFmp4; + /** + * 转协议时是否开启音频 + */ + @JSONField(name = "enable_audio") + private Boolean enableAudio; + /** + * 转协议时,无音频是否添加静音aac音频 + */ + @JSONField(name = "add_mute_audio") + private Boolean addMuteAudio; + /** + * mp4录制文件保存根目录,置空使用默认 + */ + @JSONField(name = "mp4_save_path") + private String mp4SavePath; + /** + * mp4录制切片大小,单位秒 + */ + @JSONField(name = "mp4_max_second") + private Integer mp4MaxSecond; + /** + * hls文件保存保存根目录,置空使用默认 + */ + @JSONField(name = "hls_save_path") + private String hlsSavePath; + /** + * 断连续推延时,单位毫秒,置空使用配置文件默认值 + */ + @JSONField(name = "continue_push_ms") + private Long continuePushMs; + + public static PublishResp success() { + PublishResp resp = new PublishResp(); + resp.setSuccess(); + return resp; + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RecordMp4Req.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RecordMp4Req.java new file mode 100644 index 0000000..50142ec --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RecordMp4Req.java @@ -0,0 +1,48 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:32 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class RecordMp4Req extends BaseEventReq { + /** + * 文件名 + */ + @JSONField(name = "file_name") + private String fileName; + /** + * 文件绝对路径 + */ + @JSONField(name = "file_path") + private String filePath; + /** + * 文件大小,单位字节 + */ + @JSONField(name = "file_size") + private Integer fileSize; + /** + * 文件所在目录路径 + */ + private String folder; + /** + * 开始录制时间戳 + */ + @JSONField(name = "start_time") + private Integer startTime; + /** + * 录制时长,单位秒 + */ + @JSONField(name = "time_len") + private Integer timeLen; + /** + * http/rtsp/rtmp点播相对url路径 + */ + private String url; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthReq.java new file mode 100644 index 0000000..bcb51e5 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthReq.java @@ -0,0 +1,30 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:35 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class RtspAuthReq extends BaseEventReq { + /** + * 请求的密码是否必须为明文(base64鉴权需要明文密码) + */ + @JSONField(name = "must_no_encrypt") + private Boolean mustNoEncrypt; + /** + * rtsp播放鉴权加密realm + */ + private String realm; + /** + * 播放用户名 + */ + @JSONField(name = "user_name") + private String userName; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthResp.java new file mode 100644 index 0000000..2021549 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspAuthResp.java @@ -0,0 +1,30 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:35 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +public class RtspAuthResp extends BaseEventResp { + /** + * 用户密码 + */ + private String passwd; + /** + * 用户密码是否已加密 + */ + private Boolean encrypted; + + public static RtspAuthResp success() { + RtspAuthResp resp = new RtspAuthResp(); + resp.setSuccess(); + return resp.setEncrypted(false); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspRealmReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspRealmReq.java new file mode 100644 index 0000000..8d6547f --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/RtspRealmReq.java @@ -0,0 +1,14 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:33 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class RtspRealmReq extends BaseEventReq { +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ServerKeepaliveReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ServerKeepaliveReq.java new file mode 100644 index 0000000..d023328 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ServerKeepaliveReq.java @@ -0,0 +1,16 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import com.dite.znpt.monitor.media.zlm.dto.resp.StatisticResp; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:43 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ServerKeepaliveReq extends BaseEventReq { + private StatisticResp data; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ShellLoginReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ShellLoginReq.java new file mode 100644 index 0000000..86016eb --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/ShellLoginReq.java @@ -0,0 +1,24 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:37 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ShellLoginReq extends BaseEventReq { + /** + * 终端登录用户密码 + */ + private String passwd; + /** + * 终端登录用户名 + */ + @JSONField(name = "user_name") + private String userName; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamChangedReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamChangedReq.java new file mode 100644 index 0000000..cd90632 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamChangedReq.java @@ -0,0 +1,19 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import com.dite.znpt.monitor.media.zlm.dto.resp.MediaResp; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:38 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class StreamChangedReq extends MediaResp { + /** + * 流注册或注销 + */ + private Boolean regist; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNoneReaderReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNoneReaderReq.java new file mode 100644 index 0000000..65e34bf --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNoneReaderReq.java @@ -0,0 +1,14 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:40 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class StreamNoneReaderReq extends BaseEventReq { +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNotFoundReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNotFoundReq.java new file mode 100644 index 0000000..0ead9cc --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/event/StreamNotFoundReq.java @@ -0,0 +1,14 @@ +package com.dite.znpt.monitor.media.zlm.dto.event; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/30 9:42 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class StreamNotFoundReq extends BaseEventReq { +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/BaseReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/BaseReq.java new file mode 100644 index 0000000..445aef0 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/BaseReq.java @@ -0,0 +1,18 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/29 10:33 + * @Description: 请求响应基类 + */ +@Data +public class BaseReq implements Serializable { + /** + * api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数 + */ + private String secret; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/CloseStreamReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/CloseStreamReq.java new file mode 100644 index 0000000..2eb3044 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/CloseStreamReq.java @@ -0,0 +1,18 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 10:51 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class CloseStreamReq extends StreamReq { + /** + * 是否强制关闭(有人在观看是否还关闭) + */ + private Integer force; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/FFmpegSourceReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/FFmpegSourceReq.java new file mode 100644 index 0000000..54de5d0 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/FFmpegSourceReq.java @@ -0,0 +1,45 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 12:26 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class FFmpegSourceReq extends BaseReq { + /** + * FFmpeg拉流地址,支持任意协议或格式(只要FFmpeg支持即可) + */ + @JSONField(name = "src_url") + private String srcUrl; + /** + * FFmpeg rtmp推流地址,一般都是推给自己,例如rtmp://127.0.0.1/live/stream_form_ffmpeg + */ + @JSONField(name = "dst_url") + private String dstUrl; + /** + * FFmpeg推流成功超时时间 + */ + @JSONField(name = "timeout_ms") + private Integer timeoutMs; + /** + * 是否开启hls录制 + */ + @JSONField(name = "enable_hls") + private Integer enableHls; + /** + * 是否开启mp4录制 + */ + @JSONField(name = "enable_mp4") + private Integer enableMp4; + /** + * 配置文件中FFmpeg命令参数模板key(非内容),置空则采用默认模板:ffmpeg.cmd + */ + @JSONField(name = "ffmpeg_cmd_key") + private String ffmpegCmdKey; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetAllSessionReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetAllSessionReq.java new file mode 100644 index 0000000..dd01aa5 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetAllSessionReq.java @@ -0,0 +1,25 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 17:21 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class GetAllSessionReq extends BaseReq{ + /** + *筛选本机端口,例如筛选rtsp链接:554 + */ + @JSONField(name = "local_port") + private Integer localPort; + /** + *筛选客户端ip + */ + @JSONField(name = "peer_ip") + private String peerIp; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetMp4RecordFileReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetMp4RecordFileReq.java new file mode 100644 index 0000000..6b05d35 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/GetMp4RecordFileReq.java @@ -0,0 +1,24 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 13:04 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class GetMp4RecordFileReq extends StreamReq { + /** + * 流的录像日期,格式为2020-02-01,如果不是完整的日期,那么是搜索录像文件夹列表,否则搜索对应日期下的mp4文件列表 + */ + private String period; + /** + * 自定义搜索路径,与startRecord方法中的customized_path一样,默认为配置文件的路径 + */ + @JSONField(name = "customized_path") + private String customizedPath; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/IdReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/IdReq.java new file mode 100644 index 0000000..82c454d --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/IdReq.java @@ -0,0 +1,20 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 17:24 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +public class IdReq extends BaseReq { + /** + * 客户端唯一id,可以通过getAllSession接口获取 + */ + private Long id; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/KeyReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/KeyReq.java new file mode 100644 index 0000000..91ebcd9 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/KeyReq.java @@ -0,0 +1,20 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 17:30 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +public class KeyReq extends BaseReq { + /** + * addStreamProxy接口返回的key + */ + private String key; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RecordReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RecordReq.java new file mode 100644 index 0000000..4e3dc21 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RecordReq.java @@ -0,0 +1,29 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 13:11 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class RecordReq extends StreamReq { + /** + * 0为hls,1为mp4 + */ + private Integer type; + /** + * 录像保存目录 + */ + @JSONField(name = "customized_path") + private String customizedPath; + /** + * mp4录像切片时间大小,单位秒,置0则采用配置项 + */ + @JSONField(name = "max_second") + private Integer maxSecond; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RtpServerReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RtpServerReq.java new file mode 100644 index 0000000..a7003e7 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/RtpServerReq.java @@ -0,0 +1,45 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * @Author: huise23 + * @Date: 2022/8/29 13:17 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +@NoArgsConstructor +public class RtpServerReq extends BaseReq { + /** + * 接收端口,0则为随机端口 + */ + private Integer port; + /** + * 启用UDP监听的同时是否监听TCP端口 + */ + @JSONField(name = "enable_tcp") + private Integer enableTcp; + /** + * 该端口绑定的流ID,该端口只能创建这一个流(而不是根据ssrc创建多个) + */ + @JSONField(name = "stream_id") + private String streamId; + + public RtpServerReq(Integer port, Integer enableTcp, String streamId) { + this.port = port; + this.enableTcp = enableTcp; + this.streamId = streamId; + } + + public RtpServerReq(Integer enableTcp, String streamId) { + this(0, enableTcp, streamId); + } + + public RtpServerReq(String streamId) { + this(1, streamId); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SendRtpReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SendRtpReq.java new file mode 100644 index 0000000..20807df --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SendRtpReq.java @@ -0,0 +1,53 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 13:23 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SendRtpReq extends StreamReq { + /** + * 推流的rtp的ssrc,指定不同的ssrc可以同时推流到多个服务器 + */ + private String ssrc; + /** + * 目标ip或域名 + */ + @JSONField(name = "dst_url") + private String dstUrl; + /** + * 目标端口 + */ + @JSONField(name = "dst_port") + private Integer dstPort; + /** + * 是否为udp模式,否则为tcp模式 + */ + @JSONField(name = "is_udp") + private Integer isUdp; + /** + * 使用的本机端口,为0或不传时默认为随机端口 + */ + @JSONField(name = "src_port") + private Integer srcPort; + /** + * 使用的本机端口,为0或不传时默认为随机端口 + */ + private Integer pt; + /** + * 发送时,rtp的负载类型。为1时,负载为ps;为0时,为es;不传时默认为1 + */ + @JSONField(name = "use_ps") + private Integer usePs; + /** + * 当use_ps 为0时,有效。为1时,发送音频;为0时,发送视频;不传时默认为0 + */ + @JSONField(name = "only_audio") + private Integer onlyAudio; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SnapReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SnapReq.java new file mode 100644 index 0000000..548cfd7 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/SnapReq.java @@ -0,0 +1,29 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 13:14 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SnapReq extends BaseReq { + /** + * 需要截图的url,可以是本机的,也可以是远程主机的 + */ + private String url; + /** + * 截图失败超时时间,防止FFmpeg一直等待截图 + */ + @JSONField(name = "timeout_sec") + private Integer timeoutSec; + /** + * 截图的过期时间,该时间内产生的截图都会作为缓存返回 + */ + @JSONField(name = "expire_sec") + private Integer expireSec; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamIdReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamIdReq.java new file mode 100644 index 0000000..a53d2dc --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamIdReq.java @@ -0,0 +1,22 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 17:30 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +@AllArgsConstructor +public class StreamIdReq extends BaseReq { + /** + * RTP的ssrc,16进制字符串或者是流的id(openRtpServer接口指定) + */ + @JSONField(name = "stream_id") + private String streamId; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamProxyReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamProxyReq.java new file mode 100644 index 0000000..8481f0a --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamProxyReq.java @@ -0,0 +1,89 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 12:19 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class StreamProxyReq extends StreamReq { + /** + * 拉流地址,例如rtmp://live.hkstv.hk.lxdns.com/live/hks2 Y + */ + private String url; + /** + * 拉流重试次数,默认为-1无限重试 + */ + @JSONField(name = "retry_count") + private Integer retryCount; + /** + * rtsp拉流时,拉流方式,0:tcp,1:udp,2:组播 + */ + @JSONField(name = "rtp_type") + private Integer rtpType; + /** + * 拉流超时时间,单位秒,float类型 + */ + @JSONField(name = "timeout_sec") + private Integer timeoutSec; + /** + * 是否转换成hls协议 + */ + @JSONField(name = "enable_hls") + private Integer enableHls; + /** + * 是否允许mp4录制 + */ + @JSONField(name = "enable_mp4") + private Integer enableMp4; + /** + * 是否转rtsp协议 + */ + @JSONField(name = "enable_rtsp") + private Integer enableRtsp; + /** + * 是否转rtmp/flv协议 + */ + @JSONField(name = "enable_rtmp") + private Integer enableRtmp; + /** + * 是否转http-ts/ws-ts协议 + */ + @JSONField(name = "enable_ts") + private Integer enableTs; + /** + * 是否转http-fmp4/ws-fmp4协议 + */ + @JSONField(name = "enable_fmp4") + private Integer enableFmp4; + /** + * 转协议时是否开启音频 + */ + @JSONField(name = "enable_audio") + private Integer enableAudio; + /** + * 转协议时,无音频是否添加静音aac音频 + */ + @JSONField(name = "add_mute_audio") + private Integer addMuteAudio; + /** + * mp4录制文件保存根目录,置空使用默认 + */ + @JSONField(name = "mp4_save_path") + private String mp4SavePath; + /** + * mp4录制切片大小,单位秒 + */ + @JSONField(name = "mp4_max_second") + private Integer mp4MaxSecond; + /** + * hls文件保存保存根目录,置空使用默认 + */ + @JSONField(name = "hls_save_path") + private String hlsSavePath; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamPusherProxyReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamPusherProxyReq.java new file mode 100644 index 0000000..6729ba1 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamPusherProxyReq.java @@ -0,0 +1,35 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 13:50 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class StreamPusherProxyReq extends StreamReq { + /** + * 目标转推url,带参数需要自行url转义 + */ + @JSONField(name = "dst_url") + private String dstUrl; + /** + * 转推失败重试次数,默认无限重试 + */ + @JSONField(name = "retry_count") + private Integer retryCount; + /** + * rtsp拉流时,拉流方式,0:tcp,1:udp,2:组播 + */ + @JSONField(name = "rtp_type") + private Integer rtpType; + /** + * 拉流超时时间,单位秒,float类型 + */ + @JSONField(name = "timeout_sec") + private Float timeoutSec; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamReq.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamReq.java new file mode 100644 index 0000000..404369a --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/req/StreamReq.java @@ -0,0 +1,32 @@ +package com.dite.znpt.monitor.media.zlm.dto.req; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * @Author: huise23 + * @Date: 2022/8/29 13:05 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +public class StreamReq extends BaseReq { + /** + * 协议,例如 rtsp或rtmp + */ + private String schema; + /** + * 筛选虚拟主机,例如__defaultVhost__ + */ + private String vhost; + /** + * 筛选应用名,例如 live + */ + private String app; + /** + * 筛选流id,例如 test + */ + private String stream; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/BaseResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/BaseResp.java new file mode 100644 index 0000000..d6cfa48 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/BaseResp.java @@ -0,0 +1,64 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import lombok.Data; + +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/29 10:33 + * @Description: 请求响应基类 + */ +@Data +public class BaseResp { + /** + * code == 0时代表完全成功 + */ + private Integer code; + /** + * 失败提示 + */ + private String msg; + /** + * 失败具体原因 + */ + private String result; + /** + * 返回数据 + */ + private String data; + /** + * 配置项变更个数 + */ + private Integer changed; + /** + * false:未录制,true:正在录制 + */ + private Boolean status; + /** + * 接收端口,方便获取随机端口号 + */ + private Integer port; + /** + * 是否找到记录并关闭 + */ + private Integer hit; + + public String getMsg() { + return StrUtil.format("{}:{}", code, msg); + } + + public Boolean isSuccess() { + return code == 0; + } + + public T getData(Class clazz) { + return JSON.parseObject(data, clazz); + } + + public List getList(Class clazz) { + return JSON.parseArray(data, clazz); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/CloseStreamResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/CloseStreamResp.java new file mode 100644 index 0000000..aa0bf8e --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/CloseStreamResp.java @@ -0,0 +1,25 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 11:59 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class CloseStreamResp extends BaseResp { + /** + * 筛选命中的流个数 + */ + @JSONField(name = "count_hit") + private Integer countHit; + /** + * 被关闭的流个数,可能小于count_hit + */ + @JSONField(name = "count_closed") + private Integer countClosed; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/MediaResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/MediaResp.java new file mode 100644 index 0000000..942b099 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/MediaResp.java @@ -0,0 +1,61 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import com.dite.znpt.monitor.media.zlm.dto.event.BaseEventReq; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/29 11:18 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class MediaResp extends BaseEventReq implements Serializable { + private static final long serialVersionUID = -8710934021370904914L; + + /** + * 本协议观看人数 + */ + private Integer readerCount; + /** + * 观看总人数,包括hls/rtsp/rtmp/http-flv/ws-flv + */ + private Integer totalReaderCount; + /** + * 客户端和服务器网络信息,可能为null类型 + */ + private OriginSock originSock; + /** + * 产生源类型,包括 unknown = 0, rtmp_push = 1, rtsp_push = 2, rtp_push = 3, pull = 4, ffmpeg_pull = 5, mp4_vod = 6, device_chn = 7 + */ + private Integer originType; + /** + * 产生源类型 + */ + private String originTypeStr; + /** + * 产生源的url + */ + private String originUrl; + /** + * GMT unix系统时间戳,单位秒 + */ + private Long createStamp; + /** + * 存活时间,单位秒 + */ + private Long aliveSecond; + /** + * 数据产生速度,单位byte/s + */ + private Long bytesSpeed; + /** + * 音视频轨道 + */ + private List tracks; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/Mp4RecordFileResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/Mp4RecordFileResp.java new file mode 100644 index 0000000..f39ea4f --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/Mp4RecordFileResp.java @@ -0,0 +1,23 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import lombok.Data; + +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/29 13:08 + * @Description: + */ +@Data +public class Mp4RecordFileResp { + /** + * 文件列表 + */ + private List paths; + /** + * 根路径 + */ + private String rootPath; +} + diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/OriginSock.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/OriginSock.java new file mode 100644 index 0000000..5ca64f3 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/OriginSock.java @@ -0,0 +1,26 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/29 11:20 + * @Description: + */ +@Data +public class OriginSock implements Serializable { + private static final long serialVersionUID = 5628294142872524316L; + + private String identifier; + @JSONField(name = "local_ip") + private String localIp; + @JSONField(name = "local_port") + private Integer localPort; + @JSONField(name = "peer_ip") + private String peerIp; + @JSONField(name = "peer_port") + private Integer peerPort; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/RtpInfoResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/RtpInfoResp.java new file mode 100644 index 0000000..b8654a5 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/RtpInfoResp.java @@ -0,0 +1,39 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @Author: huise23 + * @Date: 2022/8/29 12:32 + * @Description: + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class RtpInfoResp extends BaseResp { + /** + * 是否存在 + */ + private Boolean exist; + /** + * 推流客户端ip + */ + @JSONField(name = "peer_ip") + private String peerIp; + /** + * 客户端端口号 + */ + @JSONField(name = "peer_port") + private Integer peerPort; + /** + * 本地监听的网卡ip + */ + @JSONField(name = "local_ip") + private String localIp; + /** + * 本地监听端口号 + */ + @JSONField(name = "local_port") + private Integer localPort; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/RtpServerResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/RtpServerResp.java new file mode 100644 index 0000000..1a5267f --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/RtpServerResp.java @@ -0,0 +1,22 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +/** + * @Author: huise23 + * @Date: 2022/8/29 13:22 + * @Description: + */ +@Data +public class RtpServerResp { + /** + * 绑定的端口号 + */ + private Integer port; + /** + * 绑定的流ID + */ + @JSONField(name = "stream_id") + private String streamId; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/SessionResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/SessionResp.java new file mode 100644 index 0000000..eaf6119 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/SessionResp.java @@ -0,0 +1,41 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +/** + * @Author: huise23 + * @Date: 2022/8/29 12:04 + * @Description: + */ +@Data +public class SessionResp { + /** + * 该tcp链接唯一id + */ + private Long id; + /** + * 本机网卡ip + */ + @JSONField(name = "local_ip") + private String localIp; + /** + * 本机端口号 (这是个rtmp播放器或推流器) + */ + @JSONField(name = "local_port") + private Integer localPort; + /** + * 客户端ip + */ + @JSONField(name = "peer_ip") + private String peerIp; + /** + * 客户端端口号 + */ + @JSONField(name = "peer_port") + private Integer peerPort; + /** + * 客户端TCPSession typeid + */ + private String typeid; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/StatisticResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/StatisticResp.java new file mode 100644 index 0000000..f464784 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/StatisticResp.java @@ -0,0 +1,28 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import lombok.Data; + +/** + * @Author: huise23 + * @Date: 2022/8/29 13:47 + * @Description: + */ +@Data +public class StatisticResp { + private Integer Buffer; + private Integer BufferLikeString; + private Integer BufferList; + private Integer BufferRaw; + private Integer Frame; + private Integer FrameImp; + private Integer MediaSource; + private Integer MultiMediaSourceMuxer; + private Integer RtmpPacket; + private Integer RtpPacket; + private Integer Socket; + private Integer TcpClient; + private Integer TcpServer; + private Integer TcpSession; + private Integer UdpServer; + private Integer UdpSession; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/ThreadsLoadResp.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/ThreadsLoadResp.java new file mode 100644 index 0000000..9c0257e --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/ThreadsLoadResp.java @@ -0,0 +1,22 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import lombok.Data; + + + +/** + * @Author: huise23 + * @Date: 2022/8/29 10:40 + * @Description: 各epoll(或select)线程负载以及延时 + */ +@Data +public class ThreadsLoadResp { + /** + * 该线程延时 + */ + private Integer delay; + /** + * 该线程负载 + */ + private Integer load; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/Track.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/Track.java new file mode 100644 index 0000000..e7307aa --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/dto/resp/Track.java @@ -0,0 +1,61 @@ +package com.dite.znpt.monitor.media.zlm.dto.resp; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +import java.io.Serializable; + +/** + * @Author: huise23 + * @Date: 2022/8/29 11:25 + * @Description: + */ +@Data +public class Track implements Serializable { + private static final long serialVersionUID = 5317048895056912057L; + /** + * 音频通道数 + */ + private Integer channels; + /** + * H264 = 0, H265 = 1, AAC = 2, G711A = 3, G711U = 4 + */ + @JSONField(name = "codec_id") + private Integer codecId; + /** + * 编码类型名称 + */ + @JSONField(name = "codec_id_name") + private String codecIdName; + /** + * Video = 0, Audio = 1 + */ + @JSONField(name = "codec_type") + private Integer codecType; + /** + * 轨道是否准备就绪 + */ + private Boolean ready; + /** + * 音频采样位数 + */ + @JSONField(name = "sample_bit") + private Integer sampleBit; + /** + * 音频采样率 + */ + @JSONField(name = "sample_rate") + private Integer sampleRate; + /** + * 视频fps + */ + private Integer fps; + /** + * 视频高 + */ + private Integer height; + /** + * 视频宽 + */ + private Integer width; +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/enums/MediaFormatType.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/enums/MediaFormatType.java new file mode 100644 index 0000000..215715c --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/enums/MediaFormatType.java @@ -0,0 +1,43 @@ +package com.dite.znpt.monitor.media.zlm.enums; + +import lombok.Getter; + +/** + * @Author: huise23 + * @Date: 2022/8/31 10:29 + * @Description: + */ +@Getter +public enum MediaFormatType { + /** + * FLV + */ + flv(".live.flv"), + /** + * MP4 + */ + mp4(".live.mp4"), + /** + * HLS + */ + hls("/hls.m3u8"), + /** + * RTS + */ + rts(".live.ts"); + + private final String suffix; + + MediaFormatType(String suffix) { + this.suffix = suffix; + } + + public static String getSuffix(String name) { + for (MediaFormatType value : MediaFormatType.values()) { + if (value.name().equals(name)) { + return value.getSuffix(); + } + } + return ""; + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmApiImpl.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmApiImpl.java new file mode 100644 index 0000000..f1e20ce --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmApiImpl.java @@ -0,0 +1,223 @@ +package com.dite.znpt.monitor.media.zlm.impl; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; +import com.dite.znpt.monitor.media.zlm.ZlmApi; +import com.dite.znpt.monitor.media.zlm.dto.ServerConfig; +import com.dite.znpt.monitor.media.zlm.dto.ServerInfo; +import com.dite.znpt.monitor.media.zlm.dto.req.*; +import com.dite.znpt.monitor.media.zlm.dto.resp.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/29 10:22 + * @Description: + */ +@Service +@Slf4j +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ZlmApiImpl implements ZlmApi { + private final HttpServletResponse response; + + public V post(ServerInfo server, String url, T req, Class clazz) { + url = StrUtil.format("http://{}:{}/index/api/{}", server.getApiHost(), server.getApiPort(), url); + log.info("ZLM:" + url); + log.info("REQ:" + req); + req.setSecret(server.getSecretKey()); + String respStr = HttpUtil.post(url, JSON.toJSONString(req)); + V resp = JSON.parseObject(respStr, clazz); + if (resp.isSuccess()) { + return resp; + } + throw new RuntimeException(resp.getMsg()); + } + + public BaseResp post(ServerInfo server, String url, T req) { + return post(server, url, req, BaseResp.class); + } + + public V post(ServerInfo server, String url, Class clazz) { + return post(server, url, new BaseReq(), clazz); + } + + public BaseResp post(ServerInfo server, String url) { + return post(server, url, new BaseReq()); + } + + @Override + public List getApiList(ServerInfo server) { + return post(server, "getApiList").getList(String.class); + } + + @Override + public List getThreadsLoad(ServerInfo server) { + return post(server, "getThreadsLoad").getList(ThreadsLoadResp.class); + } + + @Override + public List getWorkThreadsLoad(ServerInfo server) { + return post(server, "getWorkThreadsLoad").getList(ThreadsLoadResp.class); + } + + @Override + public List getServerConfig(ServerInfo server) { + return post(server, "getServerConfig").getList(ServerConfig.class); + } + +// public static void main(String[] args) { +// ZlmApi zlmApi = new ZlmApiImpl(null); +// ServerInfo server = new ServerInfo("10.12.1.41", 8819, "035c73f7-bb6b-4889-a715-d9eb2d1925cc"); +// List config = zlmApi.getMediaList(server,new StreamReq()); +// System.out.println(JSONUtil.toJsonPrettyStr(config)); +// for (MediaResp mediaResp : config) { +// zlmApi.closeRtpServer(server,mediaResp.getStream() ); +// } +// } + + @Override + public Integer setServerConfig(ServerInfo server, ServerConfig config) { + BaseResp req = post(server, "setServerConfig", config); + return req.getChanged(); + } + + @Override + public Boolean restartServer(ServerInfo server) { + BaseResp req = post(server, "restartServer"); + return req.isSuccess(); + } + + @Override + public List getMediaList(ServerInfo server, StreamReq req) { + return post(server, "getMediaList", req).getList(MediaResp.class); + } + + @Override + public CloseStreamResp closeStreams(ServerInfo server, CloseStreamReq req) { + return post(server, "close_streams", req, CloseStreamResp.class); + } + + @Override + public List getAllSession(ServerInfo server, GetAllSessionReq req) { + return post(server, "getAllSession", req).getList(SessionResp.class); + } + + @Override + public Boolean kickSession(ServerInfo server, Long id) { + return post(server, "kick_session", new IdReq(id)).isSuccess(); + } + + @Override + public Integer kickSession(ServerInfo server, GetAllSessionReq req) { + return post(server, "kick_sessions", req, CloseStreamResp.class).getCountHit(); + } + + @Override + public String addStreamProxy(ServerInfo server, StreamProxyReq req) { + return post(server, "addStreamProxy", req).getData(Dict.class).getStr("key"); + } + + @Override + public Boolean delStreamProxy(ServerInfo server, String key) { + return post(server, "delStreamProxy", new KeyReq(key)).getData(Dict.class).getBool("flag"); + } + + @Override + public String addFfMpegSource(ServerInfo server, FFmpegSourceReq req) { + return post(server, "addFFmpegSource", req).getData(Dict.class).getStr("key"); + } + + @Override + public Boolean delFfMpegSource(ServerInfo server, String key) { + return post(server, "delFFmpegSource", new KeyReq(key)).getData(Dict.class).getBool("flag"); + } + + @Override + public RtpInfoResp getRtpInfo(ServerInfo server, String streamId) { + return post(server, "getRtpInfo", new StreamIdReq(streamId), RtpInfoResp.class); + } + + @Override + public Mp4RecordFileResp getMp4RecordFile(ServerInfo server, GetMp4RecordFileReq req) { + return post(server, "getMp4RecordFile", req).getData(Mp4RecordFileResp.class); + } + + @Override + public Boolean startRecord(ServerInfo server, RecordReq req) { + return BooleanUtil.toBoolean(post(server, "startRecord", req).getResult()); + } + + @Override + public Boolean stopRecord(ServerInfo server, RecordReq req) { + return BooleanUtil.toBoolean(post(server, "stopRecord", req).getResult()); + } + + @Override + public Boolean isRecording(ServerInfo server, RecordReq req) { + return post(server, "isRecording", req).getStatus(); + } + + @Override + public void getSnap(ServerInfo server, SnapReq req) throws IOException { + String url = StrUtil.format("http://{}:{}/index/api/getSnap", server.getApiHost(), server.getApiPort()); + req.setSecret(server.getSecretKey()); + url += "?" + HttpUtil.toParams(JSON.parseObject(JSON.toJSONString(req))); + HttpUtil.download(url, response.getOutputStream(), true); + } + + @Override + public Integer openRtpServer(ServerInfo server, RtpServerReq req) { + return post(server, "openRtpServer", req).getPort(); + } + + @Override + public Boolean closeRtpServer(ServerInfo server, String streamId) { + BaseResp closeRtpServer = post(server, "closeRtpServer", new StreamIdReq(streamId)); + return closeRtpServer.getHit() == 1; + } + + @Override + public List listRtpServer(ServerInfo server) { + return post(server, "listRtpServer").getList(RtpServerResp.class); + } + + @Override + public Integer startSendRtp(ServerInfo server, SendRtpReq req) { + return post(server, "startSendRtp", req, RtpInfoResp.class).getLocalPort(); + } + + @Override + public Integer startSendRtpPassive(ServerInfo server, SendRtpReq req) { + return post(server, "startSendRtpPassive", req, RtpInfoResp.class).getLocalPort(); + } + + @Override + public Boolean stopSendRtp(ServerInfo server, SendRtpReq req) { + return post(server, "stopSendRtp", req).isSuccess(); + } + + @Override + public StatisticResp getStatistic(ServerInfo server) { + return post(server, "getStatistic").getData(StatisticResp.class); + } + + @Override + public String addStreamPusherProxy(ServerInfo server, StreamPusherProxyReq req) { + return post(server, "addStreamPusherProxy", req).getData(Dict.class).getStr("key"); + } + + @Override + public Boolean delStreamPusherProxy(ServerInfo server, String key) { + return post(server, "delStreamPusherProxy", new KeyReq(key)).getData(Dict.class).getBool("flag"); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmHookService.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmHookService.java new file mode 100644 index 0000000..2bc7a2e --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmHookService.java @@ -0,0 +1,132 @@ +package com.dite.znpt.monitor.media.zlm.impl; + +import com.dite.znpt.monitor.media.zlm.ZlmService; +import com.dite.znpt.monitor.media.zlm.dto.ServerConfig; +import com.dite.znpt.monitor.media.zlm.dto.event.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @Author: huise23 + * @Date: 2022/8/30 10:32 + * @Description: + */ +@Service +@Slf4j +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class ZlmHookService { + private final ZlmService zlmService; + + /** + * TODO 流量统计事件,播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件, + * 阈值通过配置文件general.flowThreshold配置;此事件对回复不敏感。 + */ + public BaseEventResp onFlowReport(FlowReportReq req) { + log.debug("[ZLM] onFlowReport : {}", req); + return BaseEventResp.success(); + } + + /** + * TODO 访问http文件服务器上hls之外的文件时触发。 + */ + public HttpAccessResp onHttpAccess(HttpAccessReq req) { + log.debug("[ZLM] onHttpAccess : {}", req); + return HttpAccessResp.success(); + } + + /** + * TODO 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件; + * 如果流不存在,那么先触发on_play事件然后触发on_stream_not_found事件。 + * 播放rtsp流时,如果该流启动了rtsp专属鉴权(on_rtsp_realm)那么将不再触发on_play事件。 + */ + public BaseEventResp onPlay(PlayReq req) { + log.debug("[ZLM] onPlay : {}", req); + return BaseEventResp.success(); + } + + /** + * TODO rtsp/rtmp/rtp推流鉴权事件。 + */ + public PublishResp onPublish(PublishReq req) { + System.out.println("[ZLM] 接收到推流信息"); + log.info("[ZLM] onPublish : {}", req); + return PublishResp.success(); + } + + /** + * TODO 录制mp4完成后通知事件;此事件对回复不敏感。 + */ + public BaseEventResp onRecordMp4(RecordMp4Req req) { + log.debug("[ZLM] onRecordMp4 : {}", req); + return BaseEventResp.success(); + } + + /** + * TODO 该rtsp流是否开启rtsp专用方式的鉴权事件,开启后才会触发on_rtsp_auth事件。 + * 需要指出的是rtsp也支持url参数鉴权,它支持两种方式鉴权。 + */ + public BaseEventResp onRtspRealm(RtspRealmReq req) { + log.debug("[ZLM] onRtspRealm : {}", req); + return BaseEventResp.success(); + } + + /** + * TODO rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。 + */ + public RtspAuthResp onRtspAuth(RtspAuthReq req) { + log.debug("[ZLM] onRtspAuth : {}", req); + return RtspAuthResp.success(); + } + + /** + * TODO shell登录鉴权,ZLMediaKit提供简单的telnet调试方式 + * 使用telnet 127.0.0.1 9000能进入MediaServer进程的shell界面。 + */ + public BaseEventResp onShellLogin(ShellLoginReq req) { + log.debug("[ZLM] onShellLogin : {}", req); + return BaseEventResp.success(); + } + + /** + * TODO rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 + */ + public BaseEventResp onStreamChanged(StreamChangedReq req) { + log.info("[ZLM] onStreamChanged 流信息 : {}", req); + return BaseEventResp.success(); + } + + /** + * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。 + */ + public BaseEventResp onStreamNoneReader(StreamNoneReaderReq req) { + log.debug("[ZLM] onStreamNoneReader : {}", req); + zlmService.display(req.getMediaServerId(), req.getStream()); + return BaseEventResp.success().setClose(false); + } + + /** + * TODO 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。 + */ + public BaseEventResp onStreamNotFound(StreamNotFoundReq req) { + log.debug("[ZLM] onStreamNotFound : {}", req); + return BaseEventResp.success(); + } + + /** + * TODO 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。 + */ + public BaseEventResp onServerStarted(ServerConfig req) { + log.debug("[ZLM] onServerStarted : {}", req); + return BaseEventResp.success(); + } + + /** + * TODO 服务器定时上报时间,上报间隔可配置,默认10s上报一次 + */ + public BaseEventResp onServerKeepalive(ServerKeepaliveReq req) { + log.debug("[ZLM] onServerKeepalive : {}", req); + return BaseEventResp.success(); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmServiceImpl.java b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmServiceImpl.java new file mode 100644 index 0000000..cf5a44b --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/media/zlm/impl/ZlmServiceImpl.java @@ -0,0 +1,194 @@ +package com.dite.znpt.monitor.media.zlm.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.dite.znpt.exception.ServiceException; +import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat; +import com.dite.znpt.monitor.media.zlm.ZlmApi; +import com.dite.znpt.monitor.media.zlm.ZlmService; +import com.dite.znpt.monitor.media.zlm.cache.MediaServerCache; +import com.dite.znpt.monitor.media.zlm.cache.MediaServerChannelCache; +import com.dite.znpt.monitor.media.zlm.config.StreamMediaServerConfig; +import com.dite.znpt.monitor.media.zlm.dto.MediaItem; +import com.dite.znpt.monitor.media.zlm.dto.ServerConfig; +import com.dite.znpt.monitor.media.zlm.dto.ServerItem; +import com.dite.znpt.monitor.media.zlm.dto.req.RtpServerReq; +import com.dite.znpt.monitor.media.zlm.dto.req.StreamReq; +import com.dite.znpt.monitor.media.zlm.dto.resp.MediaResp; +import com.dite.znpt.monitor.media.zlm.dto.resp.RtpInfoResp; +import com.dite.znpt.monitor.service.StreamMediaFormatService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.env.Environment; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static com.dite.znpt.monitor.config.MediaFormatConfig.streamMediaFormatList; + +/** + * @Author: huise23 + * @Date: 2022/8/30 10:40 + * @Description: + */ +@Service +@Slf4j +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +@EnableScheduling +public class ZlmServiceImpl implements ZlmService, ApplicationRunner { + private final StreamMediaFormatService formatService; + private final ZlmApi zlmApi; + private final MediaServerCache serverCache; + private final MediaServerChannelCache channelCache; + private final Environment environment; + private final StreamMediaServerConfig zlmConfig; + + @Value("${spring.profiles.active:dev}") + private String activeProfile; + @Value("${server.host:''}") + private String serverHost; + @Value("${server.port:''}") + private String serverPort; + + private String profile = "dev"; + + /** + * 系统启动加载服务器 + */ + @Override + public void run(ApplicationArguments args) throws Exception { + //TODO 媒体格式注入 + List formatList = streamMediaFormatList(); + ServerItem item = new ServerItem(zlmConfig, formatList); + try { + // 开发环境不去修改多媒体配置 +// if (!StrUtil.equalsIgnoreCase(activeProfile, profile)) { +// String host = StrUtil.blankToDefault(serverHost, IpUtils.getHostIp()); +// String port = StrUtil.blankToDefault(serverPort, environment.getProperty("server.port")); +// ServerConfig config = new ServerConfig(); +// config.refreshHook(host, port, zlmConfig); +// zlmApi.setServerConfig(item.getServer(), config); +// } + List configList = zlmApi.getServerConfig(item.getServer()); + item.setConfig(configList.get(0)); + item.setStatus(true); + } catch (Exception e) { + log.error("流服务器节点配置错误:", e); + item.setStatus(false); + } + //初始化参数 + serverCache.putLoad(item); + } + +// @Scheduled(cron = "*/10 * * * * ?") + public void heartbeat1() { + log.debug("开始心跳检查..."); + ServerItem item = serverCache.getLoad(); + // 获取服务器流信息 + try { + List media = zlmApi.getMediaList(item.getServer(), new StreamReq()); + item.setMedia(media); + item.setStatus(true); + } catch (Exception e) { + log.error("流服务器节点丢失:", e); + item.setStatus(false); + } + serverCache.putLoad(item); + } + + @Override + public MediaItem play(String deviceCode, String channelCode) { + // 获取通道信息 + MediaItem media; + if (channelCache.has(deviceCode, channelCode)) { + media = channelCache.get(deviceCode, channelCode); + } else { + media = new MediaItem().setDeviceCode(deviceCode).setChannelCode(channelCode); + } + media.setIsCache(true); + // 检查节点是否正常 + if (!checkServer(media.getConfigId())) { + // 获取负载最小的节点 + ServerItem server = serverCache.getLoad(); + if (!server.getStatus()) { + throw new ServiceException("无正常的流媒体节点可用!"); + } + media.setSsrc(server.genPlaySsrc(channelCode)); + media.setConfigId(server.getConfigId()); + media.setConfig(server.getConfig()); + media.setServer(server.getServer()); + media.setFormatList(server.getFormatList()); + media.setIsCache(false); + serverCache.putLoad(server); + } + // 检查播放流是否正常 + if (StrUtil.isNotBlank(media.getStreamId())) { + RtpInfoResp rtp = zlmApi.getRtpInfo(media.getServer(), media.getStreamId()); + if (rtp.getExist()) { + media.setRtp(rtp); + media.setIsCache(true); + return media; + } + } + // 不正常,需要重新拉流 + Integer rtpPort = zlmApi.openRtpServer(media.getServer(), new RtpServerReq(media.getSsrc())); + media.setRtpPort(rtpPort); + media.setStreamId(media.getSsrc()); + // 缓存链接信息 + channelCache.put(deviceCode, channelCode, media); + media.setIsCache(false); + return media; + } + + @Override + public void release(String deviceCode, String channelCode) { + // 获取通道信息 + MediaItem media = channelCache.get(deviceCode, channelCode); + release(media); + } + + @Override + public void release(MediaItem media) { + if (ObjectUtil.isNull(media)) { + return; + } + try { + List list = zlmApi.getMediaList(media.getServer(), new StreamReq().setStream(media.getStreamId())); + if (!CollUtil.isEmpty(list) && list.get(0).getTotalReaderCount() > 0) { + // 当前还有观看者 不释放资源 + return; + } + zlmApi.closeRtpServer(media.getServer(), media.getStreamId()); + } catch (Exception e) { + log.error("流媒体服务器调用失败:", e); + } + // 释放SSRC句柄 + serverCache.releaseSsrc(media.getSsrc()); + // 删除链接信息 + channelCache.delete(media); + } + + @Override + public void display(String mediaServerId, String streamId) { + // 获取通道信息 + MediaItem media = channelCache.getByStream(mediaServerId, streamId); + release(media); + } + + /** + * 检查节点是否正常 + */ + private boolean checkServer(String configId) { + if (ObjectUtil.isNull(configId)) { + return false; + } + ServerItem item = serverCache.getLoad(); + return ObjectUtil.isNotNull(item) && item.getStatus(); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/service/DeviceVideoChannelService.java b/sip/src/main/java/com/dite/znpt/monitor/service/DeviceVideoChannelService.java new file mode 100644 index 0000000..6632114 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/service/DeviceVideoChannelService.java @@ -0,0 +1,120 @@ +package com.dite.znpt.monitor.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.dite.znpt.domain.PageResult; +import com.dite.znpt.domain.Result; +import com.dite.znpt.monitor.domain.entity.DeviceVideoChannelEntity; +import com.dite.znpt.monitor.domain.req.VideoInfoReq; +import com.dite.znpt.monitor.domain.resp.VideoInfoResp; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelEditReq; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelListResp; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelResp; +import com.dite.znpt.monitor.domain.vo.video.VideoPayResp; + +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:10 + * @Description: + */ +public interface DeviceVideoChannelService extends IService { + /** + * 查询视频通道列表 + * + * @param videoId 视频id + * @param keyword 查询条件 + * @return {@link PageResult < DeviceVideoChannelListResp >} + */ + PageResult selectDeviceVideoChannel(Long videoId, String keyword); + + /** + * 查询所有视频通道列表 + * + * @param keyword 查询条件 + * @return {@link PageResult< DeviceVideoChannelListResp>} + */ + PageResult selectAllDeviceVideoChannel(String keyword); + + /** + * 查询视频通道详情 + * + * @param channelCode + * @return {@link DeviceVideoChannelResp} + */ + DeviceVideoChannelResp getDeviceVideoChannelDetail(String channelCode); + + /** + * 编辑视频设备通道 + * + * @param channelId + * @param req + * @return + */ + void editDeviceVideoChannel(Long channelId, DeviceVideoChannelEditReq req); + + /** + * 根据通道id删除通道信息 + * + * @param channelIds + * @return {@link boolean} + */ + Result removeByChannelIds(List channelIds); + + /** + * 根据通道编码查询通道信息 + * + * @param channelCode 通道编码 + * @return 通道信息 + */ + DeviceVideoChannelEntity getByCode(String channelCode); + + /** + * 根据设备id查询设备通道 + * @param videoId 设备id + * @return {@link List< DeviceVideoChannelEntity>} + */ + List selectDeviceVideoChannelByVideoCode(Long videoId); + + /** + * 播放直播视频 + * + * @param channelCode + * @return + */ + VideoPayResp play(String channelCode); + + /** + * 是否在线 + * + * @param channelCode + * @return + */ + boolean isOnline(String channelCode); + + /** + * 停止播放直播 + * + * @param channelCode + * @return + */ + void stop(String channelCode); + + /** + * 下线视频设备下的所有通道 + * @param videoId + * @return + */ + void offlineByVideoId(Long videoId); + + /** + * 查询通道及视频信息 + * + * @param videoInfoReq 查询参数 + * @return {@link VideoInfoResp } + * @author huise23 + * @since 2024-12-03 13:54:52 + */ + List selectVideoInfoList(VideoInfoReq videoInfoReq); + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/service/DeviceVideoService.java b/sip/src/main/java/com/dite/znpt/monitor/service/DeviceVideoService.java new file mode 100644 index 0000000..79ac330 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/service/DeviceVideoService.java @@ -0,0 +1,104 @@ +package com.dite.znpt.monitor.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.dite.znpt.domain.PageResult; +import com.dite.znpt.domain.Result; +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoEditReq; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoListResp; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoNumResp; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:09 + * @Description: + */ +public interface DeviceVideoService extends IService { + + /** + * 查询视频设备列表 + * + * @param status 状态 + * @param keyword 设备编码或者设备名称 + * @return {@link PageResult< DeviceVideoListResp>} + */ + PageResult selectDeviceVideoList(String status, String keyword, String hostAddress); + + /** + * 查询视频设备数量 + * + * @return {@link DeviceVideoNumResp} + */ + DeviceVideoNumResp countDeviceVideoNum(); + + /** + * 编辑视频设备信息 + * + * @param videoId 视频设备id + * @param req 视频设备信息 + * @return + */ + void editDeviceVideo(Long videoId, DeviceVideoEditReq req); + + /** + * 删除视频设备 + * + * @param videoId 视频设备id + * @return + */ + Result removeByVideoId(Long videoId); + + /** + * 根据端口和host查询设备 + * + * @param host 地址 + * @param port 端口 + * @return 设备 + */ + DeviceVideoEntity getDeviceByHostAndPort(String host, int port); + + /** + * 设备离线 + * + * @param videoCode videoCode + */ + void offline(String videoCode); + + /** + * 上线设备 + * @param entity + * @return + */ + void online(DeviceVideoEntity entity); + + /** + * 判断是否注册已经失效 + * @param entity 设备信息 + * @return {@link boolean} + */ + boolean expire(DeviceVideoEntity entity); + + /** + * 根据 videoCode 获取设备 + * + * @param videoCode videoCode + * @return 设备 + */ + DeviceVideoEntity getByCode(String videoCode); + + /** + * 根据 videoCode 获取设备 + * + * @param channelCode + * @return 设备 + */ + DeviceVideoEntity getByChannelCode(String channelCode); + + /** + * 查询设备状态 + * @param videoId + * @return + */ + void queryDeviceStatus(Long videoId); + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/service/IpConfigService.java b/sip/src/main/java/com/dite/znpt/monitor/service/IpConfigService.java new file mode 100644 index 0000000..11a53d1 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/service/IpConfigService.java @@ -0,0 +1,27 @@ +package com.dite.znpt.monitor.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.dite.znpt.monitor.domain.entity.IpConfigEntity; +import com.dite.znpt.monitor.domain.req.MonitorConfigAddReq; + +import java.util.List; + +/** + * @Date: 2023/09/05 16:39 + * @Description: 监控设备IP配置表服务接口 + */ +public interface IpConfigService extends IService { + + /** + * 新增ip配置 + * @param req + */ + void configAdd(MonitorConfigAddReq req); + + /** + * 查询ip配置 + * @return + */ + List configList(); +} + diff --git a/sip/src/main/java/com/dite/znpt/monitor/service/StreamMediaFormatService.java b/sip/src/main/java/com/dite/znpt/monitor/service/StreamMediaFormatService.java new file mode 100644 index 0000000..fa959bc --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/service/StreamMediaFormatService.java @@ -0,0 +1,12 @@ +package com.dite.znpt.monitor.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat; + +/** + * @Author: huise23 + * @Date: 2022/8/11 14:58 + * @Description: + */ +public interface StreamMediaFormatService extends IService { +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/service/impl/DeviceVideoChannelServiceImpl.java b/sip/src/main/java/com/dite/znpt/monitor/service/impl/DeviceVideoChannelServiceImpl.java new file mode 100644 index 0000000..931a754 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/service/impl/DeviceVideoChannelServiceImpl.java @@ -0,0 +1,262 @@ +package com.dite.znpt.monitor.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.dite.znpt.domain.PageResult; +import com.dite.znpt.domain.Result; +import com.dite.znpt.exception.ServiceException; +import com.dite.znpt.monitor.constant.IotRespMessage; +import com.dite.znpt.monitor.constant.dict.CameraType; +import com.dite.znpt.monitor.constant.dict.DeviceStatus; +import com.dite.znpt.monitor.constant.dict.YesOrNo; +import com.dite.znpt.monitor.domain.entity.DeviceVideoChannelEntity; +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.domain.req.VideoInfoReq; +import com.dite.znpt.monitor.domain.resp.VideoInfoResp; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelEditReq; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelListResp; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelResp; +import com.dite.znpt.monitor.domain.vo.video.VideoPayResp; +import com.dite.znpt.monitor.mapper.DeviceVideoChannelMapper; +import com.dite.znpt.monitor.media.zlm.ZlmApi; +import com.dite.znpt.monitor.media.zlm.ZlmService; +import com.dite.znpt.monitor.media.zlm.cache.MediaServerChannelCache; +import com.dite.znpt.monitor.media.zlm.dto.MediaItem; +import com.dite.znpt.monitor.media.zlm.dto.req.StreamReq; +import com.dite.znpt.monitor.media.zlm.dto.resp.MediaResp; +import com.dite.znpt.monitor.service.DeviceVideoChannelService; +import com.dite.znpt.monitor.service.DeviceVideoService; +import com.dite.znpt.monitor.sip.config.SipConfig; +import com.dite.znpt.monitor.sip.transmit.cmd.ISipDeviceCommander; +import com.dite.znpt.monitor.utils.DictUtils; +import com.dite.znpt.util.PageUtil; +import com.github.pagehelper.PageInfo; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:10 + * @Description: + */ +@Service +public class DeviceVideoChannelServiceImpl extends ServiceImpl implements DeviceVideoChannelService { + + @Resource + private MediaServerChannelCache channelCache; + + @Resource + private DeviceVideoService deviceVideoService; + + @Resource + private ISipDeviceCommander sipDeviceCommander; + + @Resource + private ZlmService zlmService; + + @Resource + private ZlmApi zlmApi; + + @Resource + private SipConfig sipConfig; + + /** + * 查询视频通道列表 + * + * @param videoId 视频id + * @param keyword 查询条件 + * @return {@link PageResult < DeviceVideoChannelListResp >} + */ + @Override + public PageResult selectDeviceVideoChannel(Long videoId, String keyword) { + PageUtil.startPage(); + List list = this.baseMapper.selectDeviceVideoChannel(videoId, keyword); + return buildPageResult(list); + } + + /** + * 查询所有视频通道列表 + * + * @param keyword 查询条件 + * @return {@link PageResult< DeviceVideoChannelListResp>} + */ + @Override + public PageResult selectAllDeviceVideoChannel(String keyword) { + PageUtil.startPage(); + List list = this.baseMapper.selectAllDeviceVideoChannel(keyword); + return buildPageResult(list); + } + + private PageResult buildPageResult(List list) { + if (CollectionUtil.isEmpty(list)) { + return PageResult.ok(list, 0); + } + long total = new PageInfo<>(list).getTotal(); + list.stream().peek(resp -> { + resp.setCameraTypeLabel(DictUtils.getDictLabel(CameraType.class, resp.getCameraType())); + resp.setPtzControlLabel(DictUtils.getDictLabel(YesOrNo.class, resp.getPtzControl())); + resp.setStatusLabel(DictUtils.getDictLabel(DeviceStatus.class, resp.getStatus())); + }).collect(Collectors.toList()); + return PageResult.ok(list, total); + } + + /** + * 查询视频通道详情 + * + * @param channelCode + * @return {@link DeviceVideoChannelResp} + */ + @Override + public DeviceVideoChannelResp getDeviceVideoChannelDetail(String channelCode) { + return this.baseMapper.getDeviceVideoChannelDetail(channelCode); + } + + /** + * 编辑视频设备通道 + * + * @param channelId + * @param req + * @return + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void editDeviceVideoChannel(Long channelId, DeviceVideoChannelEditReq req) { + DeviceVideoChannelEntity entity = this.getById(channelId); + BeanUtil.copyProperties(req, entity); + entity.setUpdateTime(LocalDateTime.now()); + this.updateById(entity); + } + + /** + * 根据通道id删除通道信息 + * + * @param channelIds + * @return {@link boolean} + */ + @Transactional(rollbackFor = Exception.class) + @Override + public Result removeByChannelIds(List channelIds) { + List list = this.listByIds(channelIds); + List ids = list.stream().map(DeviceVideoChannelEntity::getChannelId).collect(Collectors.toList()); + if (ids.size() == list.size()) { + return Result.ok(this.removeByIds(ids)); + } else { + return Result.error(IotRespMessage.DEVICE_VIDEO_CANNOT_DELETE); + } + } + + @Override + public DeviceVideoChannelEntity getByCode(String channelCode) { + return super.getOne(Wrappers.lambdaQuery(DeviceVideoChannelEntity.class) + .eq(DeviceVideoChannelEntity::getChannelCode, channelCode) + .orderByDesc(DeviceVideoChannelEntity::getCreateTime) + .last("limit 1")); + } + + /** + * 根据设备国标编码查询设备通道 + * + * @param videoId 设备id + * @return {@link List< DeviceVideoChannelEntity>} + */ + @Override + public List selectDeviceVideoChannelByVideoCode(Long videoId) { + return this.list(Wrappers.lambdaQuery(DeviceVideoChannelEntity.class).eq(DeviceVideoChannelEntity::getVideoId, videoId)); + } + + /** + * 播放视频 + * + * @param channelCode + * @return + */ + @Override + public VideoPayResp play(String channelCode) { + DeviceVideoChannelEntity channelEntity = Optional.ofNullable(this.getByCode(channelCode)).orElseThrow(() -> new ServiceException(IotRespMessage.ID_NOT_FOUND)); + DeviceVideoEntity videoEntity = Optional.ofNullable(deviceVideoService.getById(channelEntity.getVideoId())).orElseThrow(() -> new ServiceException(IotRespMessage.ID_NOT_FOUND)); + MediaItem mediaItem = zlmService.play(videoEntity.getVideoCode(), channelEntity.getChannelCode()); + if (!mediaItem.getIsCache()) { + sipDeviceCommander.playStreamCmd(videoEntity, channelEntity.getChannelCode(), mediaItem.getSsrc(), mediaItem.getRtpPort(), mediaItem.getServer().getApiHost()); + } + return VideoPayResp.builder() + .mediaType(sipConfig.getMediaType()) + .streamMediaFormatList(mediaItem.getFormatList(sipConfig.getMediaRouter())) + .build(); + } + + /** + * 是否在线 + * + * @param channelCode + * @return + */ + @Override + public boolean isOnline(String channelCode) { + DeviceVideoChannelEntity channelEntity = this.getByCode(channelCode); + return Objects.nonNull(channelEntity) && channelEntity.getStatus().equals(DeviceStatus.ONLINE.getValue()); + } + + /** + * 停止播放直播 + * + * @param channelCode + * @return + */ + @Override + public void stop(String channelCode) { + DeviceVideoChannelEntity channelEntity = Optional.ofNullable(this.getByCode(channelCode)).orElseThrow(() -> new ServiceException(IotRespMessage.ID_NOT_FOUND)); + DeviceVideoEntity videoEntity = Optional.ofNullable(deviceVideoService.getById(channelEntity.getVideoId())).orElseThrow(() -> new ServiceException(IotRespMessage.ID_NOT_FOUND)); + MediaItem mediaItem = channelCache.get(videoEntity.getVideoCode(), channelEntity.getChannelCode()); + if (null != mediaItem) { + List list = zlmApi.getMediaList(mediaItem.getServer(), new StreamReq().setStream(mediaItem.getStreamId())); + if (CollectionUtil.isNotEmpty(list) && list.get(0).getTotalReaderCount() <= 1) { + // 当只有一个人观看时,想设备发送停止推流命令 + sipDeviceCommander.stopStreamCmd(mediaItem.getSsrc()); + } + zlmService.release(videoEntity.getVideoCode(), channelEntity.getChannelCode()); + } + } + + /** + * 下线视频设备下的所有通道 + * + * @param videoId + * @return + */ + @Override + public void offlineByVideoId(Long videoId) { + List list = this.list(Wrappers.lambdaQuery(DeviceVideoChannelEntity.class).eq(DeviceVideoChannelEntity::getVideoId, videoId)); + if (ObjectUtil.isNotNull(list)) { + this.updateBatchById( + list.stream().peek(entity -> { + entity.setUpdateTime(LocalDateTime.now()); + entity.setStatus(DeviceStatus.OFFLINE.getValue()); + }).collect(Collectors.toList()) + ); + } + } + + /** + * 查询通道及视频信息 + * + * @param videoInfoReq 查询参数 + * @return {@link VideoInfoResp } + * @author huise23 + * @since 2024-12-03 13:54:52 + */ + @Override + public List selectVideoInfoList(VideoInfoReq videoInfoReq) { + return this.baseMapper.selectVideoInfoList(videoInfoReq); + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/service/impl/DeviceVideoServiceImpl.java b/sip/src/main/java/com/dite/znpt/monitor/service/impl/DeviceVideoServiceImpl.java new file mode 100644 index 0000000..8c00abb --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/service/impl/DeviceVideoServiceImpl.java @@ -0,0 +1,225 @@ +package com.dite.znpt.monitor.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.http.HttpStatus; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.dite.znpt.domain.PageResult; +import com.dite.znpt.domain.Result; +import com.dite.znpt.exception.ServiceException; +import com.dite.znpt.monitor.constant.IotCacheConstants; +import com.dite.znpt.monitor.constant.IotDictConstants; +import com.dite.znpt.monitor.constant.IotRespMessage; +import com.dite.znpt.monitor.constant.dict.DeviceStatus; +import com.dite.znpt.monitor.constant.dict.SipTransferMode; +import com.dite.znpt.monitor.constant.dict.StreamTransferMode; +import com.dite.znpt.monitor.domain.entity.DeviceVideoChannelEntity; +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoEditReq; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoListResp; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoNumResp; +import com.dite.znpt.monitor.mapper.DeviceVideoMapper; +import com.dite.znpt.monitor.service.DeviceVideoChannelService; +import com.dite.znpt.monitor.service.DeviceVideoService; +import com.dite.znpt.monitor.sip.transmit.cmd.ISipDeviceCommander; +import com.dite.znpt.monitor.utils.DictUtils; +import com.dite.znpt.service.impl.RedisService; +import com.github.pagehelper.PageInfo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @Author: huise23 + * @Date: 2022/8/11 18:11 + * @Description: + */ +@Slf4j +@Service +public class DeviceVideoServiceImpl extends ServiceImpl implements DeviceVideoService { + + @Resource + private RedisService redisService; + + @Resource + private DeviceVideoChannelService deviceVideoChannelService; + + @Resource + private ISipDeviceCommander sipDeviceCommander; + + /** + * 查询视频设备列表 + * + * @param status 状态 + * @param keyword 设备编码或者设备名称 + * @return {@link List < DeviceVideoListResp >} + */ + @Override + public PageResult selectDeviceVideoList(String status, String keyword, String hostAddress) { + List deviceVideoListResps = this.baseMapper.selectDeviceVideoList(status, keyword,hostAddress); + int total = (int) new PageInfo(deviceVideoListResps).getTotal(); + deviceVideoListResps = deviceVideoListResps.stream().peek(resp -> { + resp.setStatusLabel(DictUtils.getDictLabel(DeviceStatus.class, resp.getStatus())); + resp.setStreamModeLabel(DictUtils.getDictLabel(StreamTransferMode.class, resp.getStreamMode())); + resp.setTransportLabel(DictUtils.getDictLabel(SipTransferMode.class, resp.getTransport())); + }).collect(Collectors.toList()); + return PageResult.ok(deviceVideoListResps,total); + } + + /** + * 查询视频设备数量 + * + * @param + * @return {@link DeviceVideoNumResp} + */ + @Override + public DeviceVideoNumResp countDeviceVideoNum() { + DeviceVideoNumResp deviceVideoNumResp = new DeviceVideoNumResp(); + List deviceVideoList = this.baseMapper.selectDeviceVideoList(null, null,null); + deviceVideoNumResp.setAllDevice(deviceVideoList.stream().count()); + deviceVideoNumResp.setOnlineDevice(deviceVideoList.stream().filter(item -> IotDictConstants.IOT_DEVICE_STATUS_ONLINE.equals(item.getStatus())).count()); + deviceVideoNumResp.setOfflineDevice(deviceVideoList.stream().filter(item -> IotDictConstants.IOT_DEVICE_STATUS_OFFLINE.equals(item.getStatus())).count()); + return deviceVideoNumResp; + } + + /** + * 编辑视频设备信息 + * + * @param videoId 视频设备id + * @param req 视频设备信息 + * @return + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void editDeviceVideo(Long videoId, DeviceVideoEditReq req) { + DeviceVideoEntity entity = this.getById(videoId); + if (null == entity) { + throw new ServiceException(IotRespMessage.ID_NOT_FOUND); + } + BeanUtil.copyProperties(req, entity); + this.updateById(entity); + } + + /** + * 删除视频设备 + * + * @param videoId 视频设备id + * @return + */ + @Transactional(rollbackFor = Exception.class) + @Override + public Result removeByVideoId(Long videoId) { + DeviceVideoEntity entity = this.getById(videoId); + if (null == entity) { + throw new ServiceException(IotRespMessage.ID_NOT_FOUND); + } + if (!DeviceStatus.OFFLINE.getValue().equals(entity.getStatus())) { + return Result.error(IotRespMessage.DEVICE_VIDEO_CANNOT_DELETE); + } + Result result = Result.ok(); + List list = deviceVideoChannelService.list(Wrappers.lambdaQuery(DeviceVideoChannelEntity.class).eq(DeviceVideoChannelEntity::getVideoId,videoId)); + List channelIds = list.stream().map(DeviceVideoChannelEntity::getChannelId).collect(Collectors.toList()); + if(CollectionUtil.isNotEmpty(channelIds)){ + result = deviceVideoChannelService.removeByChannelIds(channelIds); + } + if (HttpStatus.HTTP_OK == result.getCode()) { + return this.removeById(videoId) ? Result.ok() : Result.error(); + } else { + return result; + } + } + + @Override + public DeviceVideoEntity getDeviceByHostAndPort(String host, int port) { + return super.getOne(Wrappers.lambdaQuery(DeviceVideoEntity.class) + .eq(DeviceVideoEntity::getIp, host) + .eq(DeviceVideoEntity::getPort, port) +// .orderByDesc(DeviceVideoEntity::getCreateTime) + .last("limit 1")); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void offline(String videoCode) { + DeviceVideoEntity entity = this.getByCode(videoCode); + entity.setStatus(DeviceStatus.OFFLINE.getValue()); +// entity.setUpdateTime(LocalDateTime.now()); + this.updateById(entity); + deviceVideoChannelService.offlineByVideoId(entity.getVideoId()); + + } + + /** + * 上线设备 + * + * @param entity + * @return + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void online(DeviceVideoEntity entity) { + log.info("[设备上线] deviceId:{}->{}:{}",entity.getVideoCode(), entity.getIp(), entity.getPort()); + String deviceCacheKey = IotCacheConstants.getIotDeviceVideoKey(entity.getVideoCode()); + entity.setStatus(DeviceStatus.ONLINE.getValue()); + if(entity.getVideoId() == null){ + // 设备首次上线 + if(redisService.hasKey(deviceCacheKey)){ + // 脏数据 + redisService.deleteObject(deviceCacheKey); + } + this.save(entity); + } else { + entity.setUpdateTime(LocalDateTime.now()); + this.updateById(entity); + } + sipDeviceCommander.queryDeviceInfo(entity); + sipDeviceCommander.queryCatalog(entity); + } + + @Override + public boolean expire(DeviceVideoEntity entity) { + LocalDateTime expireDateTime = entity.getRegisterTime().plus(entity.getExpires(), ChronoUnit.MILLIS); + return expireDateTime.isBefore(LocalDateTime.now()); + } + + @Override + public DeviceVideoEntity getByCode(String videoCode) { + return super.getOne(Wrappers.lambdaQuery(DeviceVideoEntity.class) + .eq(DeviceVideoEntity::getVideoCode, videoCode) + .orderByDesc(DeviceVideoEntity::getCreateTime) + .last("limit 1")); + } + + /** + * 根据 videoCode 获取设备 + * + * @param channelCode + * @return 设备 + */ + @Override + public DeviceVideoEntity getByChannelCode(String channelCode) { + DeviceVideoChannelEntity channelEntity = Optional.ofNullable(deviceVideoChannelService.getByCode(channelCode)).orElseThrow(()-> new ServiceException(IotRespMessage.ID_NOT_FOUND)); + return this.getById(channelEntity.getVideoId()); + } + + /** + * 查询设备状态 + * + * @param videoId + * @return + */ + @Override + public void queryDeviceStatus(Long videoId) { + DeviceVideoEntity entity = Optional.ofNullable(this.getById(videoId)).orElseThrow( () -> new ServiceException(IotRespMessage.ID_NOT_FOUND)); + sipDeviceCommander.queryDeviceStatus(entity); + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/service/impl/IpConfigServiceImpl.java b/sip/src/main/java/com/dite/znpt/monitor/service/impl/IpConfigServiceImpl.java new file mode 100644 index 0000000..0d33fa6 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/service/impl/IpConfigServiceImpl.java @@ -0,0 +1,80 @@ +package com.dite.znpt.monitor.service.impl; + +import cn.hutool.core.util.NumberUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.dite.znpt.exception.ServiceException; +import com.dite.znpt.monitor.domain.entity.IpConfigEntity; +import com.dite.znpt.monitor.domain.req.MonitorConfigAddReq; +import com.dite.znpt.monitor.mapper.IpConfigMapper; +import com.dite.znpt.monitor.service.IpConfigService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @Date: 2023/09/05 16:39 + * @Description: 监控设备IP配置表服务实现类 + */ +@Service +public class IpConfigServiceImpl extends ServiceImpl implements IpConfigService { + + + @Override + @Transactional(rollbackFor = Exception.class) + public void configAdd(MonitorConfigAddReq req) { + //先删除再新增--全量新增 + final List configAdds = req.getIpAddresses().stream().map(this::BuildConfigEntity).collect(Collectors.toList()); + //校验是否有重复的--用前三位来判断重复 + checkDup(configAdds); + deleteConfig(); + this.saveBatch(configAdds); + } + + + + @Override + public List configList() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + final List ifConfigs = this.list(wrapper); + return ifConfigs.stream().map(t->t.getIp()).collect(Collectors.toList()); + } + + private void deleteConfig(){ + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + this.remove(wrapper); + } + + private void checkDup(List configAdds) { + final List ipTopThreeList = configAdds.stream().map(t -> t.getIpTopThree()).collect(Collectors.toList()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(IpConfigEntity::getIpTopThree,ipTopThreeList) + .last("limit 1"); + final IpConfigEntity one = this.getOne(wrapper); + if(one != null){ + throw new ServiceException("与现有视频地址重复,请重新选择!"); + } + } + + private IpConfigEntity BuildConfigEntity( String ip) { + IpConfigEntity entity = new IpConfigEntity(); + final String[] ipArray = ip.split("\\."); + ipValidate(ipArray); + entity.setIp(ip); + entity.setIpTopThree(ipArray[0]+"."+ipArray[1]+"."+ipArray[2]); + return entity; + } + + private void ipValidate(String[] ipArray) { + if(ipArray.length!=4){ + throw new ServiceException("ip地址长度不对"); + } + for (int i = 0; i < 4; i++) { + if(! (NumberUtil.isInteger(ipArray[i]) || (i==3 && "*".equals(ipArray[i])))){ + throw new ServiceException("ip地址为非数字"); + } + } + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/service/impl/StreamMediaFormatServiceImpl.java b/sip/src/main/java/com/dite/znpt/monitor/service/impl/StreamMediaFormatServiceImpl.java new file mode 100644 index 0000000..06aaa50 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/service/impl/StreamMediaFormatServiceImpl.java @@ -0,0 +1,17 @@ +package com.dite.znpt.monitor.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.dite.znpt.monitor.domain.vo.video.StreamMediaFormat; +import com.dite.znpt.monitor.mapper.StreamMediaFormatMapper; +import com.dite.znpt.monitor.service.StreamMediaFormatService; +import org.springframework.stereotype.Service; + +/** + * @Author: huise23 + * @Date: 2022/8/11 14:59 + * @Description: + */ +@Service +public class StreamMediaFormatServiceImpl extends ServiceImpl implements StreamMediaFormatService { + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/SipLayer.java b/sip/src/main/java/com/dite/znpt/monitor/sip/SipLayer.java new file mode 100644 index 0000000..3e1ce22 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/SipLayer.java @@ -0,0 +1,100 @@ +package com.dite.znpt.monitor.sip; + +import com.dite.znpt.monitor.sip.config.SipConfig; +import com.dite.znpt.monitor.sip.transmit.SipProcessorFactoryI; +import gov.nist.javax.sip.SipProviderImpl; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.*; +import java.util.Properties; +import java.util.TooManyListenersException; + +/** + * @Author: huise23 + * @Date: 2022/8/10 16:20 + * @Description: + */ +@Slf4j +@Component +public class SipLayer { + + @Resource + private SipConfig sipConfig; + + @Resource + private SipProcessorFactoryI sipProcessorFactory; + + private SipStack sipStack; + + private SipFactory sipFactory; + + @Bean("sipFactory") + SipFactory createSipFactory() { + sipFactory = SipFactory.getInstance(); + sipFactory.setPathName("gov.nist"); + return sipFactory; + } + + @Bean("sipStack") + @DependsOn({"sipFactory"}) + SipStack createSipStack() throws PeerUnavailableException { + Properties properties = new Properties(); + properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP"); + properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getIp()); + /** + * 完整配置参考 gov.nist.javax.sip.SipStackImpl,需要下载源码 + * gov/nist/javax/sip/SipStackImpl.class + */ + if (log.isDebugEnabled()) { + properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "true"); + } + // 接收所有notify请求,即使没有订阅 + properties.setProperty("gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY", "true"); + // 为_NULL _对话框传递_终止的_事件 + properties.setProperty("gov.nist.javax.sip.DELIVER_TERMINATED_EVENT_FOR_NULL_DIALOG", "true"); + // 会话清理策略 + properties.setProperty("gov.nist.javax.sip.RELEASE_REFERENCES_STRATEGY", "Normal"); + // 处理由该服务器处理的基于底层TCP的保持生存超时 + properties.setProperty("gov.nist.javax.sip.RELIABLE_CONNECTION_KEEP_ALIVE_TIMEOUT", "60"); + + /** + * sip_server_log.log 和 sip_debug_log.log public static final int TRACE_NONE = + * 0; public static final int TRACE_MESSAGES = 16; public static final int + * TRACE_EXCEPTION = 17; public static final int TRACE_DEBUG = 32; + */ + if (log.isDebugEnabled()) { + properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "DEBUG"); + } +// sip日志等级 + properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "ERROR"); + sipStack = sipFactory.createSipStack(properties); + + return sipStack; + } + + @Bean(name = "tcpSipProvider") + @DependsOn("sipStack") + SipProviderImpl startTcpListener() throws InvalidArgumentException, TransportNotSupportedException, ObjectInUseException, TooManyListenersException { + ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getIp(), sipConfig.getPort(), "TCP"); + SipProviderImpl tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint); + tcpSipProvider.setDialogErrorsAutomaticallyHandled(); + tcpSipProvider.addSipListener(sipProcessorFactory); + log.info("[Sip Server] TCP 启动成功 {}:{}", sipConfig.getIp(), sipConfig.getPort()); + return tcpSipProvider; + } + + @Bean(name = "udpSipProvider") + @DependsOn("sipStack") + SipProviderImpl startUdpListener() throws InvalidArgumentException, TransportNotSupportedException, ObjectInUseException, TooManyListenersException { + ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getIp(), sipConfig.getPort(), "UDP"); + SipProviderImpl udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint); + udpSipProvider.addSipListener(sipProcessorFactory); + log.info("[Sip Server] UDP 启动成功 {}:{}", sipConfig.getIp(), sipConfig.getPort()); + return udpSipProvider; + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/config/SipConfig.java b/sip/src/main/java/com/dite/znpt/monitor/sip/config/SipConfig.java new file mode 100644 index 0000000..86d475f --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/config/SipConfig.java @@ -0,0 +1,38 @@ +package com.dite.znpt.monitor.sip.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @Author: huise23 + * @Date: 2022/8/10 16:59 + * @Description: + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "sip-config") +public class SipConfig { + + String name; + + String ip; + + Integer port; + + String charset; + + String domain; + + String id; + + String password; + + String mediaType = "mp4"; + + /** + * zlm播放地址路由 + */ + String mediaRouter = "/zlm"; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/session/StreamSessionManager.java b/sip/src/main/java/com/dite/znpt/monitor/sip/session/StreamSessionManager.java new file mode 100644 index 0000000..48d758d --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/session/StreamSessionManager.java @@ -0,0 +1,26 @@ +package com.dite.znpt.monitor.sip.session; + +import org.springframework.stereotype.Component; + +import javax.sip.ClientTransaction; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @Author: huise23 + * @Date: 2022/8/29 16:52 + * @Description: 视频流session管理器,管理视频预览、预览回放的通信句柄 + */ +@Component +public class StreamSessionManager { + + private ConcurrentHashMap sessionMap = new ConcurrentHashMap<>(); + + public void put(String ssrc, ClientTransaction transaction){ + sessionMap.put(ssrc, transaction); + } + + public ClientTransaction get(String ssrc){ + return sessionMap.get(ssrc); + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/SipProcessorFactoryI.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/SipProcessorFactoryI.java new file mode 100644 index 0000000..ad1c041 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/SipProcessorFactoryI.java @@ -0,0 +1,11 @@ +package com.dite.znpt.monitor.sip.transmit; + +import javax.sip.SipListener; + +/** + * @Author: huise23 + * @Date: 2022/8/29 16:52 + * @Description: + */ +public interface SipProcessorFactoryI extends SipListener { +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/SipProcessorFactoryImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/SipProcessorFactoryImpl.java new file mode 100644 index 0000000..b450e7e --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/SipProcessorFactoryImpl.java @@ -0,0 +1,99 @@ +package com.dite.znpt.monitor.sip.transmit; + + +import cn.hutool.json.JSONUtil; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessor; +import com.dite.znpt.monitor.sip.transmit.response.ISipResponseProcessor; +import com.dite.znpt.monitor.sip.transmit.timeout.ITimeoutProcessor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import javax.sip.*; +import javax.sip.header.CSeqHeader; +import javax.sip.message.Response; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @Author: huise23 + * @Date: 2022/8/10 16:44 + * @Description: + */ +@Slf4j +@Component +public class SipProcessorFactoryImpl implements SipProcessorFactoryI { + + private static Map requestProcessorMap = new ConcurrentHashMap<>(); + + private static Map responseProcessorMap = new ConcurrentHashMap<>(); + + private static ITimeoutProcessor timeoutProcessor; + + public void addRequestProcessor(String method, ISipRequestProcessor processor) { + requestProcessorMap.put(method, processor); + } + + public void addResponseProcessor(String method, ISipResponseProcessor processor) { + responseProcessorMap.put(method, processor); + } + + public void addTimeoutProcessor(ITimeoutProcessor processor) { + timeoutProcessor = processor; + } + + @Override + @Async + public void processRequest(RequestEvent requestEvent) { + log.info("requestEvent:"+ JSONUtil.toJsonStr(requestEvent.getRequest())); + String method = requestEvent.getRequest().getMethod(); + ISipRequestProcessor sipRequestProcessor = requestProcessorMap.get(method); + if (sipRequestProcessor == null) { + log.warn("不支持方法{}的request", method); + return; + } + sipRequestProcessor.process(requestEvent); + } + + @Override + @Async + public void processResponse(ResponseEvent responseEvent) { + Response response = responseEvent.getResponse(); + int status = response.getStatusCode(); + if ((status >= 200 && status < 300) || status == Response.UNAUTHORIZED) { + CSeqHeader cseqHeader = (CSeqHeader) responseEvent.getResponse().getHeader(CSeqHeader.NAME); + String method = cseqHeader.getMethod(); + ISipResponseProcessor sipResponseProcessor = responseProcessorMap.get(method); + if (sipResponseProcessor == null) { + log.warn("不支持方法{}的response", method); + return; + } + sipResponseProcessor.process(responseEvent); + } else if ((status >= 100) && (status < 200)) { + // 增加其它无需回复的响应,如101、180等 + } else { + //未完成 接收到失败的response响应!status:400,message:Bad Request + log.warn("接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase()); + } + } + + @Override + public void processTimeout(TimeoutEvent timeoutEvent) { + // TODO Auto-generated method stub + } + + @Override + public void processIOException(IOExceptionEvent exceptionEvent) { + // TODO Auto-generated method stub + } + + @Override + public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) { + // TODO Auto-generated method stub + } + + @Override + public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) { + // TODO Auto-generated method stub + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/ISipDeviceCommander.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/ISipDeviceCommander.java new file mode 100644 index 0000000..7ace5f6 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/ISipDeviceCommander.java @@ -0,0 +1,50 @@ +package com.dite.znpt.monitor.sip.transmit.cmd; + +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; + +/** + * @Author: huise23 + * @Date: 2022/8/30 8:46 + * @Description: + */ +public interface ISipDeviceCommander { + + /** + * 查询目录列表 + * @param entity + * @return {@link boolean} + */ + boolean queryCatalog(DeviceVideoEntity entity); + + /** + * 查询设备信息 + * @param entity + * @return {@link boolean} + */ + boolean queryDeviceInfo(DeviceVideoEntity entity); + + /** + * 查询设备状态 + * @param entity + * @return {@link boolean} + */ + boolean queryDeviceStatus(DeviceVideoEntity entity); + + /** + * 请求预览视频流 + * + * @param entity + * @param channelCode + * @param ssrc + * @param ssrcPort + * @return {@link String} + */ + void playStreamCmd(DeviceVideoEntity entity, String channelCode, String ssrc, Integer ssrcPort, String mediaIp); + + /** + * 停止视频流 + * @param ssrc + * @return + */ + void stopStreamCmd(String ssrc); +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/SipRequestHeaderProvider.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/SipRequestHeaderProvider.java new file mode 100644 index 0000000..383c7c8 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/SipRequestHeaderProvider.java @@ -0,0 +1,120 @@ +package com.dite.znpt.monitor.sip.transmit.cmd; + +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.sip.config.SipConfig; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.InvalidArgumentException; +import javax.sip.PeerUnavailableException; +import javax.sip.SipFactory; +import javax.sip.SipProvider; +import javax.sip.address.Address; +import javax.sip.address.SipURI; +import javax.sip.header.*; +import javax.sip.message.Request; +import java.security.SecureRandom; +import java.text.ParseException; +import java.util.ArrayList; + +/** + * 摄像头命令request构造器 + * + * @author yun11 + * @date 2023/05/11 + */ +@Component +public class SipRequestHeaderProvider { + + @Resource + private SipConfig sipConfig; + + @Resource + private SipFactory sipFactory; + + @Resource + @Qualifier(value="tcpSipProvider") + private SipProvider tcpSipProvider; + + @Resource + @Qualifier(value="udpSipProvider") + private SipProvider udpSipProvider; + + /** + * 创建Message信令请求报文 + * @param device + * @param content + * @param viaTag + * @param fromTag + * @param toTag + * @return {@link Request} + */ + public Request createMessageRequest(DeviceVideoEntity device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException, PeerUnavailableException { + Request request = null; + // sipuri + SipURI requestUri = sipFactory.createAddressFactory().createSipURI(device.getVideoCode(), device.getHostAddress()); + // via + ArrayList viaHeaders = new ArrayList(); + ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + // from + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); + Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); + // to + SipURI toSipUri = sipFactory.createAddressFactory().createSipURI(device.getVideoCode(), device.getHostAddress()); + Address toAddress = sipFactory.createAddressFactory().createAddress(toSipUri); + ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, toTag); + // callid + CallIdHeader callIdHeader = "TCP".equals(device.getTransport()) ? tcpSipProvider.getNewCallId() : udpSipProvider.getNewCallId(); + // Forwards + MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70); + // ceq + CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(Long.valueOf(Math.abs(new SecureRandom().nextInt(Integer.MAX_VALUE)) - 1000), Request.MESSAGE); + + request = sipFactory.createMessageFactory().createRequest(requestUri, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader, + toHeader, viaHeaders, maxForwards); + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); + request.setContent(content, contentTypeHeader); + return request; + } + + public Request createInviteRequest(DeviceVideoEntity entity, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { + Request request = null; + //请求行 + SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, entity.getHostAddress()); + //via + ArrayList viaHeaders = new ArrayList(); + ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), entity.getTransport(), viaTag); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + //from + SipURI fromSipUri = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); + Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipUri); + //必须要有标记,否则无法创建会话,无法回应ack + FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag); + //to + SipURI toSipUri = sipFactory.createAddressFactory().createSipURI(channelId, entity.getHostAddress()); + Address toAddress = sipFactory.createAddressFactory().createAddress(toSipUri); + ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,null); + + //Forwards + MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70); + + //ceq + CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(Long.valueOf(Math.abs(new SecureRandom().nextInt(Integer.MAX_VALUE)) - 1000), Request.INVITE); + request = sipFactory.createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); + + Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp() + ":" + sipConfig.getPort())); + request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress)); + // Subject + SubjectHeader subjectHeader = sipFactory.createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0)); + request.addHeader(subjectHeader); + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); + request.setContent(content, contentTypeHeader); + return request; + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/impl/SipDeviceCommanderImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/impl/SipDeviceCommanderImpl.java new file mode 100644 index 0000000..72672fc --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/cmd/impl/SipDeviceCommanderImpl.java @@ -0,0 +1,226 @@ +package com.dite.znpt.monitor.sip.transmit.cmd.impl; + +import cn.hutool.core.util.SerializeUtil; +import com.dite.znpt.monitor.constant.IotCacheConstants; +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.media.zlm.ZlmService; +import com.dite.znpt.monitor.sip.transmit.cmd.ISipDeviceCommander; +import com.dite.znpt.monitor.sip.transmit.cmd.SipRequestHeaderProvider; +import com.dite.znpt.service.impl.RedisService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.ClientTransaction; +import javax.sip.InvalidArgumentException; +import javax.sip.SipException; +import javax.sip.SipProvider; +import javax.sip.address.SipURI; +import javax.sip.header.CallIdHeader; +import javax.sip.header.ViaHeader; +import javax.sip.message.Request; +import java.text.ParseException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @Author: huise23 + * @Date: 2022/8/30 8:50 + * @Description: + */ +@Slf4j +@Component +public class SipDeviceCommanderImpl implements ISipDeviceCommander { + + private final static Pattern VIA_HEADER_PATTERN = Pattern.compile("(\\d+\\.\\d+\\.\\d+\\.\\d+)\\:(\\d+)"); + @Resource + @Qualifier(value="tcpSipProvider") + private SipProvider tcpSipProvider; + + @Resource + @Qualifier(value="udpSipProvider") + private SipProvider udpSipProvider; + + @Resource + private SipRequestHeaderProvider sipRequestHeaderProvider; + + @Resource + private ZlmService zlmService; + + @Resource + private RedisService redisService; + + /** + * 查询目录列表 + * @param entity + * @return {@link boolean} + */ + @Override + public boolean queryCatalog(DeviceVideoEntity entity) { + try { + StringBuffer content = new StringBuffer(200); + String charset = entity.getCharset(); + content.append("\r\n"); + content.append("\r\n"); + content.append(" Catalog\r\n"); + content.append(" " + (int)((Math.random()*9+1)*100000) + "\r\n"); + content.append(" " + entity.getVideoCode() + "\r\n"); + content.append("\r\n"); + + String tag = String.valueOf(System.nanoTime()); + Request request = sipRequestHeaderProvider.createMessageRequest(entity, content.toString(), tag, tag, null); + transmitRequest(entity.getTransport(), request); + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + return false; + } + return true; + } + + /** + * 查询设备信息 + * @param entity + * @return {@link boolean} + */ + @Override + public boolean queryDeviceInfo(DeviceVideoEntity entity) { + try { + StringBuffer content = new StringBuffer(200); + String charset = entity.getCharset(); + content.append("\r\n"); + content.append("\r\n"); + content.append("DeviceInfo\r\n"); + content.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); + content.append("" + entity.getVideoCode() + "\r\n"); + content.append("\r\n"); + String tag = String.valueOf(System.nanoTime()); + Request request = sipRequestHeaderProvider.createMessageRequest(entity, content.toString(), tag, tag, null); + transmitRequest(entity.getTransport(), request); + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + return false; + } + return true; + } + + /** + * 查询设备状态 + * @param entity + * @return {@link boolean} + */ + @Override + public boolean queryDeviceStatus(DeviceVideoEntity entity) { + try { + StringBuffer content = new StringBuffer(200); + content.append("\r\n"); + content.append("\r\n"); + content.append("DeviceStatus\r\n"); + content.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); + content.append("" + entity.getVideoCode() + "\r\n"); + content.append("\r\n"); + String tag = String.valueOf(System.nanoTime()); + Request request = sipRequestHeaderProvider.createMessageRequest(entity, content.toString(), tag, tag, tag); + transmitRequest(entity.getTransport(), request); + return true; + + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 请求预览视频流 + * + * @param entity + * @param channelCode 流传输协议 + * @param ssrc + * @param ssrcPort + * @return {@link String} + */ + @Override + public void playStreamCmd(DeviceVideoEntity entity, String channelCode, String ssrc, Integer ssrcPort, String mediaIp){ + try { + String streamMode = entity.getStreamMode().toUpperCase(); + StringBuffer content = new StringBuffer(200); + content.append("v=0\r\n"); + content.append("o=" + channelCode + " 0 0 IN IP4 " + mediaIp + "\r\n"); + content.append("s=Play\r\n"); + content.append("c=IN IP4 " + mediaIp + "\r\n"); + content.append("t=0 0\r\n"); + if("TCP-PASSIVE".equals(streamMode)) { + content.append("m=video " + ssrcPort + " TCP/RTP/AVP 96 97 98 99\r\n"); + }else if ("TCP-ACTIVE".equals(streamMode)) { + content.append("m=video " + ssrcPort + " TCP/RTP/AVP 96 97 98 99\r\n"); + }else if("UDP".equals(streamMode)) { + content.append("m=video " + ssrcPort + " RTP/AVP 96 97 98 99\r\n"); + } + content.append("a=recvonly\r\n"); + content.append("a=rtpmap:96 PS/90000\r\n"); + content.append("a=rtpmap:98 H264/90000\r\n"); + content.append("a=rtpmap:97 MPEG4/90000\r\n"); + content.append("a=rtpmap:99 H265/90000\r\n"); + if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式 + content.append("a=setup:passive\r\n"); + content.append("a=connection:new\r\n"); + } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式 + content.append("a=setup:active\r\n"); + content.append("a=connection:new\r\n"); + } + content.append("y=" + ssrc + "\r\n");//ssrc + String tm = Long.toString(System.currentTimeMillis()); + CallIdHeader callIdHeader = "TCP".equals(entity.getTransport()) ? tcpSipProvider.getNewCallId() : udpSipProvider.getNewCallId(); + Request request = sipRequestHeaderProvider.createInviteRequest(entity, channelCode, content.toString(), null, "FromInvt" + tm, null, ssrc, callIdHeader); + ClientTransaction clientTransaction = transmitRequest(entity.getTransport(), request); + System.out.println("-----------------------------------------------------------"); + redisService.setCacheObject(IotCacheConstants.getClientTransactionCacheKey(ssrc), SerializeUtil.serialize(clientTransaction)); + } catch (ParseException | InvalidArgumentException | SipException e) { + System.out.println("[zlm]拉流失败"); + zlmService.release(entity.getVideoCode(), channelCode); + e.printStackTrace(); + } + } + + /** + * 停止视频流 + * + * @param ssrc + * @return + */ + @Override + public void stopStreamCmd(String ssrc) { + try { + ClientTransaction transaction = redisService.hasKey(ssrc) ? SerializeUtil.deserialize(redisService.getCacheObject(ssrc)) : null; + if ( null == transaction || null == transaction.getDialog()) { + return; + } + Request byeRequest = transaction.getDialog().createRequest(Request.BYE); + SipURI byeURI = (SipURI) byeRequest.getRequestURI(); + String vh = transaction.getRequest().getHeader(ViaHeader.NAME).toString(); + + Matcher matcher = VIA_HEADER_PATTERN.matcher(vh); + if (VIA_HEADER_PATTERN.matcher(vh).find()) { + byeURI.setHost(matcher.group(1)); + } + ViaHeader viaHeader = (ViaHeader) byeRequest.getHeader(ViaHeader.NAME); + String protocol = viaHeader.getTransport().toUpperCase(); + ClientTransaction clientTransaction = null; + if("TCP".equals(protocol)) { + clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest); + } else if("UDP".equals(protocol)) { + clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest); + } + transaction.getDialog().sendRequest(clientTransaction); + redisService.deleteObject(ssrc); + } catch (SipException | ParseException e) { + e.printStackTrace(); + } + } + + private ClientTransaction transmitRequest(String transport, Request request) throws SipException { + ClientTransaction clientTransaction = "TCP".equals(transport) ? tcpSipProvider.getNewClientTransaction(request) : udpSipProvider.getNewClientTransaction(request); + clientTransaction.sendRequest(); + return clientTransaction; + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/ISipRequestProcessor.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/ISipRequestProcessor.java new file mode 100644 index 0000000..14ad459 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/ISipRequestProcessor.java @@ -0,0 +1,12 @@ +package com.dite.znpt.monitor.sip.transmit.request; + +import javax.sip.RequestEvent; + +/** + * @Author: huise23 + * @Date: 2022/8/10 16:46 + * @Description: + */ +public interface ISipRequestProcessor { + void process(RequestEvent event); +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/ISipRequestProcessorAbstract.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/ISipRequestProcessorAbstract.java new file mode 100644 index 0000000..20d7e99 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/ISipRequestProcessorAbstract.java @@ -0,0 +1,259 @@ +package com.dite.znpt.monitor.sip.transmit.request; + +import gov.nist.javax.sip.SipProviderImpl; +import gov.nist.javax.sip.SipStackImpl; +import gov.nist.javax.sip.message.SIPRequest; +import gov.nist.javax.sip.stack.SIPServerTransaction; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import javax.sip.*; +import javax.sip.address.Address; +import javax.sip.address.AddressFactory; +import javax.sip.address.SipURI; +import javax.sip.header.ContentTypeHeader; +import javax.sip.header.ExpiresHeader; +import javax.sip.header.HeaderFactory; +import javax.sip.header.ViaHeader; +import javax.sip.message.MessageFactory; +import javax.sip.message.Request; +import javax.sip.message.Response; +import java.io.ByteArrayInputStream; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/10 16:48 + * @Description: + */ + +@Slf4j +public abstract class ISipRequestProcessorAbstract { + + @Autowired + @Qualifier(value="tcpSipProvider") + private SipProviderImpl tcpSipProvider; + + @Autowired + @Qualifier(value="udpSipProvider") + private SipProviderImpl udpSipProvider; + + /** + * 根据 RequestEvent 获取 ServerTransaction + * @param evt + * @return + */ + public ServerTransaction getServerTransaction(RequestEvent evt) { + Request request = evt.getRequest(); + ServerTransaction serverTransaction = evt.getServerTransaction(); + // 判断TCP还是UDP + boolean isTcp = false; + ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); + String transport = reqViaHeader.getTransport(); + if ("TCP".equals(transport)) { + isTcp = true; + } + + if (serverTransaction == null) { + try { + if (isTcp) { + SipStackImpl stack = (SipStackImpl)tcpSipProvider.getSipStack(); + serverTransaction = (SIPServerTransaction) stack.findTransaction((SIPRequest)request, true); + if (serverTransaction == null) { + serverTransaction = tcpSipProvider.getNewServerTransaction(request); + } + } else { + SipStackImpl stack = (SipStackImpl)udpSipProvider.getSipStack(); + serverTransaction = (SIPServerTransaction) stack.findTransaction((SIPRequest)request, true); + if (serverTransaction == null) { + serverTransaction = udpSipProvider.getNewServerTransaction(request); + } + } + } catch (TransactionAlreadyExistsException e) { + log.error(e.getMessage()); + } catch (TransactionUnavailableException e) { + log.error(e.getMessage()); + } + } + return serverTransaction; + } + + public AddressFactory getAddressFactory() { + try { + return SipFactory.getInstance().createAddressFactory(); + } catch (PeerUnavailableException e) { + e.printStackTrace(); + } + return null; + } + + public HeaderFactory getHeaderFactory() { + try { + return SipFactory.getInstance().createHeaderFactory(); + } catch (PeerUnavailableException e) { + e.printStackTrace(); + } + return null; + } + + public MessageFactory getMessageFactory() { + try { + return SipFactory.getInstance().createMessageFactory(); + } catch (PeerUnavailableException e) { + e.printStackTrace(); + } + return null; + } + + /*** + * 回复状态码 + * 100 trying + * 200 OK + * 400 + * 404 + * @param evt + * @throws SipException + * @throws InvalidArgumentException + * @throws ParseException + */ + public void responseAck(RequestEvent evt, int statusCode) throws SipException, InvalidArgumentException, ParseException { + Response response = getMessageFactory().createResponse(statusCode, evt.getRequest()); + ServerTransaction serverTransaction = getServerTransaction(evt); + if (serverTransaction == null) { + log.warn("回复失败:{}", response); + return; + } + serverTransaction.sendResponse(response); + if (statusCode >= 200 && !"NOTIFY".equals(evt.getRequest().getMethod())) { + if (serverTransaction.getDialog() != null) { + serverTransaction.getDialog().delete(); + } + } + } + + public void responseAck(RequestEvent evt, int statusCode, String msg) throws SipException, InvalidArgumentException, ParseException { + Response response = getMessageFactory().createResponse(statusCode, evt.getRequest()); + response.setReasonPhrase(msg); + ServerTransaction serverTransaction = getServerTransaction(evt); + serverTransaction.sendResponse(response); + if (statusCode >= 200 && !"NOTIFY".equals(evt.getRequest().getMethod())) { + if (serverTransaction.getDialog() != null) { + serverTransaction.getDialog().delete(); + } + } + } + + /** + * 回复带sdp的200 + * @param evt + * @param sdp + * @param sipServerCode SIP服务国标编码 + * @param serverIp SIP服务ip + * @param sipServerPort SIP服务端口 + * @return + */ + public void responseSdpAck(RequestEvent evt, String sdp, String sipServerCode, String serverIp, Integer sipServerPort) throws SipException, InvalidArgumentException, ParseException { + Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest()); + SipFactory sipFactory = SipFactory.getInstance(); + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); + response.setContent(sdp, contentTypeHeader); + + // 兼容国标中的使用编码@域名作为RequestURI的情况 + SipURI sipURI = (SipURI)evt.getRequest().getRequestURI(); + if (sipURI.getPort() == -1) { + sipURI = sipFactory.createAddressFactory().createSipURI(sipServerCode, serverIp+":" + sipServerPort); + } + log.debug("responseSdpAck SipURI: {}:{}", sipURI.getHost(), sipURI.getPort()); + + Address concatAddress = sipFactory.createAddressFactory().createAddress( + sipFactory.createAddressFactory().createSipURI(sipURI.getUser(), sipURI.getHost()+":"+sipURI.getPort() + )); + response.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress)); + getServerTransaction(evt).sendResponse(response); + } + + /** + * 回复带xml的200 + * @param evt + * @param xml + * @param sipServerCode SIP服务国标编码 + * @param serverIp SIP服务ip + * @param sipServerPort SIP服务端口 + * @throws SipException + * @throws InvalidArgumentException + * @throws ParseException + */ + public Response responseXmlAck(RequestEvent evt, String xml, String sipServerCode, String serverIp, Integer sipServerPort) throws SipException, InvalidArgumentException, ParseException { + Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest()); + SipFactory sipFactory = SipFactory.getInstance(); + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); + response.setContent(xml, contentTypeHeader); + + // 兼容国标中的使用编码@域名作为RequestURI的情况 + SipURI sipURI = (SipURI)evt.getRequest().getRequestURI(); + if (sipURI.getPort() == -1) { + sipURI = sipFactory.createAddressFactory().createSipURI(sipServerCode, serverIp + ":" + sipServerPort); + } + log.debug("responseXmlAck SipURI: {}:{}", sipURI.getHost(), sipURI.getPort()); + + Address concatAddress = sipFactory.createAddressFactory().createAddress( + sipFactory.createAddressFactory().createSipURI(sipURI.getUser(), sipURI.getHost()+":"+sipURI.getPort() + )); + response.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress)); + response.addHeader(evt.getRequest().getHeader(ExpiresHeader.NAME)); + getServerTransaction(evt).sendResponse(response); + return response; + } + + public Element getRootElement(RequestEvent evt) throws DocumentException { + return getRootElement(evt, "gb2312"); + } + + public Element getRootElement(RequestEvent evt, String charset) throws DocumentException { + if (charset == null) { + charset = "gb2312"; + } + Request request = evt.getRequest(); + SAXReader reader = new SAXReader(); + reader.setEncoding(charset); + // 对海康出现的未转义字符做处理。 + String[] destStrArray = new String[]{"<",">","&","'","""}; + char despChar = '&'; // 或许可扩展兼容其他字符 + byte destBye = (byte) despChar; + List result = new ArrayList<>(); + byte[] rawContent = request.getRawContent(); + if (rawContent == null) { + return null; + } + for (int i = 0; i < rawContent.length; i++) { + if (rawContent[i] == destBye) { + boolean resul = false; + for (String destStr : destStrArray) { + if (i + destStr.length() <= rawContent.length) { + byte[] bytes = Arrays.copyOfRange(rawContent, i, i + destStr.length()); + resul = resul || (Arrays.equals(bytes,destStr.getBytes())); + } + } + if (resul) { + result.add(rawContent[i]); + } + }else { + result.add(rawContent[i]); + } + } + Byte[] bytes = new Byte[0]; + byte[] bytesResult = ArrayUtils.toPrimitive(result.toArray(bytes)); + + Document xml = reader.read(new ByteArrayInputStream(bytesResult)); + return xml.getRootElement(); + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/AckRequestProcessorImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/AckRequestProcessorImpl.java new file mode 100644 index 0000000..4fdfe4c --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/AckRequestProcessorImpl.java @@ -0,0 +1,53 @@ +package com.dite.znpt.monitor.sip.transmit.request.impl; + +import com.dite.znpt.monitor.sip.transmit.SipProcessorFactoryImpl; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessor; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessorAbstract; +import gov.nist.javax.sip.header.CSeq; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.DocumentException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.Dialog; +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.message.Request; + +/** + * @Author: huise23 + * @Date: 2022/8/10 16:54 + * @Description: + */ +@Slf4j +@Component +public class AckRequestProcessorImpl extends ISipRequestProcessorAbstract implements InitializingBean, ISipRequestProcessor { + + private final String METHOD = "ACK"; + + @Resource + private SipProcessorFactoryImpl sipProcessorFactory; + + @Override + public void afterPropertiesSet() { + sipProcessorFactory.addRequestProcessor(METHOD, this); + } + + + @Override + public void process(RequestEvent evt) { + Request request = evt.getRequest(); + Dialog dialog = evt.getDialog(); + try { + log.info("接收到bye请求:{}", getRootElement(evt)); + CSeq csReq = (CSeq) request.getHeader(CSeq.NAME); + Request ackRequest = dialog.createAck(csReq.getSeqNumber()); + dialog.sendAck(ackRequest); + log.info("send ack : " + ackRequest.toString()); + } catch (SipException | InvalidArgumentException | DocumentException e) { + e.printStackTrace(); + } + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/ByeRequestProcessorImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/ByeRequestProcessorImpl.java new file mode 100644 index 0000000..a872d5f --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/ByeRequestProcessorImpl.java @@ -0,0 +1,63 @@ +package com.dite.znpt.monitor.sip.transmit.request.impl; + +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.media.zlm.ZlmService; +import com.dite.znpt.monitor.service.DeviceVideoService; +import com.dite.znpt.monitor.sip.transmit.SipProcessorFactoryImpl; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessor; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessorAbstract; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.*; +import javax.sip.address.SipURI; +import javax.sip.header.FromHeader; +import javax.sip.header.HeaderAddress; +import javax.sip.message.Response; +import java.text.ParseException; + +/** + * @Author: huise23 + * @Date: 2022/8/10 16:55 + * @Description: + */ +@Slf4j +@Component +public class ByeRequestProcessorImpl extends ISipRequestProcessorAbstract implements InitializingBean, ISipRequestProcessor { + + private final String METHOD = "BYE"; + + @Resource + private SipProcessorFactoryImpl sipProcessorFactory; + + @Resource + private DeviceVideoService deviceVideoService; + + @Resource + private ZlmService zlmService; + + @Override + public void afterPropertiesSet() { + sipProcessorFactory.addRequestProcessor(METHOD, this); + } + @Override + public void process(RequestEvent evt) { + try { + responseAck(evt, Response.OK); + Dialog dialog = evt.getDialog(); + if (dialog == null) { + return; + } + if (dialog.getState().equals(DialogState.TERMINATED)) { + String channelCode = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser(); + DeviceVideoEntity videoEntity = deviceVideoService.getByChannelCode(channelCode); + zlmService.release(videoEntity.getVideoCode(), channelCode); + } + } catch (SipException | InvalidArgumentException | ParseException e) { + e.printStackTrace(); + } + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/MessageRequestProcessorImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/MessageRequestProcessorImpl.java new file mode 100644 index 0000000..674bbf6 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/MessageRequestProcessorImpl.java @@ -0,0 +1,75 @@ +package com.dite.znpt.monitor.sip.transmit.request.impl; + +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.service.DeviceVideoService; +import com.dite.znpt.monitor.sip.transmit.SipProcessorFactoryImpl; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessor; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessorAbstract; +import com.dite.znpt.monitor.sip.transmit.request.impl.message.IMessageHandler; +import com.dite.znpt.monitor.sip.utils.XmlUtil; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.RequestEvent; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @Author: huise23 + * @Date: 2022/8/29 18:43 + * @Description: + */ +@Slf4j +@Component +public class MessageRequestProcessorImpl extends ISipRequestProcessorAbstract implements InitializingBean, ISipRequestProcessor { + + private final String METHOD = "MESSAGE"; + + public Map messageHandlerMap = new ConcurrentHashMap<>(); + + public void addHandler(String cmdType, IMessageHandler messageHandler) { + messageHandlerMap.put(cmdType, messageHandler); + } + + @Resource + private SipProcessorFactoryImpl sipProcessorFactory; + + @Resource + private DeviceVideoService deviceVideoService; + + @Override + public void afterPropertiesSet() { + sipProcessorFactory.addRequestProcessor(METHOD, this); + } + + @Override + public void process(RequestEvent event) { + try { + Element rootElement = getRootElement(event); + String cmd = XmlUtil.getText(rootElement,"CmdType"); + log.info("接收到message请求:{}", cmd); + IMessageHandler messageHandler = messageHandlerMap.get(cmd); + if(null == messageHandler){ + log.info("不支持的消息类型:{}", cmd); + return; + } + String videoCode = rootElement.element("DeviceID").getText(); + DeviceVideoEntity entity = deviceVideoService.getByCode(videoCode); + if(null == entity){ + log.info("设备未注册,国标编码:{}", videoCode); + return; + } + messageHandler.process(entity, event); + + } catch (DocumentException e) { + e.printStackTrace(); + } + } + + + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/RegisterRequestProcessorImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/RegisterRequestProcessorImpl.java new file mode 100644 index 0000000..b9c89d1 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/RegisterRequestProcessorImpl.java @@ -0,0 +1,156 @@ +package com.dite.znpt.monitor.sip.transmit.request.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.dite.znpt.monitor.constant.dict.DeviceStatus; +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.service.DeviceVideoService; +import com.dite.znpt.monitor.service.IpConfigService; +import com.dite.znpt.monitor.sip.config.SipConfig; +import com.dite.znpt.monitor.sip.transmit.SipProcessorFactoryImpl; +import com.dite.znpt.monitor.sip.transmit.cmd.ISipDeviceCommander; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessor; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessorAbstract; +import com.dite.znpt.monitor.sip.utils.DigestServerAuthenticationHelper; +import gov.nist.javax.sip.address.AddressImpl; +import gov.nist.javax.sip.address.SipUri; +import gov.nist.javax.sip.header.Expires; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.header.*; +import javax.sip.message.Request; +import javax.sip.message.Response; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.time.LocalDateTime; +import java.util.Calendar; +import java.util.Locale; + +/** + * @Author: huise23 + * @Date: 2022/8/10 16:53 + * @Description: + */ +@Slf4j +@Component +public class RegisterRequestProcessorImpl extends ISipRequestProcessorAbstract implements InitializingBean, ISipRequestProcessor { + + public final String METHOD = "REGISTER"; + + @Resource + private SipProcessorFactoryImpl sipProcessorFactory; + + @Resource + private SipConfig sipConfig; + + @Resource + private DeviceVideoService deviceVideoService; + + @Resource + private ISipDeviceCommander sipDeviceCommander; + + @Resource + private IpConfigService ipConfigService; + + @Override + public void afterPropertiesSet() { + sipProcessorFactory.addRequestProcessor(METHOD, this); + } + + @Override + public synchronized void process(RequestEvent evt) { + try { + log.info("收到注册请求,开始处理"); + Request request = evt.getRequest(); + Response response; + // 注册标志 + boolean registerFlag = false; + AuthorizationHeader authorHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); + + String password = sipConfig.getPassword(); + if (StrUtil.isNotBlank(password)) { + // 未携带授权头或者密码错误 回复401 + if (authorHeader == null && StrUtil.isNotEmpty(sipConfig.getPassword())) { + log.info("未携带授权头或者密码错误,回复401"); + response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request); + new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain()); + getServerTransaction(evt).sendResponse(response); + return; + } + // 校验密码是否正确 + boolean passwordCorrect = new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, sipConfig.getPassword()); + if (!passwordCorrect) { + response = getMessageFactory().createResponse(Response.FORBIDDEN, request); + response.setReasonPhrase("wrong password"); + log.info("[注册请求] 密码/SIP服务器ID错误, 回复403"); + getServerTransaction(evt).sendResponse(response); + return; + } + } + // 携带授权头并且密码正确 + response = getMessageFactory().createResponse(Response.OK, request); + // 添加date头 + response.addHeader(getHeaderFactory().createDateHeader(Calendar.getInstance(Locale.ENGLISH))); + ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME); + // 添加Contact头 + response.addHeader(request.getHeader(ContactHeader.NAME)); + // 添加Expires头 + response.addHeader(request.getExpires()); + // 获取到通信地址等信息 + FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME); + ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); + AddressImpl address = (AddressImpl) fromHeader.getAddress(); + SipUri uri = (SipUri) address.getURI(); + String videoCode = uri.getUser(); + DeviceVideoEntity entity = deviceVideoService.getByCode(videoCode); + log.info("entity:"+JSONUtil.toJsonStr(entity)); + if (expiresHeader != null && expiresHeader.getExpires() == 0) { + // 注册失败 + registerFlag = false; + } else { + // 注册成功 + registerFlag = true; + if (entity == null) { + entity = new DeviceVideoEntity(); + entity.setStreamMode("UDP"); + entity.setVideoCode(videoCode); + entity.setVideoCode(DeviceStatus.OFFLINE.getValue()); + } + entity.setVideoCode(videoCode); + entity.setExpires(expiresHeader.getExpires()); + + String ip = viaHeader.getHost(); + int rPort = viaHeader.getRPort(); + // 解析本地地址替代 + if (ObjectUtils.isEmpty(ip) || rPort == -1) { + ip = viaHeader.getHost(); + rPort = viaHeader.getPort(); + } + entity.setIp(ip); + entity.setPort(rPort); + entity.setHostAddress(ip.concat(":").concat(String.valueOf(rPort))); + // 判断TCP还是UDP + ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); + entity.setTransport("TCP".equals(reqViaHeader.getTransport()) ? "TCP" : "UDP"); + entity.setCharset("GB2312"); + } + getServerTransaction(evt).sendResponse(response); + if(registerFlag){ + entity.setRegisterTime(LocalDateTime.now()); + deviceVideoService.online(entity); + }else{ + deviceVideoService.offline(videoCode); + } + } catch (SipException | InvalidArgumentException | NoSuchAlgorithmException | ParseException e) { + e.printStackTrace(); + } + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/IMessageHandler.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/IMessageHandler.java new file mode 100644 index 0000000..36db6b6 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/IMessageHandler.java @@ -0,0 +1,20 @@ +package com.dite.znpt.monitor.sip.transmit.request.impl.message; + +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; + +import javax.sip.RequestEvent; + +/** + * @Author: huise23 + * @Date: 2022/9/1 13:48 + * @Description: + */ +public interface IMessageHandler { + + /** + * 处理消息 + * @param evt + * @return + */ + void process(DeviceVideoEntity entity, RequestEvent evt); +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/CatalogHandlerImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/CatalogHandlerImpl.java new file mode 100644 index 0000000..bd28287 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/CatalogHandlerImpl.java @@ -0,0 +1,137 @@ +package com.dite.znpt.monitor.sip.transmit.request.impl.message.query; + +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.dite.znpt.monitor.constant.dict.DeviceStatus; +import com.dite.znpt.monitor.domain.entity.DeviceVideoChannelEntity; +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.service.DeviceVideoChannelService; +import com.dite.znpt.monitor.service.DeviceVideoService; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessorAbstract; +import com.dite.znpt.monitor.sip.transmit.request.impl.MessageRequestProcessorImpl; +import com.dite.znpt.monitor.sip.transmit.request.impl.message.IMessageHandler; +import com.dite.znpt.monitor.sip.utils.XmlUtil; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.message.Response; +import java.text.ParseException; +import java.time.LocalDateTime; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @Author: huise23 + * @Date: 2022/9/1 13:33 + * @Description: + */ +@Slf4j +@Component +public class CatalogHandlerImpl extends ISipRequestProcessorAbstract implements InitializingBean, IMessageHandler { + + private final static String CMD_TYPE = "Catalog"; + + @Resource + private MessageRequestProcessorImpl messageRequestProcessor; + + @Resource + private DeviceVideoService deviceVideoService; + + @Resource + private DeviceVideoChannelService deviceVideoChannelService; + + @Override + public void afterPropertiesSet(){ + messageRequestProcessor.addHandler(CMD_TYPE, this); + } + + /** + * 处理消息 + * + * @param evt + * @param entity + * @return + */ + @Override + public void process(DeviceVideoEntity entity, RequestEvent evt) { + try { + Element rootElement = getRootElement(evt); + log.info("接收到catalog消息"); + Element deviceChannelElement = rootElement.element("DeviceList"); + Element sumNumElement = rootElement.element("SumNum"); + Element snElement = rootElement.element("SN"); + if (snElement == null || sumNumElement == null || deviceChannelElement == null) { + log.info("报文异常:{},{},{}", deviceChannelElement.getText(), sumNumElement.getText(), snElement.getText()); + responseAck(evt, Response.BAD_REQUEST); + return; + } + Iterator deviceListIterator = deviceChannelElement.elementIterator(); + if (deviceListIterator != null) { + List channelEntityList = deviceVideoChannelService.selectDeviceVideoChannelByVideoCode(entity.getVideoId()); + Map channelCodeMapEntity = channelEntityList.stream().collect(Collectors.toMap(k->k.getChannelCode(), v->v)); + // 遍历DeviceList + while (deviceListIterator.hasNext()) { + Element itemDevice = deviceListIterator.next(); + Element channelDeviceElement = itemDevice.element("DeviceID"); + + int parental = NumberUtil.isInteger(XmlUtil.getText(itemDevice,"Parental")) ? NumberUtil.parseInt(XmlUtil.getText(itemDevice,"Parental")): 0; + String parentId = XmlUtil.getText(itemDevice, "ParentID"); + if (channelDeviceElement == null || parental != 0 || StrUtil.isEmpty(parentId)) { + continue; + } + String channelCode = channelDeviceElement.getText(); + DeviceVideoChannelEntity channelEntity = channelCodeMapEntity.containsKey(channelCode) ? channelCodeMapEntity.get(channelCode) : new DeviceVideoChannelEntity(); + channelEntity.setChannelCode(channelCode); + channelEntity.setVideoId(entity.getVideoId()); + channelEntity.setChannelName( ObjectUtil.isNotEmpty(itemDevice.element("Name")) ? itemDevice.element("Name").getText(): ""); + channelEntity.setManufacture(XmlUtil.getText(itemDevice,"Manufacturer")); + channelEntity.setModel(XmlUtil.getText(itemDevice,"Model")); + channelEntity.setOwner(XmlUtil.getText(itemDevice,"Owner")); + channelEntity.setCivilCode(XmlUtil.getText(itemDevice,"CivilCode")); + channelEntity.setBlock(XmlUtil.getText(itemDevice,"Block")); + channelEntity.setAddress(XmlUtil.getText(itemDevice,"Address")); + channelEntity.setParental(parental); + channelEntity.setParentId(parentId); + channelEntity.setSafetyWay(NumberUtil.isInteger(XmlUtil.getText(itemDevice,"SafetyWay")) ? NumberUtil.parseInt(XmlUtil.getText(itemDevice,"SafetyWay")):0); + channelEntity.setRegisterWay(NumberUtil.isInteger(XmlUtil.getText(itemDevice,"RegisterWay"))? NumberUtil.parseInt(XmlUtil.getText(itemDevice,"RegisterWay")): 1); + channelEntity.setCertNum(XmlUtil.getText(itemDevice,"CertNum")); + channelEntity.setCertifiable(NumberUtil.isInteger(XmlUtil.getText(itemDevice,"Certifiable"))? NumberUtil.parseInt(XmlUtil.getText(itemDevice,"Certifiable")): 0); + channelEntity.setErrCode(NumberUtil.isInteger(XmlUtil.getText(itemDevice,"ErrCode"))? NumberUtil.parseInt(XmlUtil.getText(itemDevice,"ErrCode")): 0); + channelEntity.setCertNum(XmlUtil.getText(itemDevice,"CertNum")); + channelEntity.setEndTime(XmlUtil.getText(itemDevice,"EndTime")); + channelEntity.setSecrecy(XmlUtil.getText(itemDevice,"Secrecy")); + channelEntity.setIpAddress(XmlUtil.getText(itemDevice,"IPAddress")); + channelEntity.setPort(NumberUtil.isInteger(XmlUtil.getText(itemDevice,"Port"))? NumberUtil.parseInt(XmlUtil.getText(itemDevice,"Port")): 0); + channelEntity.setPassword(XmlUtil.getText(itemDevice,"Password")); + Element statusElement = itemDevice.element("Status"); + String status = statusElement != null && "OFF".equals(statusElement.getText())? DeviceStatus.OFFLINE.getValue() : DeviceStatus.ONLINE.getValue(); + channelEntity.setStatus(status); + channelEntity.setLongitude(NumberUtil.isDouble(XmlUtil.getText(itemDevice,"Longitude"))?NumberUtil.parseDouble(XmlUtil.getText(itemDevice,"Longitude")): 0.00); + channelEntity.setLatitude(NumberUtil.isDouble(XmlUtil.getText(itemDevice,"Latitude"))?NumberUtil.parseDouble(XmlUtil.getText(itemDevice,"Latitude")): 0.00); + channelEntity.setUpdateTime(LocalDateTime.now()); + channelEntityList.add(channelEntity); + } + deviceVideoChannelService.saveOrUpdateBatch(channelEntityList); + + entity.setChannelCount(channelEntityList.size()); + entity.setUpdateTime(LocalDateTime.now()); + deviceVideoService.updateById(entity); + + responseAck(evt, Response.OK); + } + } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { + e.printStackTrace(); + } + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/DeviceInfoHandlerImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/DeviceInfoHandlerImpl.java new file mode 100644 index 0000000..7e44cb4 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/DeviceInfoHandlerImpl.java @@ -0,0 +1,68 @@ +package com.dite.znpt.monitor.sip.transmit.request.impl.message.query; + +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.service.DeviceVideoService; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessorAbstract; +import com.dite.znpt.monitor.sip.transmit.request.impl.MessageRequestProcessorImpl; +import com.dite.znpt.monitor.sip.transmit.request.impl.message.IMessageHandler; +import com.dite.znpt.monitor.sip.utils.XmlUtil; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.message.Response; +import java.text.ParseException; +import java.time.LocalDateTime; + +/** + * @Author: huise23 + * @Date: 2022/9/1 13:34 + * @Description: blog.csdn.net/Marvin1311/article/details/98845468 + */ +@Slf4j +@Component +public class DeviceInfoHandlerImpl extends ISipRequestProcessorAbstract implements InitializingBean, IMessageHandler { + + private final static String CMD_TYPE = "DeviceInfo"; + + @Resource + private MessageRequestProcessorImpl messageRequestProcessor; + + @Resource + private DeviceVideoService deviceVideoService; + + @Override + public void afterPropertiesSet() { + messageRequestProcessor.addHandler(CMD_TYPE, this); + } + + /** + * 处理消息 + * + * @param evt + * @param entity + * @return + */ + @Override + public void process(DeviceVideoEntity entity, RequestEvent evt) { + try { + Element rootElement = getRootElement(evt); + log.info("接收到deviceInfo消息"); + entity.setVideoName(XmlUtil.getText(rootElement,"DeviceName")); + entity.setManufacturer(XmlUtil.getText(rootElement,"Manufacturer")); + entity.setModel(XmlUtil.getText(rootElement,"Model")); + entity.setFirmware(XmlUtil.getText(rootElement,"Firmware")); + entity.setUpdateTime(LocalDateTime.now()); + deviceVideoService.updateById(entity); + responseAck(evt, Response.OK); + } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { + e.printStackTrace(); + } + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/DeviceStatusHandlerImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/DeviceStatusHandlerImpl.java new file mode 100644 index 0000000..8a28193 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/DeviceStatusHandlerImpl.java @@ -0,0 +1,68 @@ +package com.dite.znpt.monitor.sip.transmit.request.impl.message.query; + +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.service.DeviceVideoService; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessorAbstract; +import com.dite.znpt.monitor.sip.transmit.request.impl.MessageRequestProcessorImpl; +import com.dite.znpt.monitor.sip.transmit.request.impl.message.IMessageHandler; +import lombok.extern.slf4j.Slf4j; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.message.Response; +import java.text.ParseException; +import java.util.Objects; + +/** + * @Author: huise23 + * @Date: 2022/9/1 13:34 + * @Description: + */ +@Slf4j +@Component +public class DeviceStatusHandlerImpl extends ISipRequestProcessorAbstract implements InitializingBean, IMessageHandler { + + private final static String CMD_TYPE = "DeviceStatus"; + + @Resource + private MessageRequestProcessorImpl messageRequestProcessor; + + @Override + public void afterPropertiesSet() { + messageRequestProcessor.addHandler(CMD_TYPE, this); + } + + + @Resource + private DeviceVideoService deviceVideoService; + /** + * 处理消息 + * + * @param evt + * @param entity + * @return + */ + @Override + public void process(DeviceVideoEntity entity, RequestEvent evt) { + try { + Element rootElement = getRootElement(evt); + log.info("接收到DeviceStatus消息"); + responseAck(evt, Response.OK); + Element onlineElement = rootElement.element("Online"); + String text = onlineElement.getText(); + if (Objects.equals(text.trim().toUpperCase(), "ONLINE")) { + deviceVideoService.online(entity); + }else { + deviceVideoService.offline(entity.getVideoCode()); + } + } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { + e.printStackTrace(); + } + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/KeepaliveHandlerImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/KeepaliveHandlerImpl.java new file mode 100644 index 0000000..47927b8 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/request/impl/message/query/KeepaliveHandlerImpl.java @@ -0,0 +1,89 @@ +package com.dite.znpt.monitor.sip.transmit.request.impl.message.query; + +import com.alibaba.fastjson.JSON; +import com.dite.znpt.monitor.constant.dict.DeviceStatus; +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.service.DeviceVideoService; +import com.dite.znpt.monitor.sip.transmit.request.ISipRequestProcessorAbstract; +import com.dite.znpt.monitor.sip.transmit.request.impl.MessageRequestProcessorImpl; +import com.dite.znpt.monitor.sip.transmit.request.impl.message.IMessageHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.header.ViaHeader; +import javax.sip.message.Response; +import java.text.ParseException; +import java.time.LocalDateTime; + +/** + * @Author: huise23 + * @Date: 2022/9/1 13:35 + * @Description: + */ +@Slf4j +@Component +public class KeepaliveHandlerImpl extends ISipRequestProcessorAbstract implements InitializingBean, IMessageHandler { + + private final static String CMD_TYPE = "Keepalive"; + + @Resource + private MessageRequestProcessorImpl messageRequestProcessor; + + @Resource + private DeviceVideoService deviceVideoService; + + @Override + public void afterPropertiesSet() { + messageRequestProcessor.addHandler(CMD_TYPE, this); + } + + /** + * 处理消息 + * + * @param evt + * @param entity + * @return + */ + @Override + public void process(DeviceVideoEntity entity, RequestEvent evt) { + try { + log.info("接收到keepalive消息"); + // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息 + // 获取到通信地址等信息 + ViaHeader viaHeader = (ViaHeader) evt.getRequest().getHeader(ViaHeader.NAME); + log.info("viaHeader:{}", JSON.toJSONString(viaHeader)); + String ip = viaHeader.getHost(); + int rPort = viaHeader.getRPort(); + // 解析本地地址替代 + if (ObjectUtils.isEmpty(ip) || rPort == -1) { + ip = viaHeader.getHost(); + rPort = viaHeader.getPort(); + } + log.info("port:{}",rPort); + if (entity.getPort() != rPort) { + entity.setPort(rPort); + entity.setHostAddress(ip.concat(":").concat(String.valueOf(rPort))); + } + entity.setKeepaliveTime(LocalDateTime.now()); + // 回复200 OK + responseAck(evt, Response.OK); + if (DeviceStatus.ONLINE.getValue().equals(entity.getStatus())) { + entity.setUpdateTime(LocalDateTime.now()); + deviceVideoService.updateById(entity); + }else { + // 对于已经离线的设备判断他的注册是否已经过期 + if (deviceVideoService.expire(entity)){ + deviceVideoService.online(entity); + } + } + } catch (SipException | InvalidArgumentException | ParseException e) { + e.printStackTrace(); + } + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/ISipResponseProcessor.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/ISipResponseProcessor.java new file mode 100644 index 0000000..255e4c2 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/ISipResponseProcessor.java @@ -0,0 +1,13 @@ +package com.dite.znpt.monitor.sip.transmit.response; + + +import javax.sip.ResponseEvent; + +/** + * @Author: huise23 + * @Date: 2022/8/10 16:47 + * @Description: + */ +public interface ISipResponseProcessor { + void process(ResponseEvent evt); +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/ISipResponseProcessorAbstract.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/ISipResponseProcessorAbstract.java new file mode 100644 index 0000000..30e3a4e --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/ISipResponseProcessorAbstract.java @@ -0,0 +1,12 @@ +package com.dite.znpt.monitor.sip.transmit.response; + +import org.springframework.beans.factory.InitializingBean; + +/** + * @Author: huise23 + * @Date: 2022/8/29 17:59 + * @Description: + */ +public abstract class ISipResponseProcessorAbstract implements InitializingBean, ISipResponseProcessor{ + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/impl/InviteResponseProcessorImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/impl/InviteResponseProcessorImpl.java new file mode 100644 index 0000000..bb82e96 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/response/impl/InviteResponseProcessorImpl.java @@ -0,0 +1 @@ +package com.dite.znpt.monitor.sip.transmit.response.impl; import com.dite.znpt.monitor.sip.config.SipConfig; import com.dite.znpt.monitor.sip.transmit.SipProcessorFactoryImpl; import com.dite.znpt.monitor.sip.transmit.response.ISipResponseProcessorAbstract; import gov.nist.javax.sip.ResponseEventExt; import gov.nist.javax.sip.stack.SIPDialog; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.sdp.SdpFactory; import javax.sdp.SdpParseException; import javax.sdp.SessionDescription; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; import javax.sip.SipFactory; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.header.CSeqHeader; import javax.sip.message.Request; import javax.sip.message.Response; import java.text.ParseException; /** * @Description:处理INVITE响应 * @author: swwheihei * @date: 2020年5月3日 下午4:43:52 */ @Slf4j @Component public class InviteResponseProcessorImpl extends ISipResponseProcessorAbstract { private final String method = "INVITE"; @Resource private SipConfig sipConfig; @Resource private SipFactory sipFactory; @Resource private SipProcessorFactoryImpl sipProcessorFactory; @Override public void afterPropertiesSet(){ sipProcessorFactory.addResponseProcessor(method, this); } /** * 处理invite响应 * @param evt 响应消息 */ @Override public void process(ResponseEvent evt) { try { Response response = evt.getResponse(); int statusCode = response.getStatusCode(); // trying不会回复 if (statusCode == Response.TRYING) { } // 成功响应 // 下发ack if (statusCode == Response.OK) { ResponseEventExt event = (ResponseEventExt)evt; SIPDialog dialog = (SIPDialog)evt.getDialog(); CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME); Request reqAck = dialog.createAck(cseq.getSeqNumber()); SipURI requestUri = (SipURI) reqAck.getRequestURI(); String contentString = new String(response.getRawContent()); // jainSip不支持y=字段, 移除以解析。 int ssrcIndex = contentString.indexOf("y="); // 检查是否有y字段 SessionDescription sdp; if (ssrcIndex >= 0) { //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 String substring = contentString.substring(0, contentString.indexOf("y=")); sdp = SdpFactory.getInstance().createSessionDescription(substring); } else { sdp = SdpFactory.getInstance().createSessionDescription(contentString); } requestUri.setUser(sdp.getOrigin().getUsername()); requestUri.setHost(event.getRemoteIpAddress()); requestUri.setPort(event.getRemotePort()); reqAck.setRequestURI(requestUri); Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp() + ":" + sipConfig.getPort())); reqAck.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress)); log.info("[invite回复ack] {}-> {}:{} ",requestUri, event.getRemoteIpAddress(), event.getRemotePort()); dialog.sendAck(reqAck); } } catch (InvalidArgumentException | SipException e) { e.printStackTrace(); } catch (ParseException e) { throw new RuntimeException(e); } catch (SdpParseException e) { throw new RuntimeException(e); } } } \ No newline at end of file diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/timeout/ITimeoutProcessor.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/timeout/ITimeoutProcessor.java new file mode 100644 index 0000000..36e1201 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/timeout/ITimeoutProcessor.java @@ -0,0 +1,7 @@ +package com.dite.znpt.monitor.sip.transmit.timeout; + +import javax.sip.TimeoutEvent; + +public interface ITimeoutProcessor { + void process(TimeoutEvent event); +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/timeout/impl/TimeoutProcessorImpl.java b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/timeout/impl/TimeoutProcessorImpl.java new file mode 100644 index 0000000..a750ef1 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/transmit/timeout/impl/TimeoutProcessorImpl.java @@ -0,0 +1,34 @@ +package com.dite.znpt.monitor.sip.transmit.timeout.impl; + +import com.dite.znpt.monitor.sip.transmit.SipProcessorFactoryImpl; +import com.dite.znpt.monitor.sip.transmit.timeout.ITimeoutProcessor; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.TimeoutEvent; +import javax.sip.header.CallIdHeader; + +/** + * @author huise23 + */ +@Component +public class TimeoutProcessorImpl implements InitializingBean, ITimeoutProcessor { + + @Autowired + private SipProcessorFactoryImpl sipProcessorFactory; + + + @Override + public void afterPropertiesSet() { + sipProcessorFactory.addTimeoutProcessor(this); + } + + @Override + public void process(TimeoutEvent event) { + CallIdHeader callIdHeader = event.getClientTransaction().getDialog().getCallId(); + String callId = callIdHeader.getCallId(); + // TODO + + } +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/utils/DigestServerAuthenticationHelper.java b/sip/src/main/java/com/dite/znpt/monitor/sip/utils/DigestServerAuthenticationHelper.java new file mode 100644 index 0000000..ce07ee5 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/utils/DigestServerAuthenticationHelper.java @@ -0,0 +1,210 @@ +/* +* Conditions Of Use +* +* This software was developed by employees of the National Institute of +* Standards and Technology (NIST), an agency of the Federal Government. +* Pursuant to title 15 Untied States Code Section 105, works of NIST +* employees are not subject to copyright protection in the United States +* and are considered to be in the public domain. As a result, a formal +* license is not needed to use the software. +* +* This software is provided by NIST as a service and is expressly +* provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED +* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT +* AND DATA ACCURACY. NIST does not warrant or make any representations +* regarding the use of the software or the results thereof, including but +* not limited to the correctness, accuracy, reliability or usefulness of +* the software. +* +* Permission to use this software is contingent upon your acceptance +* of the terms of this agreement +* +* . +* +*/ +package com.dite.znpt.monitor.sip.utils; + +import gov.nist.core.InternalErrorHandler; + +import javax.sip.address.URI; +import javax.sip.header.AuthorizationHeader; +import javax.sip.header.HeaderFactory; +import javax.sip.header.WWWAuthenticateHeader; +import javax.sip.message.Request; +import javax.sip.message.Response; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; +import java.util.Random; + +/** + * Implements the HTTP digest authentication method server side functionality. + * + * @author M. Ranganathan + * @author Marc Bednarek + */ + +public class DigestServerAuthenticationHelper { + + private MessageDigest messageDigest; + + public static final String DEFAULT_ALGORITHM = "MD5"; + public static final String DEFAULT_SCHEME = "Digest"; + + + + + /** to hex converter */ + private static final char[] toHex = { '0', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + /** + * Default constructor. + * @throws NoSuchAlgorithmException + */ + public DigestServerAuthenticationHelper() + throws NoSuchAlgorithmException { + messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM); + } + + public static String toHexString(byte b[]) { + int pos = 0; + char[] c = new char[b.length * 2]; + for (int i = 0; i < b.length; i++) { + c[pos++] = toHex[(b[i] >> 4) & 0x0F]; + c[pos++] = toHex[b[i] & 0x0f]; + } + return new String(c); + } + + /** + * Generate the challenge string. + * + * @return a generated nonce. + */ + private String generateNonce() { + // Get the time of day and run MD5 over it. + Date date = new Date(); + long time = date.getTime(); + Random rand = new Random(); + long pad = rand.nextLong(); + String nonceString = (new Long(time)).toString() + + (new Long(pad)).toString(); + byte mdbytes[] = messageDigest.digest(nonceString.getBytes()); + // Convert the mdbytes array into a hex string. + return toHexString(mdbytes); + } + + public Response generateChallenge(HeaderFactory headerFactory, Response response, String realm) { + try { + WWWAuthenticateHeader proxyAuthenticate = headerFactory + .createWWWAuthenticateHeader(DEFAULT_SCHEME); + proxyAuthenticate.setParameter("realm", realm); + proxyAuthenticate.setParameter("nonce", generateNonce()); + proxyAuthenticate.setParameter("opaque", ""); + proxyAuthenticate.setParameter("stale", "FALSE"); + proxyAuthenticate.setParameter("algorithm", DEFAULT_ALGORITHM); + response.setHeader(proxyAuthenticate); + } catch (Exception ex) { + InternalErrorHandler.handleException(ex); + } + return response; + } + /** + * Authenticate the inbound request. + * + * @param request - the request to authenticate. + * @param hashedPassword -- the MD5 hashed string of username:realm:plaintext password. + * + * @return true if authentication succeded and false otherwise. + */ + public boolean doAuthenticateHashedPassword(Request request, String hashedPassword) { + AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); + if ( authHeader == null ) return false; + String realm = authHeader.getRealm(); + String username = authHeader.getUsername(); + + if ( username == null || realm == null ) { + return false; + } + + String nonce = authHeader.getNonce(); + URI uri = authHeader.getURI(); + if (uri == null) { + return false; + } + + + + String A2 = request.getMethod().toUpperCase() + ":" + uri.toString(); + String HA1 = hashedPassword; + + + byte[] mdbytes = messageDigest.digest(A2.getBytes()); + String HA2 = toHexString(mdbytes); + + String cnonce = authHeader.getCNonce(); + String KD = HA1 + ":" + nonce; + if (cnonce != null) { + KD += ":" + cnonce; + } + KD += ":" + HA2; + mdbytes = messageDigest.digest(KD.getBytes()); + String mdString = toHexString(mdbytes); + String response = authHeader.getResponse(); + + + return mdString.equals(response); + } + + /** + * Authenticate the inbound request given plain text password. + * + * @param request - the request to authenticate. + * @param pass -- the plain text password. + * + * @return true if authentication succeded and false otherwise. + */ + public boolean doAuthenticatePlainTextPassword(Request request, String pass) { + AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); + if ( authHeader == null ) return false; + String realm = authHeader.getRealm(); + String username = authHeader.getUsername(); + + + if ( username == null || realm == null ) { + return false; + } + + + String nonce = authHeader.getNonce(); + URI uri = authHeader.getURI(); + if (uri == null) { + return false; + } + + + String A1 = username + ":" + realm + ":" + pass; + String A2 = request.getMethod().toUpperCase() + ":" + uri.toString(); + byte mdbytes[] = messageDigest.digest(A1.getBytes()); + String HA1 = toHexString(mdbytes); + + + mdbytes = messageDigest.digest(A2.getBytes()); + String HA2 = toHexString(mdbytes); + + String cnonce = authHeader.getCNonce(); + String KD = HA1 + ":" + nonce; + if (cnonce != null) { + KD += ":" + cnonce; + } + KD += ":" + HA2; + mdbytes = messageDigest.digest(KD.getBytes()); + String mdString = toHexString(mdbytes); + String response = authHeader.getResponse(); + return mdString.equals(response); + + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/utils/XmlUtil.java b/sip/src/main/java/com/dite/znpt/monitor/sip/utils/XmlUtil.java new file mode 100644 index 0000000..934490a --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/utils/XmlUtil.java @@ -0,0 +1,95 @@ +package com.dite.znpt.monitor.sip.utils; + +import lombok.extern.slf4j.Slf4j; +import org.dom4j.Attribute; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; + +import java.io.StringReader; +import java.util.*; + +/** + * 基于dom4j的工具包 + * + * + */ +@Slf4j +public class XmlUtil +{ + /** + * 解析XML为Document对象 + * + * @param xml 被解析的XMl + * @return Document + */ + public static Element parseXml(String xml){ + Document document = null; + // + StringReader sr = new StringReader(xml); + SAXReader saxReader = new SAXReader(); + try{ + document = saxReader.read(sr); + }catch (DocumentException e){ + log.error("解析失败", e); + } + return null == document ? null : document.getRootElement(); + } + + /** + * 获取element对象的text的值 + * + * @param em 节点的对象 + * @param tag 节点的tag + * @return 节点 + */ + public static String getText(Element em, String tag){ + if (null == em){ + return null; + } + Element e = em.element(tag); + // + return null == e ? null : e.getText(); + } + + /** + * 递归解析xml节点,适用于 多节点数据 + * + * @param node node + * @param nodeName nodeName + * @return List> + */ + public static List> listNodes(Element node, String nodeName){ + if (null == node){ + return null; + } + // 初始化返回 + List> listMap = new ArrayList>(); + // 首先获取当前节点的所有属性节点 + List list = node.attributes(); + + Map map = null; + // 遍历属性节点 + for (Attribute attribute : list){ + if (nodeName.equals(node.getName())){ + if (null == map){ + map = new HashMap(); + listMap.add(map); + } + // 取到的节点属性放到map中 + map.put(attribute.getName(), attribute.getValue()); + } + + } + // 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称 + // 使用递归 + Iterator iterator = node.elementIterator(); + while (iterator.hasNext()){ + Element e = iterator.next(); + listMap.addAll(listNodes(e, nodeName)); + } + return listMap; + } + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceAlarmVo.java b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceAlarmVo.java new file mode 100644 index 0000000..59023bb --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceAlarmVo.java @@ -0,0 +1,49 @@ +package com.dite.znpt.monitor.sip.vo; + +import lombok.Data; + +@Data +public class DeviceAlarmVo { + + /** + * 设备Id + */ + private String deviceId; + + /** + * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级 警情- + */ + private String alarmPriorit; + + /** + * 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警, + * 7其他报警;可以为直接组合如12为电话报警或 设备报警- + */ + private String alarmMethod; + + /** + * 报警时间 + */ + private String alarmTime; + + /** + * 报警内容描述 + */ + private String alarmDescription; + + /** + * 经度 + */ + private double longitude; + + /** + * 纬度 + */ + private double latitude; + + /** + * 报警类型 + */ + private String alarmType; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceChannelVo.java b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceChannelVo.java new file mode 100644 index 0000000..4e56103 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceChannelVo.java @@ -0,0 +1,129 @@ +package com.dite.znpt.monitor.sip.vo; + +import lombok.Data; + +@Data +public class DeviceChannelVo { + + /** + * 通道id + */ + private String channelId; + + /** + * 通道名 + */ + private String name; + + /** + * 生产厂商 + */ + private String manufacture; + + /** + * 型号 + */ + private String model; + + /** + * 设备归属 + */ + private String owner; + + /** + * 行政区域 + */ + private String civilCode; + + /** + * 警区 + */ + private String block; + + /** + * 安装地址 + */ + private String address; + + /** + * 是否有子设备 1有, 0没有 + */ + private int parental; + + /** + * 父级id + */ + private String parentId; + + /** + * 信令安全模式 缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式 + */ + private int safetyWay; + + /** + * 注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式 + */ + private int registerWay; + + /** + * 证书序列号 + */ + private String certNum; + + /** + * 证书有效标识 缺省为0;证书有效标识:0:无效1: 有效 + */ + private int certifiable; + + /** + * 证书无效原因码 + */ + private int errCode; + + /** + * 证书终止有效期 + */ + private String endTime; + + /** + * 保密属性 缺省为0; 0:不涉密, 1:涉密 + */ + private String secrecy; + + /** + * IP地址 + */ + private String ipAddress; + + /** + * 端口号 + */ + private int port; + + /** + * 密码 + */ + private String password; + + /** + * 在线/离线 + * 1在线,0离线 + * 默认在线 + * 信令: + * ON + * OFF + * 遇到过NVR下的IPC下发信令可以推流, 但是 Status 响应 OFF + */ + private int status; + + /** + * 经度 + */ + private double longitude; + + /** + * 纬度 + */ + private double latitude; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceVo.java b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceVo.java new file mode 100644 index 0000000..6e7f47b --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/DeviceVo.java @@ -0,0 +1,56 @@ +package com.dite.znpt.monitor.sip.vo; + +import lombok.Data; + +import java.util.Map; + +@Data +public class DeviceVo { + + /** + * 设备Id + */ + private String deviceId; + + /** + * 设备名 + */ + private String name; + + /** + * 生产厂商 + */ + private String manufacturer; + + /** + * 型号 + */ + private String model; + + /** + * 固件版本 + */ + private String firmware; + + /** + * 传输协议 + * UDP/TCP + */ + private String transport; + + /** + * wan地址 + */ + private HostVo hostVo; + + /** + * 在线 + */ + private int online; + + /** + * 通道列表 + */ + private Map channelMap; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/vo/HostVo.java b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/HostVo.java new file mode 100644 index 0000000..fdda095 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/HostVo.java @@ -0,0 +1,12 @@ +package com.dite.znpt.monitor.sip.vo; + +import lombok.Data; + +@Data +public class HostVo { + + private String ip; + private int port; + private String address; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/vo/RecordItemVo.java b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/RecordItemVo.java new file mode 100644 index 0000000..a9111b0 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/RecordItemVo.java @@ -0,0 +1,31 @@ +package com.dite.znpt.monitor.sip.vo; + +import lombok.Data; + +/** + * @Description:设备录像 + * @author: + * @date: + */ +@Data +public class RecordItemVo { + + private String deviceId; + + private String name; + + private String filePath; + + private String address; + + private String startTime; + + private String endTime; + + private int secrecy; + + private String type; + + private String recorderId; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/sip/vo/RecordVo.java b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/RecordVo.java new file mode 100644 index 0000000..f2ba518 --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/sip/vo/RecordVo.java @@ -0,0 +1,23 @@ +package com.dite.znpt.monitor.sip.vo; + +import lombok.Data; + +import java.util.List; + +/** + * @Description:设备录像信息 + * @author: + * @date: + */ +@Data +public class RecordVo { + + private String deviceId; + + private String name; + + private int sumNum; + + private List recordItemVoList; + +} diff --git a/sip/src/main/java/com/dite/znpt/monitor/utils/DictUtils.java b/sip/src/main/java/com/dite/znpt/monitor/utils/DictUtils.java new file mode 100644 index 0000000..d50f84a --- /dev/null +++ b/sip/src/main/java/com/dite/znpt/monitor/utils/DictUtils.java @@ -0,0 +1,33 @@ +package com.dite.znpt.monitor.utils; + +import com.dite.znpt.monitor.constant.dict.ValueAndLabel; + +import java.util.Arrays; + +/** + * 字典工具类 + * + * @author huise23 + * @since 2023-07-28 15:38:41 + */ +public class DictUtils { + + /** + * 通用方法,根据传入的枚举类和value返回对应的label值 + * + * @param enumClass 枚举类 + * @param value 值 + * @return {@link String } + * @author huise23 + * @since 2023-07-28 15:40:07 + */ + public static & ValueAndLabel> String getDictLabel(Class enumClass, String value) { + final String label = Arrays.stream(enumClass.getEnumConstants()) + .filter(enumItem -> enumItem.getValue().equals(value)) + .map(t->t.getLabel()) + .findFirst() + .orElse(null); + return label; + } + +} diff --git a/sip/src/main/resources/mapper/iot/DeviceVideoChannelMapper.xml b/sip/src/main/resources/mapper/iot/DeviceVideoChannelMapper.xml new file mode 100644 index 0000000..c43f856 --- /dev/null +++ b/sip/src/main/resources/mapper/iot/DeviceVideoChannelMapper.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + dvc.channel_id, dvc.channel_code, dvc.channel_name, dvc.address, dvc.camera_type, dvc.ptz_control, dvc.status, dvc.remark + + + + + + + + + + + + + + diff --git a/sip/src/main/resources/mapper/iot/DeviceVideoMapper.xml b/sip/src/main/resources/mapper/iot/DeviceVideoMapper.xml new file mode 100644 index 0000000..b0820c3 --- /dev/null +++ b/sip/src/main/resources/mapper/iot/DeviceVideoMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + dv.video_id, dv.video_code, dv.video_name, dv.transport, dv.stream_mode, dv.channel_count, dv.status, dv.register_time, + dv.keepalive_time, dv.channel_count, dv.ip, dv.port,dv.host_address, dv.manufacturer, dv.remark, dv.create_time + + + + + + diff --git a/sip/src/main/resources/mapper/iot/IpConfigMapper.xml b/sip/src/main/resources/mapper/iot/IpConfigMapper.xml new file mode 100644 index 0000000..339afe7 --- /dev/null +++ b/sip/src/main/resources/mapper/iot/IpConfigMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + ic.config_id, ic.ip + + + + + diff --git a/web/src/main/java/com/dite/znpt/web/controller/VideoController.java b/web/src/main/java/com/dite/znpt/web/controller/VideoController.java new file mode 100644 index 0000000..2eef147 --- /dev/null +++ b/web/src/main/java/com/dite/znpt/web/controller/VideoController.java @@ -0,0 +1,188 @@ +package com.dite.znpt.web.controller; + +import cn.hutool.core.bean.BeanUtil; +import com.dite.znpt.domain.PageResult; +import com.dite.znpt.domain.Result; +import com.dite.znpt.monitor.constant.IotRespMessage; +import com.dite.znpt.monitor.constant.dict.DeviceStatus; +import com.dite.znpt.monitor.constant.dict.SipTransferMode; +import com.dite.znpt.monitor.constant.dict.StreamTransferMode; +import com.dite.znpt.monitor.domain.entity.DeviceVideoEntity; +import com.dite.znpt.monitor.domain.req.MonitorConfigAddReq; +import com.dite.znpt.monitor.domain.resp.DeviceVideoResp; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelEditReq; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoChannelListResp; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoEditReq; +import com.dite.znpt.monitor.domain.vo.video.DeviceVideoListResp; +import com.dite.znpt.monitor.media.zlm.config.StreamMediaServerConfig; +import com.dite.znpt.monitor.service.DeviceVideoChannelService; +import com.dite.znpt.monitor.service.DeviceVideoService; +import com.dite.znpt.monitor.service.IpConfigService; +import com.dite.znpt.monitor.sip.config.SipConfig; +import com.dite.znpt.monitor.sip.transmit.cmd.ISipDeviceCommander; +import com.dite.znpt.monitor.utils.DictUtils; +import com.dite.znpt.util.PageUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; + +/** + * @Author: huise23 + * @Date: 2022/8/8 10:39 + * @Description: + */ +@Api(tags = "视频管理") +@RestController +@RequestMapping("/monitoring/video") +public class VideoController { + + @Resource + private SipConfig sipConfig; + + @Resource + private StreamMediaServerConfig streamMediaServerConfigService; + + @Resource + private DeviceVideoService deviceVideoService; + + @Resource + private IpConfigService ipConfigService; + + @Resource + private DeviceVideoChannelService deviceVideoChannelService; + + @Resource + private ISipDeviceCommander sipDeviceCommander; + + @ApiOperation(value = "获取信令服务器配置信息", notes = "iot:video:sip:view", httpMethod = "GET") + @GetMapping("/sipServerConfig") + public Result getSipServerConfig(){ + return Result.ok(BeanUtil.copyProperties(sipConfig, SipConfig.class)); + } + + @ApiOperation(value = "获取流媒体服务配置信息", notes = "iot:video:media:view", httpMethod = "GET") + @GetMapping("/streamMediaServerConfig") + public Result getStreamMediaServerConfig(){ + return Result.ok(BeanUtil.copyProperties(streamMediaServerConfigService, StreamMediaServerConfig.class)); + } + + @ApiOperation(value = "查询监控IP配置列表", notes = "iot:config:list", httpMethod = "GET") + @GetMapping("/config/list") + public Result> configList(){ + PageUtil.startPage(); + return Result.ok(ipConfigService.configList()); + } + + @ApiOperation(value = "配置监控IP列表,每次全量传ip列表", notes = "iot:config:add", httpMethod = "POST") + @PostMapping("/config/add") + public Result configAdd(@RequestBody MonitorConfigAddReq req){ + ipConfigService.configAdd(req); + return Result.ok(); + } + + @ApiOperation(value = "分页查询视频设备列表", notes = "iot:video:list", httpMethod = "GET") + @GetMapping("/device/page") + public PageResult pageDevice( + @RequestParam(value = "status", required = false) String status, + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "hostAddress", required = false) String hostAddress){ + PageUtil.startPage(); + return deviceVideoService.selectDeviceVideoList(status, keyword,hostAddress); + } + + @ApiOperation(value = "查看视频设备数量", notes = "iot:video:list", httpMethod = "GET") + @GetMapping("/device/count") + public Result countDevice(){ + return Result.ok(deviceVideoService.countDeviceVideoNum()); + } + + @ApiOperation(value = "查看视频设备详情", notes = "iot:video:view", httpMethod = "GET") + @GetMapping("/device/{videoId}") + public Result viewDevice(@PathVariable("videoId") Long videoId){ + final DeviceVideoEntity entity = deviceVideoService.getById(videoId); + final DeviceVideoResp resp = BeanUtil.copyProperties(entity, DeviceVideoResp.class); + resp.setStatusLabel(DictUtils.getDictLabel(DeviceStatus.class, resp.getStatus())); + resp.setStreamModeLabel(DictUtils.getDictLabel(StreamTransferMode.class, resp.getStreamMode())); + resp.setTransportLabel(DictUtils.getDictLabel(SipTransferMode.class, resp.getTransport())); + return Result.ok(resp); + } + + @ApiOperation(value = "编辑视频设备", notes = "iot:video:edit", httpMethod = "PUT") + @PutMapping("/device/{videoId}") + public Result editDevice(@PathVariable("videoId") Long videoId, @RequestBody DeviceVideoEditReq req){ + deviceVideoService.editDeviceVideo(videoId, req); + return Result.ok(); + } + + @ApiOperation(value = "更新视频设备", notes = "iot:video:sync", httpMethod = "PUT") + @PutMapping("/device/sync/{videoId}") + public Result syncDevice(@PathVariable("videoId") Long videoId){ + DeviceVideoEntity entity = deviceVideoService.getById(videoId); + if(DeviceStatus.ONLINE.getValue().equals(entity.getStatus())){ + sipDeviceCommander.queryCatalog(entity); + return Result.ok(); + }else { + return Result.error(IotRespMessage.DEVICE_VIDEO_CANNOT_SYNC); + } + } + + @ApiOperation(value = "删除视频设备", notes = "iot:video:delete", httpMethod = "DELETE") + @DeleteMapping("/device/{videoId}") + public Result deleteDevice(@PathVariable("videoId") Long videoId){ + return deviceVideoService.removeByVideoId(videoId); + } + + @ApiOperation(value = "分页查询视频通道列表", notes = "iot:video:channel:list", httpMethod = "GET") + @GetMapping("/channel/page/{videoId}") + public PageResult pageChannel(@PathVariable("videoId") Long videoId, @RequestParam(value = "keyword", required = false) String keyword){ + return deviceVideoChannelService.selectDeviceVideoChannel(videoId, keyword); + } + + @ApiOperation(value = "分页查询所有视频通道列表", notes = "iot:video:channel:list", httpMethod = "GET") + @GetMapping("/channel/page") + public PageResult pageAllChannel(@RequestParam(value = "keyword", required = false) String keyword){ + return deviceVideoChannelService.selectAllDeviceVideoChannel(keyword); + } + + @ApiOperation(value = "查看视频通道", notes = "iot:video:channel:view", httpMethod = "GET") + @GetMapping("/channel/{channelCode}") + public Result viewChannel(@PathVariable("channelCode") String channelCode){ + return Result.ok(deviceVideoChannelService.getDeviceVideoChannelDetail(channelCode)); + } + + @ApiOperation(value = "编辑视频通道", notes = "iot:video:channel:edit", httpMethod = "PUT") + @PutMapping("/channel/{channelId}") + public Result editChannel(@PathVariable("channelId") Long channelId, @RequestBody DeviceVideoChannelEditReq req){ + deviceVideoChannelService.editDeviceVideoChannel(channelId, req); + return Result.ok(); + } + + @ApiOperation(value = "删除视频通道", notes = "iot:video:channel:delete", httpMethod = "DELETE") + @DeleteMapping("/channel/{channelId}") + public Result deleteChannel(@PathVariable("channelId") Long channelId){ + return deviceVideoChannelService.removeByChannelIds(Arrays.asList(channelId)); + } + + @ApiOperation(value = "播放直播视频", notes = "iot:video:play", httpMethod = "GET") + @GetMapping("/play/live/{channelCode}") + public Result play(@PathVariable("channelCode") String channelCode){ + return Result.ok(deviceVideoChannelService.play(channelCode)); + } + + @ApiOperation(value = "查询视频设备是否在线", notes = "iot:video:isOnline", httpMethod = "GET") + @GetMapping("/channel/isOnline/{channelCode}") + public Result isOnline(@PathVariable("channelCode") String channelCode){ + return Result.ok(deviceVideoChannelService.isOnline(channelCode)); + } + + @ApiOperation(value = "停止播放直播", notes = "iot:video:stop", httpMethod = "GET") + @GetMapping("/stop/live/{channelCode}") + public Result stop(@PathVariable("channelCode") String channelCode){ + deviceVideoChannelService.stop(channelCode); + return Result.ok(); + } +} From a996310f25f1b2a3c60d011826fe533a4a83e01d Mon Sep 17 00:00:00 2001 From: cuizhibin Date: Thu, 7 Aug 2025 15:33:32 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/pom.xml | 36 + .../java/com/dite/znpt/converts/Converts.java | 2 + .../com/dite/znpt/domain/dto/FolderDto.java | 15 + .../domain/entity/BusinessDataEntity.java | 38 + .../domain/entity/BusinessDataFileEntity.java | 37 + .../znpt/domain/entity/EquipmentEntity.java | 166 +++- .../dite/znpt/domain/entity/PostEntity.java | 30 +- .../entity/ProjectBudgetInfoEntity.java | 16 +- .../znpt/domain/entity/ProjectEntity.java | 40 + .../znpt/domain/entity/RegulationEntity.java | 14 +- .../com/dite/znpt/domain/page/PageBean.java | 15 + .../dite/znpt/domain/vo/ContractListReq.java | 10 - .../com/dite/znpt/domain/vo/ContractResp.java | 12 +- .../dite/znpt/domain/vo/EquipmentListReq.java | 151 ++++ .../com/dite/znpt/domain/vo/EquipmentReq.java | 201 ++++- .../dite/znpt/domain/vo/EquipmentResp.java | 182 ++++ .../java/com/dite/znpt/domain/vo/PostReq.java | 17 +- .../com/dite/znpt/domain/vo/PostResp.java | 22 + .../vo/ProjectBudgetInfoDetailResp.java | 57 ++ .../domain/vo/ProjectBudgetInfoImportReq.java | 37 + .../domain/vo/ProjectBudgetInfoListReq.java | 8 +- .../domain/vo/ProjectBudgetInfoListResp.java | 4 +- .../znpt/domain/vo/ProjectBudgetInfoReq.java | 9 +- .../znpt/domain/vo/ProjectBudgetInfoResp.java | 5 +- .../znpt/domain/vo/ProjectDetailResp.java | 23 +- .../znpt/domain/vo/ProjectKanbanDataResp.java | 66 ++ .../dite/znpt/domain/vo/ProjectListReq.java | 8 +- .../dite/znpt/domain/vo/ProjectListResp.java | 12 +- .../com/dite/znpt/domain/vo/ProjectReq.java | 24 +- .../com/dite/znpt/domain/vo/ProjectResp.java | 17 +- .../dite/znpt/enums/ContractStatusEnum.java | 54 ++ .../com/dite/znpt/enums/HealthStatusEnum.java | 53 ++ .../dite/znpt/enums/LocationStatusEnum.java | 55 ++ .../znpt/mapper/BusinessDataFileMapper.java | 20 + .../dite/znpt/mapper/BusinessDataMapper.java | 26 + .../znpt/service/BusinessDataFileService.java | 27 + .../znpt/service/BusinessDataService.java | 15 + .../com/dite/znpt/service/EmailService.java | 30 + .../dite/znpt/service/EquipmentService.java | 56 +- .../service/ProjectBudgetInfoService.java | 39 +- .../dite/znpt/service/UserPostService.java | 3 +- .../dite/znpt/service/UserRoleService.java | 3 + .../impl/BusinessDataFileServiceImpl.java | 163 ++++ .../service/impl/BusinessDataServiceImpl.java | 197 +++++ .../service/impl/ContractServiceImpl.java | 13 +- .../znpt/service/impl/EmailServiceImpl.java | 66 ++ .../service/impl/EquipmentServiceImpl.java | 791 +++++++++++++++++- .../impl/InMemoryVerificationCodeStore.java | 74 ++ .../impl/ProjectBudgetInfoServiceImpl.java | 158 ++-- .../impl/ProjectMemberServiceImpl.java | 91 +- .../service/impl/UserPostServiceImpl.java | 11 + .../service/impl/UserRoleServiceImpl.java | 11 + .../znpt/service/impl/UserServiceImpl.java | 5 +- .../mapper/BusinessDataFileMapper.xml | 50 ++ .../resources/mapper/BusinessDataMapper.xml | 55 ++ .../main/resources/mapper/ContractMapper.xml | 9 - .../mapper/ProjectBudgetInfoMapper.xml | 35 +- .../main/resources/mapper/ProjectMapper.xml | 2 +- core/src/main/resources/mapper/UserMapper.xml | 46 +- flowable/pom.xml | 4 + pom.xml | 1 + web/pom.xml | 10 +- .../controller/BusinessDataController.java | 58 ++ .../BusinessDataFileController.java | 137 +++ .../znpt/web/controller/CommonController.java | 6 + .../znpt/web/controller/EmailController.java | 46 + .../web/controller/EquipmentController.java | 60 +- .../znpt/web/controller/PostController.java | 13 + .../ProjectBudgetInfoController.java | 39 +- .../controller/ProjectMemberController.java | 8 +- .../znpt/web/controller/RoleController.java | 15 +- .../znpt/web/controller/UserController.java | 8 + web/src/main/resources/application-dev.yml | 66 +- 73 files changed, 3558 insertions(+), 345 deletions(-) create mode 100644 core/src/main/java/com/dite/znpt/domain/dto/FolderDto.java create mode 100644 core/src/main/java/com/dite/znpt/domain/entity/BusinessDataEntity.java create mode 100644 core/src/main/java/com/dite/znpt/domain/entity/BusinessDataFileEntity.java create mode 100644 core/src/main/java/com/dite/znpt/domain/page/PageBean.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoDetailResp.java create mode 100644 core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoImportReq.java create mode 100644 core/src/main/java/com/dite/znpt/enums/ContractStatusEnum.java create mode 100644 core/src/main/java/com/dite/znpt/enums/HealthStatusEnum.java create mode 100644 core/src/main/java/com/dite/znpt/enums/LocationStatusEnum.java create mode 100644 core/src/main/java/com/dite/znpt/mapper/BusinessDataFileMapper.java create mode 100644 core/src/main/java/com/dite/znpt/mapper/BusinessDataMapper.java create mode 100644 core/src/main/java/com/dite/znpt/service/BusinessDataFileService.java create mode 100644 core/src/main/java/com/dite/znpt/service/BusinessDataService.java create mode 100644 core/src/main/java/com/dite/znpt/service/EmailService.java create mode 100644 core/src/main/java/com/dite/znpt/service/impl/BusinessDataFileServiceImpl.java create mode 100644 core/src/main/java/com/dite/znpt/service/impl/BusinessDataServiceImpl.java create mode 100644 core/src/main/java/com/dite/znpt/service/impl/EmailServiceImpl.java create mode 100644 core/src/main/java/com/dite/znpt/service/impl/InMemoryVerificationCodeStore.java create mode 100644 core/src/main/resources/mapper/BusinessDataFileMapper.xml create mode 100644 core/src/main/resources/mapper/BusinessDataMapper.xml create mode 100644 web/src/main/java/com/dite/znpt/web/controller/BusinessDataController.java create mode 100644 web/src/main/java/com/dite/znpt/web/controller/BusinessDataFileController.java create mode 100644 web/src/main/java/com/dite/znpt/web/controller/EmailController.java diff --git a/core/pom.xml b/core/pom.xml index 94afd03..a1e8bba 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -10,6 +10,10 @@ core 1.0.0-SNAPSHOT + + UTF-8 + + org.springframework.boot @@ -161,6 +165,38 @@ opencv 4.7.0-0 + + + + org.springframework.boot + spring-boot-starter-mail + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + com.aliyun + aliyun-java-sdk-core + 4.6.3 + + + + + com.aliyun + aliyun-java-sdk-dysmsapi + 2.1.0 + + + + + javax.xml.bind + jaxb-api + 2.3.1 + diff --git a/core/src/main/java/com/dite/znpt/converts/Converts.java b/core/src/main/java/com/dite/znpt/converts/Converts.java index 272e17e..ea0dd55 100644 --- a/core/src/main/java/com/dite/znpt/converts/Converts.java +++ b/core/src/main/java/com/dite/znpt/converts/Converts.java @@ -59,6 +59,8 @@ public interface Converts { List toRoleResp(List list); + List toUserResp(List list); + RoleEntity toRoleEntity(RoleReq req); MenuEntity toMenuEntity(MenuReq req); diff --git a/core/src/main/java/com/dite/znpt/domain/dto/FolderDto.java b/core/src/main/java/com/dite/znpt/domain/dto/FolderDto.java new file mode 100644 index 0000000..a91faff --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/dto/FolderDto.java @@ -0,0 +1,15 @@ +package com.dite.znpt.domain.dto; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@ApiModel("接受文件夹参数") +public class FolderDto { + private String name = "tom"; + private Long parentId = 0L; +} diff --git a/core/src/main/java/com/dite/znpt/domain/entity/BusinessDataEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/BusinessDataEntity.java new file mode 100644 index 0000000..3bd598e --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/entity/BusinessDataEntity.java @@ -0,0 +1,38 @@ +package com.dite.znpt.domain.entity; + + +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("business_data_part") +@ApiModel(value="商务资料文件夹对象") +@AllArgsConstructor +@NoArgsConstructor +public class BusinessDataEntity { + + // 主键 + private Long folderId = null; + // 文件夹名称 + private String folderName = null; + // 父级文件夹 + private Long parentId = null; + // 创建人 + private Long creatorId = null; + // 创建时间 + private LocalDateTime createTime = null; + // 更新时间 + private LocalDateTime updateTime = null; + // 是否删除 + private Boolean isDeleted = false; + // 文件夹路径 + private String folderPath = null; + +} diff --git a/core/src/main/java/com/dite/znpt/domain/entity/BusinessDataFileEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/BusinessDataFileEntity.java new file mode 100644 index 0000000..6b14ed8 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/entity/BusinessDataFileEntity.java @@ -0,0 +1,37 @@ +package com.dite.znpt.domain.entity; + + +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("business_data_part_file") +@ApiModel(value="商务资料对象") + +public class BusinessDataFileEntity { + + //文件id + private Long fileId = null; + //文件夹id + private Long folderId = null; + //文件名 + private String fileName = null; + //文件路径 + private String filePath = null; + //文件类型 + private String fileType = "unknown"; + //文件大小 + private Long fileSize = null; + //上传时间 + private Date uploadTime = null; + //上传人id + private Long uploaderId = null; + //是否删除 + private Boolean isDeleted = false; + +} diff --git a/core/src/main/java/com/dite/znpt/domain/entity/EquipmentEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/EquipmentEntity.java index 098ae58..4224af2 100644 --- a/core/src/main/java/com/dite/znpt/domain/entity/EquipmentEntity.java +++ b/core/src/main/java/com/dite/znpt/domain/entity/EquipmentEntity.java @@ -7,8 +7,9 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; -import java.io.Serial; import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; /** * @author Bear.G @@ -20,13 +21,15 @@ import java.io.Serializable; @TableName("equipment") @ApiModel(value="EquipmentEntity对象", description="设备信息表") public class EquipmentEntity extends AuditableEntity implements Serializable { - @Serial private static final long serialVersionUID = -6665040704562461286L; @ApiModelProperty("设备id") @TableId(type = IdType.ASSIGN_ID) private String equipmentId; + @ApiModelProperty("资产编号") + private String assetCode; + @ApiModelProperty("设备名称") private String equipmentName; @@ -45,6 +48,165 @@ public class EquipmentEntity extends AuditableEntity implements Serializable { @ApiModelProperty("设备序列号") private String equipmentSn; + @ApiModelProperty("品牌") + private String brand; + + @ApiModelProperty("配置规格/参数") + private String specification; + + @ApiModelProperty("位置状态") + private String locationStatus; + + @ApiModelProperty("设备当前物理位置") + private String physicalLocation; + + @ApiModelProperty("负责人") + private String responsiblePerson; + + @ApiModelProperty("健康状态") + private String healthStatus; + + @ApiModelProperty("采购时间") + private LocalDateTime purchaseTime; + + @ApiModelProperty("入库时间") + private LocalDateTime inStockTime; + + @ApiModelProperty("启用时间") + private LocalDateTime activationTime; + + @ApiModelProperty("预计报废时间") + private LocalDateTime expectedScrapTime; + + @ApiModelProperty("实际报废时间") + private LocalDateTime actualScrapTime; + + @ApiModelProperty("状态变更时间") + private LocalDateTime statusChangeTime; + + @ApiModelProperty("采购订单") + private String purchaseOrder; + + @ApiModelProperty("供应商名称") + private String supplierName; + + // 移除采购价格字段,使用单价和总价替代 + // @ApiModelProperty("采购价格") + // private BigDecimal purchasePrice; + + @ApiModelProperty("当前净值") + private BigDecimal currentNetValue; + + @ApiModelProperty("折旧方法") + private String depreciationMethod; + + @ApiModelProperty("折旧年限") + private Integer depreciationYears; + + @ApiModelProperty("残值") + private BigDecimal salvageValue; + + @ApiModelProperty("保修截止日期") + private LocalDateTime warrantyExpireDate; + + @ApiModelProperty("上次维护日期") + private LocalDateTime lastMaintenanceDate; + + @ApiModelProperty("下次维护日期") + private LocalDateTime nextMaintenanceDate; + + @ApiModelProperty("维护人员") + private String maintenancePerson; + + @ApiModelProperty("库存条码") + private String inventoryBarcode; + + @ApiModelProperty("资产备注") + private String assetRemark; + + @ApiModelProperty("次户号") + private String accountNumber; + + @ApiModelProperty("数量") + private Integer quantity; + + @ApiModelProperty("单价") + private BigDecimal unitPrice; + + @ApiModelProperty("总价") + private BigDecimal totalPrice; + + // 移除备用状态字段,使用现有的 location_status 字段 + // @ApiModelProperty("备用状态") + // private String spareStatus; + + @ApiModelProperty("盘点依据") + private String inventoryBasis; + + @ApiModelProperty("动态记录") + private String dynamicRecord; + + // 移除认证状态字段,使用现有的 health_status 字段 + // @ApiModelProperty("认证状态") + // private String certificationStatus; + + @ApiModelProperty("使用部门/人") + private String usingDepartment; + + @ApiModelProperty("领用时间") + private LocalDateTime borrowingTime; + + @ApiModelProperty("归还时间") + private LocalDateTime returnTime; + + @ApiModelProperty("出库时间") + private LocalDateTime outStockTime; + + @ApiModelProperty("总使用时间") + private String totalUsageTime; + + @ApiModelProperty("折旧率") + private BigDecimal depreciationRate; + + @ApiModelProperty("折旧方法说明") + private String depreciationMethodDesc; + + @ApiModelProperty("发票") + private String invoice; + + @ApiModelProperty("发票状态") + private String invoiceStatus; + + @ApiModelProperty("附件") + private String attachments; + + @ApiModelProperty("照片") + private String photos; + + @ApiModelProperty("条码") + private String barcode; + + @ApiModelProperty("导入人") + private String importer; + + @ApiModelProperty("盘库时间/状态1") + private String inventoryTimeStatus1; + + @ApiModelProperty("盘库时间/状态2") + private String inventoryTimeStatus2; + + @ApiModelProperty("盘库时间/状态3") + private String inventoryTimeStatus3; + + @ApiModelProperty("盘点时间/状态1") + private String inventoryCheckTimeStatus1; + + @ApiModelProperty("盘点时间/状态2") + private String inventoryCheckTimeStatus2; + + @ApiModelProperty("盘点时间/状态3") + private String inventoryCheckTimeStatus3; + @ApiModelProperty("当前使用记录id") @TableField(updateStrategy = FieldStrategy.ALWAYS) private String useRecordId; diff --git a/core/src/main/java/com/dite/znpt/domain/entity/PostEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/PostEntity.java index f19d72e..dcfcdb9 100644 --- a/core/src/main/java/com/dite/znpt/domain/entity/PostEntity.java +++ b/core/src/main/java/com/dite/znpt/domain/entity/PostEntity.java @@ -1,6 +1,5 @@ package com.dite.znpt.domain.entity; -import com.alibaba.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; @@ -13,6 +12,7 @@ import lombok.EqualsAndHashCode; import java.io.Serial; import java.io.Serializable; +import java.time.LocalDateTime; /** * @author Bear.G @@ -50,4 +50,32 @@ public class PostEntity extends AuditableEntity implements Serializable { @ApiModelProperty("备注") @TableField("remark") private String remark; + + @ApiModelProperty("创建时间") + @TableField("create_time") + private LocalDateTime createTime; + + @ApiModelProperty("更新时间") + @TableField("update_time") + private LocalDateTime updateTime; + + @ApiModelProperty("岗位说明") + @TableField("statement") + private String statement; + + @ApiModelProperty("岗位任务") + @TableField("responsibilities_task") + private String responsibilitiesTask; + + @ApiModelProperty("岗位任职资格") + @TableField("qualifications") + private String qualifications; + + @ApiModelProperty("岗位工作条件") + @TableField("working_conditions") + private String workingConditions; + + @ApiModelProperty("岗位薪资") + @TableField("salary") + private Double salary; } diff --git a/core/src/main/java/com/dite/znpt/domain/entity/ProjectBudgetInfoEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/ProjectBudgetInfoEntity.java index 7b5e4bb..83be724 100644 --- a/core/src/main/java/com/dite/znpt/domain/entity/ProjectBudgetInfoEntity.java +++ b/core/src/main/java/com/dite/znpt/domain/entity/ProjectBudgetInfoEntity.java @@ -30,7 +30,7 @@ public class ProjectBudgetInfoEntity extends AuditableEntity implements Serializ @ExcelProperty("主键") @ApiModelProperty("主键") - @TableId(value = "budget_id", type = IdType.ASSIGN_ID) + @TableId(value = "budget_id", type = IdType.AUTO) private String budgetId; @ExcelProperty("项目id") @@ -43,13 +43,8 @@ public class ProjectBudgetInfoEntity extends AuditableEntity implements Serializ @TableField("budget_name") private String budgetName; - @ExcelProperty("预算类型") - @ApiModelProperty("预算类型") - @TableField("budget_type") - private String budgetType; - - @ExcelProperty("预算金额(万元)") - @ApiModelProperty("预算金额(万元)") + @ExcelProperty("预算花费金额") + @ApiModelProperty("预算花费金额") @TableField("budget_amount") private Double budgetAmount; @@ -57,5 +52,10 @@ public class ProjectBudgetInfoEntity extends AuditableEntity implements Serializ @ApiModelProperty("预算说明") @TableField("budget_desc") private String budgetDesc; + + @ExcelProperty("附件") + @ApiModelProperty("附件") + @TableField("attach") + private String attach; } diff --git a/core/src/main/java/com/dite/znpt/domain/entity/ProjectEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/ProjectEntity.java index 718558f..943620b 100644 --- a/core/src/main/java/com/dite/znpt/domain/entity/ProjectEntity.java +++ b/core/src/main/java/com/dite/znpt/domain/entity/ProjectEntity.java @@ -33,6 +33,16 @@ public class ProjectEntity extends AuditableEntity implements Serializable { @TableId(value = "project_id", type = IdType.ASSIGN_UUID) private String projectId; + @ExcelProperty("项目来源") + @ApiModelProperty("项目来源") + @TableField("project_origin") + private String projectOrigin; + + @ExcelProperty("项目预算") + @ApiModelProperty("项目预算") + @TableField("project_budget") + private Integer projectBudget; + @ExcelProperty("项目名称") @ApiModelProperty("项目名称") @TableField("project_name") @@ -169,5 +179,35 @@ public class ProjectEntity extends AuditableEntity implements Serializable { // 施工人员,安全经理,项目经理,商务,财务,高级管理员,项目远程顾问外部协作者,质量经理、现场经理及工作组长。 @ApiModelProperty(value = "结束时间") private LocalDate endDate; + + @ApiModelProperty("人工成本") + private Double laborCost; + + @ApiModelProperty("设备摊销") + private Double equipmentAmortization; + + @ApiModelProperty("奖金预提") + private Double bonusProvision; + + @ApiModelProperty("交通食宿") + private Double transAccomMeals; + + @ApiModelProperty("其他杂费") + private Double othersCost; + + @ApiModelProperty("已用人工成本") + private Double useLaborCost; + + @ApiModelProperty("已用设备摊销") + private Double useEquipmentAmortization; + + @ApiModelProperty("已用奖金预提") + private Double useBonusProvision; + + @ApiModelProperty("已用交通食宿") + private Double useTransAccomMeals; + + @ApiModelProperty("已用其他杂费") + private Double useOthersCost; } diff --git a/core/src/main/java/com/dite/znpt/domain/entity/RegulationEntity.java b/core/src/main/java/com/dite/znpt/domain/entity/RegulationEntity.java index 6dd36f1..e3519a0 100644 --- a/core/src/main/java/com/dite/znpt/domain/entity/RegulationEntity.java +++ b/core/src/main/java/com/dite/znpt/domain/entity/RegulationEntity.java @@ -1,12 +1,15 @@ package com.dite.znpt.domain.entity; -import com.baomidou.mybatisplus.annotation.*; +import com.alibaba.excel.annotation.ExcelProperty; +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.dite.znpt.domain.AuditableEntity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; -import com.alibaba.excel.annotation.ExcelProperty; import java.io.Serializable; import java.time.LocalDateTime; @@ -39,9 +42,7 @@ public class RegulationEntity extends AuditableEntity implements Serializable { @TableField("content") private String content; - @ExcelProperty("制度类型") - @ApiModelProperty("制度类型") - @TableField("regulation_type") + @TableField(exist = false) private String type; @ExcelProperty("制度状态") @@ -97,7 +98,8 @@ public class RegulationEntity extends AuditableEntity implements Serializable { @ApiModelProperty("创建人姓名") private String createByName; - @TableField(exist = false) + @ExcelProperty("制度类型") @ApiModelProperty("制度类型") + @TableField("regulation_type") private String regulationType; } \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/page/PageBean.java b/core/src/main/java/com/dite/znpt/domain/page/PageBean.java new file mode 100644 index 0000000..5a2992b --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/page/PageBean.java @@ -0,0 +1,15 @@ +package com.dite.znpt.domain.page; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PageBean { + private Long total; + private List rows; +} diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ContractListReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ContractListReq.java index fa58191..facfb38 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ContractListReq.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ContractListReq.java @@ -7,7 +7,6 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; -import java.util.Date; /** * @author huise23 @@ -42,9 +41,6 @@ public class ContractListReq implements Serializable { @ApiModelProperty("部门id") private String departmentId; - @ApiModelProperty("签订日期") - private Date signDate; - @ApiModelProperty("期限") private String duration; @@ -54,12 +50,6 @@ public class ContractListReq implements Serializable { @ApiModelProperty("产品或服务") private String productService; - @ApiModelProperty("付款日期/交付日期") - private Date paymentDate; - - @ApiModelProperty("履约时间期限") - private Date performanceDeadline; - @ApiModelProperty("付款地址/交付地址") private String paymentAddress; diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ContractResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ContractResp.java index f21e2ca..a1f4ff0 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ContractResp.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ContractResp.java @@ -1,15 +1,12 @@ package com.dite.znpt.domain.vo; -import java.math.BigDecimal; -import java.util.Date; - -import com.alibaba.excel.annotation.ExcelProperty; -import com.baomidou.mybatisplus.annotation.TableField; +import com.dite.znpt.domain.entity.ContractEntity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; -import com.dite.znpt.domain.entity.ContractEntity; + +import java.math.BigDecimal; /** * @author huise23 @@ -35,5 +32,8 @@ public class ContractResp extends ContractEntity { @ApiModelProperty("已收款金额") private BigDecimal receivedAmount; + + @ApiModelProperty("合同状态名称") + private String contractStatusLabel; } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/EquipmentListReq.java b/core/src/main/java/com/dite/znpt/domain/vo/EquipmentListReq.java index 36d9cfc..d746d98 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/EquipmentListReq.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/EquipmentListReq.java @@ -6,6 +6,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; +import java.time.LocalDateTime; /** * @author Bear.G @@ -26,4 +27,154 @@ public class EquipmentListReq implements Serializable { @ApiModelProperty("设备状态,0-空闲中,1-使用,3-保养中,4-维修中,5-已报废") private String equipmentStatus; + + @ApiModelProperty("设备序列号") + private String equipmentSn; + + @ApiModelProperty("资产编号") + private String assetCode; + + @ApiModelProperty("品牌") + private String brand; + + @ApiModelProperty("位置状态") + private String locationStatus; + + @ApiModelProperty("健康状态") + private String healthStatus; + + @ApiModelProperty("负责人") + private String responsiblePerson; + + @ApiModelProperty("使用状态,0-空闲中,1-使用中") + private String useStatus; + + @ApiModelProperty("项目ID") + private String projectId; + + @ApiModelProperty("用户ID") + private String userId; + + @ApiModelProperty("设备型号") + private String equipmentModel; + + @ApiModelProperty("配置规格/参数") + private String specification; + + @ApiModelProperty("设备当前物理位置") + private String physicalLocation; + + @ApiModelProperty("供应商名称") + private String supplierName; + + @ApiModelProperty("采购订单号") + private String purchaseOrder; + + @ApiModelProperty("维护人员") + private String maintenancePerson; + + @ApiModelProperty("次户号") + private String accountNumber; + + @ApiModelProperty("数量") + private Integer quantity; + + @ApiModelProperty("单价") + private java.math.BigDecimal unitPrice; + + @ApiModelProperty("总价") + private java.math.BigDecimal totalPrice; + + @ApiModelProperty("盘点依据") + private String inventoryBasis; + + @ApiModelProperty("动态记录") + private String dynamicRecord; + + @ApiModelProperty("库存条码") + private String inventoryBarcode; + + @ApiModelProperty("资产备注") + private String assetRemark; + + @ApiModelProperty("使用部门/人") + private String usingDepartment; + + @ApiModelProperty("发票") + private String invoice; + + @ApiModelProperty("条码") + private String barcode; + + @ApiModelProperty("导入人") + private String importer; + + @ApiModelProperty("采购时间开始") + private LocalDateTime purchaseTimeStart; + + @ApiModelProperty("采购时间结束") + private LocalDateTime purchaseTimeEnd; + + @ApiModelProperty("入库时间开始") + private LocalDateTime inStockTimeStart; + + @ApiModelProperty("入库时间结束") + private LocalDateTime inStockTimeEnd; + + @ApiModelProperty("启用时间开始") + private LocalDateTime activationTimeStart; + + @ApiModelProperty("启用时间结束") + private LocalDateTime activationTimeEnd; + + @ApiModelProperty("预计报废时间开始") + private LocalDateTime expectedScrapTimeStart; + + @ApiModelProperty("预计报废时间结束") + private LocalDateTime expectedScrapTimeEnd; + + @ApiModelProperty("保修截止日期开始") + private LocalDateTime warrantyExpireDateStart; + + @ApiModelProperty("保修截止日期结束") + private LocalDateTime warrantyExpireDateEnd; + + @ApiModelProperty("上次维护日期开始") + private LocalDateTime lastMaintenanceDateStart; + + @ApiModelProperty("上次维护日期结束") + private LocalDateTime lastMaintenanceDateEnd; + + @ApiModelProperty("下次维护日期开始") + private LocalDateTime nextMaintenanceDateStart; + + @ApiModelProperty("下次维护日期结束") + private LocalDateTime nextMaintenanceDateEnd; + + @ApiModelProperty("创建时间开始") + private LocalDateTime createTimeStart; + + @ApiModelProperty("创建时间结束") + private LocalDateTime createTimeEnd; + + @ApiModelProperty("更新时间开始") + private LocalDateTime updateTimeStart; + + @ApiModelProperty("更新时间结束") + private LocalDateTime updateTimeEnd; + + @ApiModelProperty("当前页码") + private Integer pageNum; + + @ApiModelProperty("每页大小") + private Integer pageSize; + + @ApiModelProperty("排序字段") + private String orderBy; + + @ApiModelProperty("排序方向") + private String orderDirection; + + @ApiModelProperty("页码") + private Integer page; } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/EquipmentReq.java b/core/src/main/java/com/dite/znpt/domain/vo/EquipmentReq.java index db16ba9..202d213 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/EquipmentReq.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/EquipmentReq.java @@ -8,6 +8,8 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import java.io.Serial; import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; /** * @author Bear.G @@ -22,7 +24,7 @@ public class EquipmentReq implements Serializable { @ApiModelProperty("设备名称") @NotBlank(message = "设备名称不能为空") - @Size(max = 100, message = "设备名称长度不能超过100个字") + @Size(max = 200, message = "设备名称长度不能超过200个字") private String equipmentName; @ApiModelProperty("设备类型, 枚举:EquipmentTypeEnum") @@ -31,11 +33,204 @@ public class EquipmentReq implements Serializable { @ApiModelProperty("设备型号") @NotBlank(message = "设备型号不能为空") - @Size(max = 50, message = "设备型号长度不能超过50个字") + @Size(max = 200, message = "设备型号长度不能超过200个字") private String equipmentModel; @ApiModelProperty("设备SN") @NotBlank(message = "设备SN不能为空") - @Size(max = 50, message = "设备SN长度不能超过50个字") + @Size(max = 100, message = "设备SN长度不能超过100个字") private String equipmentSn; + + @ApiModelProperty("资产编号") + @Size(max = 50, message = "资产编号长度不能超过50个字") + private String assetCode; + + @ApiModelProperty("品牌") + @Size(max = 100, message = "品牌长度不能超过100个字") + private String brand; + + @ApiModelProperty("配置规格/参数") + @Size(max = 500, message = "配置规格长度不能超过500个字") + private String specification; + + @ApiModelProperty("设备状态,枚举:EquipmentStatusEnum") + private String equipmentStatus; + + @ApiModelProperty("使用状态,0-空闲中,1-使用中") + private String useStatus; + + @ApiModelProperty("位置状态") + private String locationStatus; + + @ApiModelProperty("设备当前物理位置") + @Size(max = 200, message = "物理位置长度不能超过200个字") + private String physicalLocation; + + @ApiModelProperty("负责人") + @Size(max = 100, message = "负责人长度不能超过100个字") + private String responsiblePerson; + + @ApiModelProperty("健康状态") + private String healthStatus; + + @ApiModelProperty("采购时间") + private LocalDateTime purchaseTime; + + @ApiModelProperty("入库时间") + private LocalDateTime inStockTime; + + @ApiModelProperty("启用时间") + private LocalDateTime activationTime; + + @ApiModelProperty("预计报废时间") + private LocalDateTime expectedScrapTime; + + @ApiModelProperty("实际报废时间") + private LocalDateTime actualScrapTime; + + @ApiModelProperty("状态变更时间") + private LocalDateTime statusChangeTime; + + @ApiModelProperty("采购订单") + @Size(max = 100, message = "采购订单长度不能超过100个字") + private String purchaseOrder; + + @ApiModelProperty("供应商名称") + @Size(max = 200, message = "供应商名称长度不能超过200个字") + private String supplierName; + + // 移除采购价格字段,使用单价和总价替代 + // @ApiModelProperty("采购价格") + // private BigDecimal purchasePrice; + + @ApiModelProperty("当前净值") + private BigDecimal currentNetValue; + + @ApiModelProperty("折旧方法") + private String depreciationMethod; + + @ApiModelProperty("折旧年限") + private Integer depreciationYears; + + @ApiModelProperty("残值") + private BigDecimal salvageValue; + + @ApiModelProperty("保修截止日期") + private LocalDateTime warrantyExpireDate; + + @ApiModelProperty("上次维护日期") + private LocalDateTime lastMaintenanceDate; + + @ApiModelProperty("下次维护日期") + private LocalDateTime nextMaintenanceDate; + + @ApiModelProperty("维护人员") + @Size(max = 100, message = "维护人员长度不能超过100个字") + private String maintenancePerson; + + @ApiModelProperty("库存条码") + @Size(max = 100, message = "库存条码长度不能超过100个字") + private String inventoryBarcode; + + @ApiModelProperty("资产备注") + @Size(max = 1000, message = "资产备注长度不能超过1000个字") + private String assetRemark; + + @ApiModelProperty("次户号") + private String accountNumber; + + @ApiModelProperty("数量") + private Integer quantity; + + @ApiModelProperty("单价") + private BigDecimal unitPrice; + + @ApiModelProperty("总价") + private BigDecimal totalPrice; + + // 移除备用状态字段,使用现有的 location_status 字段 + // @ApiModelProperty("备用状态") + // private String spareStatus; + + @ApiModelProperty("盘点依据") + private String inventoryBasis; + + @ApiModelProperty("动态记录") + private String dynamicRecord; + + // 移除认证状态字段,使用现有的 health_status 字段 + // @ApiModelProperty("认证状态") + // private String certificationStatus; + + @ApiModelProperty("使用部门/人") + @Size(max = 200, message = "使用部门/人长度不能超过200个字") + private String usingDepartment; + + @ApiModelProperty("领用时间") + private LocalDateTime borrowingTime; + + @ApiModelProperty("归还时间") + private LocalDateTime returnTime; + + @ApiModelProperty("出库时间") + private LocalDateTime outStockTime; + + @ApiModelProperty("总使用时间") + @Size(max = 100, message = "总使用时间长度不能超过100个字") + private String totalUsageTime; + + @ApiModelProperty("折旧率") + private BigDecimal depreciationRate; + + @ApiModelProperty("折旧方法说明") + @Size(max = 500, message = "折旧方法说明长度不能超过500个字") + private String depreciationMethodDesc; + + @ApiModelProperty("发票") + @Size(max = 100, message = "发票长度不能超过100个字") + private String invoice; + + @ApiModelProperty("发票状态") + @Size(max = 50, message = "发票状态长度不能超过50个字") + private String invoiceStatus; + + @ApiModelProperty("附件") + @Size(max = 500, message = "附件长度不能超过500个字") + private String attachments; + + @ApiModelProperty("照片") + @Size(max = 500, message = "照片长度不能超过500个字") + private String photos; + + @ApiModelProperty("条码") + @Size(max = 100, message = "条码长度不能超过100个字") + private String barcode; + + @ApiModelProperty("导入人") + @Size(max = 100, message = "导入人长度不能超过100个字") + private String importer; + + @ApiModelProperty("盘库时间/状态1") + @Size(max = 100, message = "盘库时间/状态1长度不能超过100个字") + private String inventoryTimeStatus1; + + @ApiModelProperty("盘库时间/状态2") + @Size(max = 100, message = "盘库时间/状态2长度不能超过100个字") + private String inventoryTimeStatus2; + + @ApiModelProperty("盘库时间/状态3") + @Size(max = 100, message = "盘库时间/状态3长度不能超过100个字") + private String inventoryTimeStatus3; + + @ApiModelProperty("盘点时间/状态1") + @Size(max = 100, message = "盘点时间/状态1长度不能超过100个字") + private String inventoryCheckTimeStatus1; + + @ApiModelProperty("盘点时间/状态2") + @Size(max = 100, message = "盘点时间/状态2长度不能超过100个字") + private String inventoryCheckTimeStatus2; + + @ApiModelProperty("盘点时间/状态3") + @Size(max = 100, message = "盘点时间/状态3长度不能超过100个字") + private String inventoryCheckTimeStatus3; } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/EquipmentResp.java b/core/src/main/java/com/dite/znpt/domain/vo/EquipmentResp.java index 335a532..4aac551 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/EquipmentResp.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/EquipmentResp.java @@ -6,6 +6,8 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; /** * @author Bear.G @@ -21,6 +23,9 @@ public class EquipmentResp implements Serializable { @ApiModelProperty("设备ID") private String equipmentId; + @ApiModelProperty("资产编号") + private String assetCode; + @ApiModelProperty("设备名称") private String equipmentName; @@ -36,6 +41,12 @@ public class EquipmentResp implements Serializable { @ApiModelProperty("设备SN") private String equipmentSn; + @ApiModelProperty("品牌") + private String brand; + + @ApiModelProperty("配置规格/参数") + private String specification; + @ApiModelProperty("设备状态, 枚举:EquipmentStatusEnum") private String equipmentStatus; @@ -45,6 +56,171 @@ public class EquipmentResp implements Serializable { @ApiModelProperty("设备使用状态,0-空闲中,1-使用中") private String useStatus; + @ApiModelProperty("位置状态") + private String locationStatus; + + @ApiModelProperty("位置状态描述") + private String locationStatusLabel; + + @ApiModelProperty("设备当前物理位置") + private String physicalLocation; + + @ApiModelProperty("负责人") + private String responsiblePerson; + + @ApiModelProperty("健康状态") + private String healthStatus; + + @ApiModelProperty("健康状态描述") + private String healthStatusLabel; + + @ApiModelProperty("采购时间") + private LocalDateTime purchaseTime; + + @ApiModelProperty("入库时间") + private LocalDateTime inStockTime; + + @ApiModelProperty("启用时间") + private LocalDateTime activationTime; + + @ApiModelProperty("预计报废时间") + private LocalDateTime expectedScrapTime; + + @ApiModelProperty("实际报废时间") + private LocalDateTime actualScrapTime; + + @ApiModelProperty("状态变更时间") + private LocalDateTime statusChangeTime; + + @ApiModelProperty("采购订单") + private String purchaseOrder; + + @ApiModelProperty("供应商名称") + private String supplierName; + + // 移除采购价格字段,使用单价和总价替代 + // @ApiModelProperty("采购价格") + // private BigDecimal purchasePrice; + + @ApiModelProperty("当前净值") + private BigDecimal currentNetValue; + + @ApiModelProperty("折旧方法") + private String depreciationMethod; + + @ApiModelProperty("折旧年限") + private Integer depreciationYears; + + @ApiModelProperty("残值") + private BigDecimal salvageValue; + + @ApiModelProperty("保修截止日期") + private LocalDateTime warrantyExpireDate; + + @ApiModelProperty("上次维护日期") + private LocalDateTime lastMaintenanceDate; + + @ApiModelProperty("下次维护日期") + private LocalDateTime nextMaintenanceDate; + + @ApiModelProperty("维护人员") + private String maintenancePerson; + + @ApiModelProperty("库存条码") + private String inventoryBarcode; + + @ApiModelProperty("资产备注") + private String assetRemark; + + @ApiModelProperty("次户号") + private String accountNumber; + + @ApiModelProperty("数量") + private Integer quantity; + + @ApiModelProperty("单价") + private BigDecimal unitPrice; + + @ApiModelProperty("总价") + private BigDecimal totalPrice; + + // 移除备用状态字段,使用现有的 location_status 字段 + // @ApiModelProperty("备用状态") + // private String spareStatus; + + // @ApiModelProperty("备用状态描述") + // private String spareStatusLabel; + + @ApiModelProperty("盘点依据") + private String inventoryBasis; + + @ApiModelProperty("动态记录") + private String dynamicRecord; + + // 移除认证状态字段,使用现有的 health_status 字段 + // @ApiModelProperty("认证状态") + // private String certificationStatus; + + // @ApiModelProperty("认证状态描述") + // private String certificationStatusLabel; + + @ApiModelProperty("使用部门/人") + private String usingDepartment; + + @ApiModelProperty("领用时间") + private LocalDateTime borrowingTime; + + @ApiModelProperty("归还时间") + private LocalDateTime returnTime; + + @ApiModelProperty("出库时间") + private LocalDateTime outStockTime; + + @ApiModelProperty("总使用时间") + private String totalUsageTime; + + @ApiModelProperty("折旧率") + private BigDecimal depreciationRate; + + @ApiModelProperty("折旧方法说明") + private String depreciationMethodDesc; + + @ApiModelProperty("发票") + private String invoice; + + @ApiModelProperty("发票状态") + private String invoiceStatus; + + @ApiModelProperty("附件") + private String attachments; + + @ApiModelProperty("照片") + private String photos; + + @ApiModelProperty("条码") + private String barcode; + + @ApiModelProperty("导入人") + private String importer; + + @ApiModelProperty("盘库时间/状态1") + private String inventoryTimeStatus1; + + @ApiModelProperty("盘库时间/状态2") + private String inventoryTimeStatus2; + + @ApiModelProperty("盘库时间/状态3") + private String inventoryTimeStatus3; + + @ApiModelProperty("盘点时间/状态1") + private String inventoryCheckTimeStatus1; + + @ApiModelProperty("盘点时间/状态2") + private String inventoryCheckTimeStatus2; + + @ApiModelProperty("盘点时间/状态3") + private String inventoryCheckTimeStatus3; + @ApiModelProperty("项目id") private String projectId; @@ -56,4 +232,10 @@ public class EquipmentResp implements Serializable { @ApiModelProperty("使用人") private String name; + + @ApiModelProperty("创建时间") + private LocalDateTime createTime; + + @ApiModelProperty("更新时间") + private LocalDateTime updateTime; } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/PostReq.java b/core/src/main/java/com/dite/znpt/domain/vo/PostReq.java index fc4df93..55371d5 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/PostReq.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/PostReq.java @@ -33,9 +33,24 @@ public class PostReq implements Serializable { @ApiModelProperty("显示顺序") private Integer postSort; - @ApiModelProperty("状态(0正常 1停用)") + @ApiModelProperty("状态(0停用 1正常)") private Integer status; @ApiModelProperty("备注") private String remark; + + @ApiModelProperty("岗位说明") + private String statement; + + @ApiModelProperty("岗位任务") + private String responsibilitiesTask; + + @ApiModelProperty("岗位任职资格") + private String qualifications; + + @ApiModelProperty("岗位工作条件") + private String workingConditions; + + @ApiModelProperty("岗位薪资") + private Double salary; } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/PostResp.java b/core/src/main/java/com/dite/znpt/domain/vo/PostResp.java index ae7eaf2..47c25ad 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/PostResp.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/PostResp.java @@ -7,6 +7,7 @@ import lombok.Data; import javax.validation.constraints.Size; import java.io.Serial; import java.io.Serializable; +import java.time.LocalDateTime; /** * @author Bear.G @@ -41,4 +42,25 @@ public class PostResp implements Serializable { @ApiModelProperty("备注") private String remark; + @ApiModelProperty("创建时间") + private LocalDateTime createTime; + + @ApiModelProperty("更新时间") + private LocalDateTime updateTime; + + @ApiModelProperty("岗位说明") + private String statement; + + @ApiModelProperty("岗位任务") + private String responsibilitiesTask; + + @ApiModelProperty("岗位任职资格") + private String qualifications; + + @ApiModelProperty("岗位工作条件") + private String workingConditions; + + @ApiModelProperty("岗位薪资") + private Double salary; + } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoDetailResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoDetailResp.java new file mode 100644 index 0000000..1216dcc --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoDetailResp.java @@ -0,0 +1,57 @@ +package com.dite.znpt.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@ApiModel("项目预算信息详情") +public class ProjectBudgetInfoDetailResp implements Serializable { + @Serial + private static final long serialVersionUID = 766154886845694269L; + + @ApiModelProperty("项目id") + private String projectId; + + @ApiModelProperty("项目名称") + private String projectName; + + @ApiModelProperty("项目预算") + private Double projectBudget; + + @ApiModelProperty("人工成本") + private Double laborCost; + + @ApiModelProperty("设备摊销") + private Double equipmentAmortization; + + @ApiModelProperty("奖金预提") + private Double bonusProvision; + + @ApiModelProperty("交通食宿") + private Double transAccomMeals; + + @ApiModelProperty("其他杂费") + private Double othersCost; + + @ApiModelProperty("已用人工成本") + private Double useLaborCost; + + @ApiModelProperty("已用设备摊销") + private Double useEquipmentAmortization; + + @ApiModelProperty("已用奖金预提") + private Double useBonusProvision; + + @ApiModelProperty("已用交通食宿") + private Double useTransAccomMeals; + + @ApiModelProperty("已用其他杂费") + private Double useOthersCost; + + @ApiModelProperty("剩余预算") + private Double restBudget; +} diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoImportReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoImportReq.java new file mode 100644 index 0000000..ec8918f --- /dev/null +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoImportReq.java @@ -0,0 +1,37 @@ +package com.dite.znpt.domain.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; +/** + * @author huise23 + * @date 2025/07/17 21:58 + * @Description: 项目预算信息表导入请求类 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value="ProjectBudgetInfo导入请求对象", description="项目预算信息表") +public class ProjectBudgetInfoImportReq implements Serializable { + + @Serial + private static final long serialVersionUID = 580212651388155611L; + + @ExcelProperty(value = "项目Id") + private String projectId; + + @ExcelProperty(value = "预算名称") + private String budgetName; + + @ExcelProperty(value = "预算花费金额") + private Double budgetAmount; + + @ExcelProperty(value = "预算说明") + private String budgetDesc; + + @ExcelProperty(value = "附件") + private String attach; +} diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoListReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoListReq.java index 047b5e9..0188223 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoListReq.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoListReq.java @@ -22,7 +22,7 @@ public class ProjectBudgetInfoListReq implements Serializable { @ApiModelProperty("查询关键字") private String keyword; - @ApiModelProperty("项目预算信息Id") + @ApiModelProperty("项目预算单Id") private String budgetId; @ApiModelProperty("项目id") @@ -31,14 +31,10 @@ public class ProjectBudgetInfoListReq implements Serializable { @ApiModelProperty("预算名称") private String budgetName; - @ApiModelProperty("预算类型") - private String budgetType; - - @ApiModelProperty("预算金额(万元)") + @ApiModelProperty("预算花费金额") private Double budgetAmount; @ApiModelProperty("预算说明") private String budgetDesc; - } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoListResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoListResp.java index af0e31b..17abe5d 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoListResp.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoListResp.java @@ -2,6 +2,7 @@ package com.dite.znpt.domain.vo; import com.dite.znpt.domain.entity.ProjectBudgetInfoEntity; import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; @@ -14,6 +15,7 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @ApiModel("项目预算信息列表响应实体") public class ProjectBudgetInfoListResp extends ProjectBudgetInfoEntity { - + @ApiModelProperty("项目名称") + private String projectName; } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoReq.java index 60caa06..27998c3 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoReq.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoReq.java @@ -26,16 +26,13 @@ public class ProjectBudgetInfoReq implements Serializable { @ApiModelProperty("预算名称") private String budgetName; - @ApiModelProperty("预算类型") - private String budgetType; - - @ApiModelProperty("预算金额(万元)") + @ApiModelProperty("预算花费金额") private Double budgetAmount; @ApiModelProperty("预算说明") private String budgetDesc; - @ApiModelProperty("附件id") - private String attachId; + @ApiModelProperty("附件") + private String attach; } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoResp.java index 70b2d3f..fed9529 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoResp.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectBudgetInfoResp.java @@ -15,8 +15,7 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) @ApiModel("项目预算信息响应实体") public class ProjectBudgetInfoResp extends ProjectBudgetInfoEntity { - - @ApiModelProperty("预算类型描述") - private String budgetTypeDesc; + @ApiModelProperty("项目名称") + private String projectName; } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectDetailResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectDetailResp.java index 2266301..d92ce3a 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectDetailResp.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectDetailResp.java @@ -4,6 +4,7 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; @@ -70,12 +71,31 @@ public class ProjectDetailResp { @ApiModelProperty("结束时间") private LocalDate endDate; + @ApiModelProperty("计划开始时间") + private LocalDate plannedStartDate; + @ApiModelProperty("创建时间") private String createTime; @ApiModelProperty("更新时间") private String updateTime; + // 新增字段 - 对应前端ProjectCard接口 + @ApiModelProperty("项目预算(万元)") + private BigDecimal budget; + + @ApiModelProperty("项目经理") + private String manager; + + @ApiModelProperty("团队规模") + private Integer teamSize; + + @ApiModelProperty("准备进度百分比") + private Integer preparationProgress; + + @ApiModelProperty("项目进度百分比") + private Integer progress; + // 项目人员信息(从新关联表获取) @ApiModelProperty("项目人员列表") private List projectMembers; @@ -152,9 +172,6 @@ public class ProjectDetailResp { @ApiModelProperty("预算名称") private String budgetName; - @ApiModelProperty("预算类型") - private String budgetType; - @ApiModelProperty("预算金额(万元)") private Double budgetAmount; diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanDataResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanDataResp.java index f80c696..2e7e598 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanDataResp.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectKanbanDataResp.java @@ -4,6 +4,7 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; @@ -71,6 +72,9 @@ public class ProjectKanbanDataResp { @ApiModelProperty("结束时间") private LocalDate endDate; + @ApiModelProperty("计划开始时间") + private LocalDate plannedStartDate; + @ApiModelProperty("项目经理") private String projectManagerName; @@ -103,5 +107,67 @@ public class ProjectKanbanDataResp { @ApiModelProperty("更新时间") private String updateTime; + + // 新增字段 - 对应前端ProjectCard接口 + @ApiModelProperty("项目预算(万元)") + private BigDecimal budget; + + @ApiModelProperty("项目经理") + private String manager; + + @ApiModelProperty("团队规模") + private Integer teamSize; + + @ApiModelProperty("准备进度百分比") + private Integer preparationProgress; + + @ApiModelProperty("项目进度百分比") + private Integer progress; + + @ApiModelProperty("团队成员列表") + private List teamMembers; + + @Data + @ApiModel(value="TeamMemberResp对象", description="团队成员响应") + public static class TeamMemberResp { + @ApiModelProperty("成员ID") + private String memberId; + + @ApiModelProperty("用户ID") + private String userId; + + @ApiModelProperty("用户姓名") + private String userName; + + @ApiModelProperty("用户账号") + private String userAccount; + + @ApiModelProperty("用户头像") + private String userAvatar; + + @ApiModelProperty("角色类型") + private String roleType; + + @ApiModelProperty("角色类型描述") + private String roleTypeDesc; + + @ApiModelProperty("岗位代码") + private String jobCode; + + @ApiModelProperty("岗位代码描述") + private String jobCodeDesc; + + @ApiModelProperty("岗位描述") + private String jobDesc; + + @ApiModelProperty("加入时间") + private LocalDate joinDate; + + @ApiModelProperty("离开时间") + private LocalDate leaveDate; + + @ApiModelProperty("状态") + private String status; + } } } \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectListReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectListReq.java index a285e33..02b087c 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectListReq.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectListReq.java @@ -1,7 +1,5 @@ package com.dite.znpt.domain.vo; -import com.baomidou.mybatisplus.annotation.TableField; -import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -23,6 +21,12 @@ public class ProjectListReq implements Serializable { @ApiModelProperty("项目名称") private String projectName; + @ApiModelProperty(value = "项目来源") + private String projectOrigin; + + @ApiModelProperty(value = "项目预算") + private Integer projectBudget; + @ApiModelProperty("风场名称") private String farmName; diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectListResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectListResp.java index f0578ef..61faa9b 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectListResp.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectListResp.java @@ -1,10 +1,5 @@ package com.dite.znpt.domain.vo; -import com.alibaba.excel.annotation.ExcelProperty; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -12,7 +7,6 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.time.LocalDate; -import java.time.LocalDateTime; /** * @Author: gaoxiong @@ -31,6 +25,12 @@ public class ProjectListResp implements Serializable { @ApiModelProperty("项目名称") private String projectName; + @ApiModelProperty(value = "项目来源") + private String projectOrigin; + + @ApiModelProperty(value = "项目预算") + private Integer projectBudget; + @ApiModelProperty("风场名称") private String farmName; diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectReq.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectReq.java index 501ac49..5fdcc16 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectReq.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectReq.java @@ -1,6 +1,5 @@ package com.dite.znpt.domain.vo; -import com.alibaba.excel.annotation.ExcelProperty; import com.dite.znpt.util.ValidationGroup; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -28,6 +27,14 @@ public class ProjectReq implements Serializable { @ApiModelProperty("项目id") private String projectId; + @NotBlank(groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "项目来源不能为空") + @ApiModelProperty("项目来源") + private String projectOrigin; + + @NotBlank(groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "项目预算不能为空") + @ApiModelProperty("项目预算") + private Integer projectBudget; + @NotBlank(groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, message = "项目名称不能为空") @Size(groups = {ValidationGroup.Insert.class, ValidationGroup.Update.class}, max = 50, message = "项目名称长度不能超过50字符") @ApiModelProperty("项目名称") @@ -105,4 +112,19 @@ public class ProjectReq implements Serializable { @ApiModelProperty(value = "结束时间") private LocalDate endDate; + + @ApiModelProperty("人工成本") + private Double laborCost; + + @ApiModelProperty("设备摊销") + private Double equipmentAmortization; + + @ApiModelProperty("奖金预提") + private Double bonusProvision; + + @ApiModelProperty("交通食宿") + private Double transAccomMeals; + + @ApiModelProperty("其他杂费") + private Double othersCost; } diff --git a/core/src/main/java/com/dite/znpt/domain/vo/ProjectResp.java b/core/src/main/java/com/dite/znpt/domain/vo/ProjectResp.java index 826a0f5..16eb289 100644 --- a/core/src/main/java/com/dite/znpt/domain/vo/ProjectResp.java +++ b/core/src/main/java/com/dite/znpt/domain/vo/ProjectResp.java @@ -1,19 +1,11 @@ package com.dite.znpt.domain.vo; -import com.alibaba.excel.annotation.ExcelProperty; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import lombok.EqualsAndHashCode; -import com.dite.znpt.domain.entity.ProjectEntity; import java.io.Serial; import java.io.Serializable; -import java.time.LocalDate; /** * @author huise23 @@ -27,6 +19,15 @@ public class ProjectResp extends ProjectReq implements Serializable { @Serial private static final long serialVersionUID = -1883901559600186726L; + @ApiModelProperty("项目ID") + private String projectId; + + @ApiModelProperty("项目来源") + private String projectOrigin; + + @ApiModelProperty("项目预算") + private Integer projectBudget; + @ApiModelProperty("施工人员") private String constructorName; diff --git a/core/src/main/java/com/dite/znpt/enums/ContractStatusEnum.java b/core/src/main/java/com/dite/znpt/enums/ContractStatusEnum.java new file mode 100644 index 0000000..0ba6bc0 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/enums/ContractStatusEnum.java @@ -0,0 +1,54 @@ +package com.dite.znpt.enums; + +import cn.hutool.json.JSONObject; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Bear.G + * @date 2025/5/27/周二 15:33 + * @description + */ +@Getter +public enum ContractStatusEnum { + + UN_APPROVAL("UN_APPROVAL", "待审批"), + UN_SETTLED("UN_SETTLED", "待结算"), + UN_PAY("UN_PAY", "待收款"), + COMPLETE("COMPLETE", "已完成"), + ; + + private final String code; + private final String desc; + + ContractStatusEnum(String code, String desc){ + this.code = code; + this.desc = desc; + } + + public static ContractStatusEnum getByCode(String code){ + for (ContractStatusEnum e : ContractStatusEnum.values() ) { + if(e.code.equals(code)){ + return e; + } + } + return null; + } + + public static String getDescByCode(String code){ + ContractStatusEnum e = getByCode(code); + return null == e ? null : e.desc; + } + + public static List listAll(){ + List list = new ArrayList<>(ContractStatusEnum.values().length); + for (ContractStatusEnum e : ContractStatusEnum.values() ) { + JSONObject jsonObject = new JSONObject(); + jsonObject.set(e.code, e.desc); + list.add(jsonObject); + } + return list; + } +} diff --git a/core/src/main/java/com/dite/znpt/enums/HealthStatusEnum.java b/core/src/main/java/com/dite/znpt/enums/HealthStatusEnum.java new file mode 100644 index 0000000..7b437d9 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/enums/HealthStatusEnum.java @@ -0,0 +1,53 @@ +package com.dite.znpt.enums; + +import cn.hutool.json.JSONObject; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Bear.G + * @date 2025/7/31/周四 15:30 + * @description 设备健康状态枚举 + */ +@Getter +public enum HealthStatusEnum { + EXCELLENT("excellent", "优秀"), + GOOD("good", "良好"), + NORMAL("normal", "一般"), + POOR("poor", "较差"), + BAD("bad", "差"); + + private final String code; + private final String desc; + + HealthStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HealthStatusEnum getByCode(String code) { + for (HealthStatusEnum e : HealthStatusEnum.values()) { + if (e.code.equals(code)) { + return e; + } + } + return null; + } + + public static String getDescByCode(String code) { + HealthStatusEnum e = getByCode(code); + return null == e ? null : e.desc; + } + + public static List listAll() { + List list = new ArrayList<>(HealthStatusEnum.values().length); + for (HealthStatusEnum e : HealthStatusEnum.values()) { + JSONObject jsonObject = new JSONObject(); + jsonObject.set(e.code, e.desc); + list.add(jsonObject); + } + return list; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/enums/LocationStatusEnum.java b/core/src/main/java/com/dite/znpt/enums/LocationStatusEnum.java new file mode 100644 index 0000000..9e04826 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/enums/LocationStatusEnum.java @@ -0,0 +1,55 @@ +package com.dite.znpt.enums; + +import cn.hutool.json.JSONObject; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Bear.G + * @date 2025/7/31/周四 15:30 + * @description 设备位置状态枚举 + */ +@Getter +public enum LocationStatusEnum { + IN_STOCK("in_stock", "库存中"), + ALLOCATED("allocated", "已分配"), + REPAIR("repair", "维修中"), + SCRAP("scrap", "待报废"), + SCRAPPED("scrapped", "已报废"), + BORROWED("borrowed", "外借中"), + LOST("lost", "丢失"); + + private final String code; + private final String desc; + + LocationStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static LocationStatusEnum getByCode(String code) { + for (LocationStatusEnum e : LocationStatusEnum.values()) { + if (e.code.equals(code)) { + return e; + } + } + return null; + } + + public static String getDescByCode(String code) { + LocationStatusEnum e = getByCode(code); + return null == e ? null : e.desc; + } + + public static List listAll() { + List list = new ArrayList<>(LocationStatusEnum.values().length); + for (LocationStatusEnum e : LocationStatusEnum.values()) { + JSONObject jsonObject = new JSONObject(); + jsonObject.set(e.code, e.desc); + list.add(jsonObject); + } + return list; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/dite/znpt/mapper/BusinessDataFileMapper.java b/core/src/main/java/com/dite/znpt/mapper/BusinessDataFileMapper.java new file mode 100644 index 0000000..6192769 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/mapper/BusinessDataFileMapper.java @@ -0,0 +1,20 @@ +package com.dite.znpt.mapper; + +import com.dite.znpt.domain.entity.BusinessDataFileEntity; +import io.swagger.annotations.ApiOperation; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@ApiOperation("商务资料文件对象") +public interface BusinessDataFileMapper { + public List List(@Param("folderId") Long folderId, @Param("fileName") String fileName); + void delete(@Param("fileId") Long fileId,@Param("folderId") Long folderId); + + void add(BusinessDataFileEntity businessDataFileEntity); + + String getPath(Long fileId); + + // 在接口中添加重命名方法 + void reName(@Param("fileId") Long fileId, @Param("newFileName") String newFileName, @Param("newFilePath") String newFilePath); +} diff --git a/core/src/main/java/com/dite/znpt/mapper/BusinessDataMapper.java b/core/src/main/java/com/dite/znpt/mapper/BusinessDataMapper.java new file mode 100644 index 0000000..b92887f --- /dev/null +++ b/core/src/main/java/com/dite/znpt/mapper/BusinessDataMapper.java @@ -0,0 +1,26 @@ +package com.dite.znpt.mapper; + +import com.dite.znpt.domain.entity.BusinessDataEntity; +import io.swagger.annotations.ApiOperation; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + + + + + +@ApiOperation("商务资料文件夹对象") +public interface BusinessDataMapper { + public String getPath(Long parentId); + + public List List(); + + void insert(BusinessDataEntity businessDataEntity); + + void delete(Long folderId); + + void reName(BusinessDataEntity businessDataEntity); + + public List ListWithCondition(@Param("folderName") String folderName); +} diff --git a/core/src/main/java/com/dite/znpt/service/BusinessDataFileService.java b/core/src/main/java/com/dite/znpt/service/BusinessDataFileService.java new file mode 100644 index 0000000..5b8d99c --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/BusinessDataFileService.java @@ -0,0 +1,27 @@ +package com.dite.znpt.service; + +import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.entity.BusinessDataFileEntity; +import com.dite.znpt.domain.page.PageBean; +import io.swagger.annotations.ApiOperation; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestParam; + +@ApiOperation("商务资料文件service") +@Service +public interface BusinessDataFileService { + + PageBean pageSelect(Integer page, Integer pageSize, Long folderId, String fileName); + + Result delete(@RequestParam(value = "fileId", required = false) Long fileId,@RequestParam(value = "foldelId", required = false) Long folderId); + + void add(BusinessDataFileEntity businessDataFileEntity); + + String getPath(Long fileId); + + // 在接口中添加重命名方法 + Result reName(Long fileId, String newFileName); + + // 在接口中添加预览方法 +// Result preview(Long fileId, HttpServletResponse response); +} diff --git a/core/src/main/java/com/dite/znpt/service/BusinessDataService.java b/core/src/main/java/com/dite/znpt/service/BusinessDataService.java new file mode 100644 index 0000000..ec37987 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/BusinessDataService.java @@ -0,0 +1,15 @@ +package com.dite.znpt.service; + +import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.page.PageBean; +import io.swagger.annotations.ApiOperation; + +@ApiOperation("商务资料文件夹service") +public interface BusinessDataService { + + PageBean pageSelect(Integer page, Integer pageSize, String folderName); + Result createFolder(String folderName, Long parentId); + String getPath(Long parentId); + Result delete(Long folderId); + Result reName(Long folderId, String newName); +} diff --git a/core/src/main/java/com/dite/znpt/service/EmailService.java b/core/src/main/java/com/dite/znpt/service/EmailService.java new file mode 100644 index 0000000..51a05f0 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/EmailService.java @@ -0,0 +1,30 @@ +//package com.dite.znpt.service; +// +//import org.springframework.stereotype.Service; +// +//@Service +//public interface EmailService { +// /** +// * 发送邮箱验证码 +// * @param email +// * @param code +// * @return +// */ +// public boolean sendVerificationCode(String email, String code); +// +// /** +// * 生成验证码 +// * @param email +// * @return +// */ +// public String generateCode(String email); +// +// /** +// * 验证邮箱验证码 +// * @param email +// * @param code +// * @return +// */ +// public boolean verifyCode(String email, String code); +// +//} diff --git a/core/src/main/java/com/dite/znpt/service/EquipmentService.java b/core/src/main/java/com/dite/znpt/service/EquipmentService.java index 5c21c21..c5a6c58 100644 --- a/core/src/main/java/com/dite/znpt/service/EquipmentService.java +++ b/core/src/main/java/com/dite/znpt/service/EquipmentService.java @@ -1,24 +1,74 @@ package com.dite.znpt.service; +import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; import com.dite.znpt.domain.entity.EquipmentEntity; import com.dite.znpt.domain.vo.EquipmentListReq; import com.dite.znpt.domain.vo.EquipmentReq; import com.dite.znpt.domain.vo.EquipmentResp; -import com.dite.znpt.domain.vo.EquipmentUseRecordReq; import java.util.List; /** + * 设备服务接口 * @author Bear.G * @date 2025/7/23/周三 17:39 * @description */ public interface EquipmentService extends IService { - List page(EquipmentListReq req); - List list(EquipmentListReq req); + + /** + * 分页查询设备信息 + */ + IPage page(EquipmentListReq req); + + /** + * 查询设备详情 + */ EquipmentResp detail(String equipmentId); + + /** + * 新增设备 + */ void save(EquipmentReq req); + + /** + * 修改设备 + */ void update(String equipmentId, EquipmentReq req); + + /** + * 删除设备 + */ void deleteById(String equipmentId); + + /** + * 批量删除设备 + */ + void batchDelete(List equipmentIds); + + /** + * 获取采购统计信息 + */ + Object getProcurementStats(); + + /** + * 导出采购记录 + */ + byte[] exportProcurement(EquipmentListReq req); + + /** + * 分页查询设备采购记录 + */ + IPage procurementPage(EquipmentListReq req); + + /** + * 新增设备采购记录 + */ + void saveProcurement(EquipmentReq req); + + /** + * 修改设备采购记录 + */ + void updateProcurement(String equipmentId, EquipmentReq req); } diff --git a/core/src/main/java/com/dite/znpt/service/ProjectBudgetInfoService.java b/core/src/main/java/com/dite/znpt/service/ProjectBudgetInfoService.java index 72aa0f0..dd8c56a 100644 --- a/core/src/main/java/com/dite/znpt/service/ProjectBudgetInfoService.java +++ b/core/src/main/java/com/dite/znpt/service/ProjectBudgetInfoService.java @@ -2,10 +2,11 @@ package com.dite.znpt.service; import com.baomidou.mybatisplus.extension.service.IService; import com.dite.znpt.domain.entity.ProjectBudgetInfoEntity; +import com.dite.znpt.domain.vo.ProjectBudgetInfoDetailResp; +import com.dite.znpt.domain.vo.ProjectBudgetInfoImportReq; import com.dite.znpt.domain.vo.ProjectBudgetInfoListReq; import com.dite.znpt.domain.vo.ProjectBudgetInfoListResp; -import com.dite.znpt.domain.vo.ProjectBudgetInfoReq; -import com.dite.znpt.domain.vo.ProjectBudgetInfoResp; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -15,34 +16,16 @@ import java.util.List; * @Description: 项目预算信息表服务接口 */ public interface ProjectBudgetInfoService extends IService { + List list(ProjectBudgetInfoListReq projectBudgetInfoListReq); - /** - * 功能描述:查询项目预算信息列表 - * - * @param projectBudgetInfoReq 项目预算信息 - * @return {@link List }<{@link ProjectBudgetInfoListResp }> - * @author huise23 - * @date 2025/07/17 21:58 - **/ - List selectList(ProjectBudgetInfoListReq projectBudgetInfoReq); + List page(ProjectBudgetInfoListReq projectBudgetInfoListReq); - /** - * 功能描述:根据项目id获取项目预算信息列表 - * - * @param projectId 项目id - * @return {@link List }<{@link ProjectBudgetInfoListResp }> - * @author huise23 - * @date 2025/07/17 21:58 - **/ - List detailByProjectId(String projectId); + void saveData(ProjectBudgetInfoImportReq req); - /** - * 功能描述:新增项目预算信息 - * - * @param projectBudgetInfoReq 项目预算信息 - * @author huise23 - * @date 2025/07/17 21:58 - **/ - void saveData(List projectBudgetInfoReq); + void saveData(ProjectBudgetInfoImportReq req, MultipartFile[] files); + + ProjectBudgetInfoDetailResp detailByProjectId(String projectId); + + void delete(String budgetId); } diff --git a/core/src/main/java/com/dite/znpt/service/UserPostService.java b/core/src/main/java/com/dite/znpt/service/UserPostService.java index bc14fd1..1060aff 100644 --- a/core/src/main/java/com/dite/znpt/service/UserPostService.java +++ b/core/src/main/java/com/dite/znpt/service/UserPostService.java @@ -1,9 +1,9 @@ package com.dite.znpt.service; import com.baomidou.mybatisplus.extension.service.IService; -import com.dite.znpt.domain.entity.PostEntity; import com.dite.znpt.domain.entity.UserPostEntity; import com.dite.znpt.domain.vo.PostResp; +import com.dite.znpt.domain.vo.UserResp; import java.util.List; @@ -16,4 +16,5 @@ public interface UserPostService extends IService { List getPostsByUserId(String userId); void bindUserPost(String userId, List postIds); void bindPostUser(String postId, List userIds); + List getUsersByPostId(String postId); } diff --git a/core/src/main/java/com/dite/znpt/service/UserRoleService.java b/core/src/main/java/com/dite/znpt/service/UserRoleService.java index f4e508d..8527e4d 100644 --- a/core/src/main/java/com/dite/znpt/service/UserRoleService.java +++ b/core/src/main/java/com/dite/znpt/service/UserRoleService.java @@ -3,6 +3,7 @@ package com.dite.znpt.service; import com.baomidou.mybatisplus.extension.service.IService; import com.dite.znpt.domain.entity.UserRoleEntity; import com.dite.znpt.domain.vo.RoleResp; +import com.dite.znpt.domain.vo.UserResp; import com.dite.znpt.domain.vo.UserRoleReq; import java.util.List; @@ -19,4 +20,6 @@ public interface UserRoleService extends IService { void bindUserRole(UserRoleReq req); void bindRoleUser(String roleId, List userIds); + + List getUsersByRoleId(String roleId); } diff --git a/core/src/main/java/com/dite/znpt/service/impl/BusinessDataFileServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/BusinessDataFileServiceImpl.java new file mode 100644 index 0000000..2392b49 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/impl/BusinessDataFileServiceImpl.java @@ -0,0 +1,163 @@ +package com.dite.znpt.service.impl; + + + +import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.entity.BusinessDataFileEntity; +import com.dite.znpt.domain.page.PageBean; +import com.dite.znpt.mapper.BusinessDataFileMapper; +import com.dite.znpt.service.BusinessDataFileService; +import com.github.pagehelper.Page; +import com.github.pagehelper.PageHelper; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static jodd.io.FileUtil.deleteFile; +import static org.apache.tomcat.util.http.fileupload.FileUtils.deleteDirectory; + +@AllArgsConstructor +@Service +@ApiOperation("商务资料文件service实现类") +public class BusinessDataFileServiceImpl implements BusinessDataFileService { + @Resource + private BusinessDataFileMapper businessDataFileMapper; + + + @ApiOperation("分页查询文件") + @Override + public PageBean pageSelect(Integer page, Integer pageSize, Long folderId, String fileName) { + PageHelper.startPage(page, pageSize); + List list = businessDataFileMapper.List(folderId, fileName); + Page p = (Page) list; + PageBean pageBean = new PageBean(p.getTotal(), p.getResult()); + return pageBean; + } + @ApiOperation("删除文件") + public Result delete(Long fileId, Long folderId) { + //删除数据库数据 + if (folderId != null){ + businessDataFileMapper.delete(null,folderId); + System.out.println("888888888走对了"); + + return Result.okM("删除,走对了,成功"); + } + + //删除文件 + String sPath = businessDataFileMapper.getPath(fileId); + + businessDataFileMapper.delete(fileId,null); + + boolean flag = false; + File file = new File(sPath); + // 判断目录或文件是否存在 + if (!file.exists()) { // 不存在返回 false + return Result.error("文件不存在"); + } else { + // 判断是否为文件 + if (file.isFile()) { // 为文件时调用删除文件方法 + try { + deleteFile(file); + } catch (IOException e) { + throw new RuntimeException(e); + } + return Result.okM("删除成功"); + } else { // 为目录时调用删除目录方法 + try { + deleteDirectory(file); + } catch (IOException e) { + throw new RuntimeException(e); + } + return Result.okM("删除成功"); + } + } + + + } + + @ApiOperation("增加文件") + public void add(BusinessDataFileEntity businessDataFileEntity) { + businessDataFileMapper.add(businessDataFileEntity); + } + + @ApiOperation("获取文件路径") + public String getPath(Long fileId) { + return businessDataFileMapper.getPath(fileId); + } + + @ApiOperation("重命名文件") + @Override + public Result reName(Long fileId, String newFileName) { + // 参数校验 + if (fileId == null) { + return Result.error("文件ID不能为空"); + } + if (newFileName == null || newFileName.trim().isEmpty()) { + return Result.error("新文件名不能为空"); + } + if (newFileName.length() > 100) { + return Result.error("文件名过长"); + } + + try { + // 获取原文件信息 + String oldFilePath = businessDataFileMapper.getPath(fileId); + if (oldFilePath == null) { + return Result.error("文件不存在"); + } + + // 创建原文件对象 + File oldFile = new File(oldFilePath); + if (!oldFile.exists()) { + return Result.error("文件不存在"); + } + + // 构建新文件路径 + String parentPath = oldFile.getParent(); + String fileExtension = ""; + String fileNameWithoutExt = newFileName; + + // 获取原文件扩展名 + int lastDotIndex = oldFile.getName().lastIndexOf('.'); + if (lastDotIndex > 0) { + fileExtension = oldFile.getName().substring(lastDotIndex); + } + + // 如果新文件名没有扩展名,则添加原文件扩展名 + if (!newFileName.contains(".")) { + newFileName = newFileName + fileExtension; + } + + // 构建新文件路径 + String newFilePath = parentPath + File.separator + newFileName; + File newFile = new File(newFilePath); + + // 检查新文件名是否已存在 + if (newFile.exists()) { + return Result.error("文件名已存在"); + } + + // 重命名实际文件 + boolean renameSuccess = oldFile.renameTo(newFile); + if (!renameSuccess) { + return Result.error("文件重命名失败"); + } + + // 更新数据库中的文件信息 + businessDataFileMapper.reName(fileId, newFileName, newFilePath); + + return Result.okM("文件重命名成功"); + + } catch (Exception e) { + return Result.error("文件重命名失败: " + e.getMessage()); + } + } + + + +} diff --git a/core/src/main/java/com/dite/znpt/service/impl/BusinessDataServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/BusinessDataServiceImpl.java new file mode 100644 index 0000000..3d74793 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/impl/BusinessDataServiceImpl.java @@ -0,0 +1,197 @@ +package com.dite.znpt.service.impl; + +import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.entity.BusinessDataEntity; +import com.dite.znpt.domain.page.PageBean; +import com.dite.znpt.mapper.BusinessDataMapper; +import com.dite.znpt.service.BusinessDataFileService; +import com.dite.znpt.service.BusinessDataService; +import com.github.pagehelper.Page; +import com.github.pagehelper.PageHelper; +import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; + +/** + * 商务资料文件夹实现类 + */ +@Service +@ApiOperation("商务资料文件夹service实现类") +@AllArgsConstructor +@NoArgsConstructor +public class BusinessDataServiceImpl implements BusinessDataService { + @Resource + private BusinessDataMapper businessDataMapper; + @Resource + private BusinessDataFileService businessDataFileService; + + // 从配置文件中读取基础路径(默认值:D:/upload/businessData) + // ,新建文件夹的时候,如果没指定父文件夹Id,就用这个 + @Value("${file.upload.businessDataPath:D:/upload/businessData}") + private String businessDataPath; + + @ApiOperation(value = "分页查询") + @Override + public PageBean pageSelect(Integer page, Integer pageSize, String folderName) { + PageHelper.startPage(page, pageSize); + List businessDataEntityList = businessDataMapper.ListWithCondition(folderName); + Page p = (Page) businessDataEntityList; + PageBean pageBean = new PageBean(p.getTotal(), p.getResult()); + return pageBean; + } + + @ApiOperation(value = "创建文件夹") + @Override + public Result createFolder(String folderName, Long parentId) { + //获取ID + Long loginIdAsLong = 888L; +// loginIdAsLong = StpUtil.getLoginIdAsLong(); +// + // 文件夹名称不能为空 + //TODO: 添加文件夹名称校验,后续最好更规范些,写个工具类专门校验,用正则表达式 + if (folderName == null || folderName.trim().isEmpty()) { + return Result.error("文件夹名称不能为空"); + } + if (folderName.length() > 50) { + return Result.error("文件夹名称过长"); + } + + // 文件夹名称前置一个/ + String folderName1 = "/" + folderName; + // 目标文件夹 + File targetDir=Paths.get(businessDataPath, folderName1).toFile(); + if(parentId != 0L){ + // 获取父文件夹路径 + targetDir = Paths.get(businessDataMapper.getPath(parentId), folderName1).toFile(); + } + // 创建文件夹和新增文件夹路径 + if (!targetDir.exists()) { + // 创建文件夹 + boolean created = targetDir.mkdirs(); + if (!created) { + throw new RuntimeException("文件夹创建失败: " + targetDir.getAbsolutePath()); + } + //上面是新增文件夹功能,但没有往数据库插入文件夹相关数据,所以下面新增 + // 创建BusinessDataEntity对象并设置属性 + BusinessDataEntity businessDataEntity = new BusinessDataEntity( + null, + folderName, + parentId, + loginIdAsLong, + LocalDateTime.now(), + LocalDateTime.now(), + false, + targetDir.getAbsolutePath() + ); + // 插入到数据库 + businessDataMapper.insert(businessDataEntity); + return Result.okM( "文件夹创建成功"); + } else { + return Result.error("文件夹已存在: "); + } + } + + @ApiOperation("获取文件夹路径") + public String getPath(Long parentId) { + return businessDataMapper.getPath(parentId); + } + +// @ApiOperation("删除文件夹") +// @Override +// public Result delete(Long folderId) { +// // 获取文件夹路径 +// String folderPath = businessDataMapper.getPath(folderId); +// +// // 创建File对象并删除文件夹 +// File folder = new File(folderPath); +// if (folder.exists()) { +// boolean deleted = folder.delete(); +// if (!deleted) { +// // throw new RuntimeException("文件夹删除失败: " + folderPath); +// // TODO: 以后可以用全局异常处理器捕获,或者用try catch捕获 +// return Result.error("文件夹删除失败: " + folderPath); +// } +// //删除数据库中文件夹的数据 +// businessDataMapper.delete(folderId); +// //删除文件夹下文件的数据 +// businessDataFileService.delete(folderId); +// return Result.okM("删除成功"); +// } else { +// // throw new RuntimeException("文件夹不存在: " + folderPath); +// return Result.error("文件夹不存在: " + folderPath); +// } +// } + @ApiOperation("删除文件夹") + @Override + public Result delete(Long folderId) { + // 获取文件夹路径 + String folderPath = businessDataMapper.getPath(folderId); + + // 创建Path对象并删除文件夹 + Path folder = Paths.get(folderPath); + if (Files.exists(folder)) { + try { + // 使用Files.walk获取所有文件和目录,按深度排序后删除 + java.util.stream.Stream filePaths = Files.walk(folder); + filePaths.sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + filePaths.close(); + + //删除数据库中文件夹的数据 + businessDataMapper.delete(folderId); + //删除文件夹下文件的数据 + businessDataFileService.delete(null , folderId); + return Result.okM("删除成功"); + } catch (Exception e) { + return Result.okM("删除成功"); + } + } else { + return Result.error("文件夹不存在: " + folderPath); + } + } + @ApiOperation("重命名文件夹") + @Override + public Result reName(Long folderId, String newName) { + // 获取文件夹路径 + String folderPath = businessDataMapper.getPath(folderId); + String newPath = folderPath.substring(0, folderPath.lastIndexOf('\\'))+"\\" + newName; + System.out.printf("7777777"+newPath); +// +// //想命名的原文件的路径 +// File file = new File("f:/a/a.xlsx"); +// //将原文件更改为f:\a\b.xlsx,其中路径是必要的。注意 +// file.renameTo(new File("f:/a/b.xlsx")); + //想命名的原文件夹的路径 + File file1 = new File(folderPath); + //将原文件夹更改为A,其中路径是必要的。注意 + file1.renameTo(new File(newPath)); + LocalDateTime now = LocalDateTime.now(); + + BusinessDataEntity businessDataEntity = new BusinessDataEntity( + folderId, + newName, + null, + null, + null, + now, + null, + newPath + ); + System.out.println(businessDataEntity); + businessDataMapper.reName(businessDataEntity); + return Result.okM("重命名成功"); + } + +} diff --git a/core/src/main/java/com/dite/znpt/service/impl/ContractServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/ContractServiceImpl.java index 2f830c9..8794210 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/ContractServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/ContractServiceImpl.java @@ -1,17 +1,18 @@ package com.dite.znpt.service.impl; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.dite.znpt.domain.entity.ContractEntity; import com.dite.znpt.domain.vo.ContractListReq; -import com.dite.znpt.domain.vo.ContractResp; import com.dite.znpt.domain.vo.ContractReq; -import com.dite.znpt.service.ContractService; +import com.dite.znpt.domain.vo.ContractResp; +import com.dite.znpt.enums.ContractStatusEnum; import com.dite.znpt.mapper.ContractMapper; -import org.springframework.stereotype.Service; -import cn.hutool.core.collection.CollUtil; -import lombok.RequiredArgsConstructor; +import com.dite.znpt.service.ContractService; import com.dite.znpt.util.PageUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import java.util.List; @@ -37,7 +38,7 @@ public class ContractServiceImpl extends ServiceImpl contractList= this.baseMapper.queryBySelective(contractReq); contractList.forEach(resp -> { - + resp.setContractStatusLabel(ContractStatusEnum.getDescByCode(resp.getContractStatus())); }); return contractList; } diff --git a/core/src/main/java/com/dite/znpt/service/impl/EmailServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/EmailServiceImpl.java new file mode 100644 index 0000000..42c9ed7 --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/impl/EmailServiceImpl.java @@ -0,0 +1,66 @@ +//package com.dite.znpt.service.impl; +// +//import com.dite.znpt.service.EmailService; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.http.HttpStatus; +//import org.springframework.mail.SimpleMailMessage; +//import org.springframework.mail.javamail.JavaMailSender; +//import org.springframework.stereotype.Service; +//import org.springframework.web.server.ResponseStatusException; +// +//import java.util.Random; +// +//@Slf4j +//@Service +//public class EmailServiceImpl implements EmailService { +// +// @Autowired +// private JavaMailSender mailSender; +// +// @Autowired +// private InMemoryVerificationCodeStore codeStore; +// +// @Value("${email.verification.from}") +// private String from; +// @Value("${email.verification.subject}") +// private String subject; +// @Value("${email.verification.template}") +// private String template; +// +// @Override +// public boolean sendVerificationCode(String email, String code) { +// SimpleMailMessage message = new SimpleMailMessage(); +// message.setFrom(from); +// message.setSubject(subject); +// message.setTo(email); +// message.setText(String.format(template,code)); +// try{ +// mailSender.send(message); +// return true; +// }catch (Exception e){ +// log.error("发送邮件失败:",e); +// return false; +// } +// } +// +// @Override +// public String generateCode(String email) { +// if (!codeStore.canSend(email)) { +// throw new ResponseStatusException( +// HttpStatus.TOO_MANY_REQUESTS, // 429 状态码 +// "验证码发送过于频繁,请稍后再试" +// ); +// } +// +// String code = String.format("%04d", new Random().nextInt(9999)); +// codeStore.save(email, code); +// return code; +// } +// +// @Override +// public boolean verifyCode(String email, String code) { +// return codeStore.validate(email, code); +// } +//} diff --git a/core/src/main/java/com/dite/znpt/service/impl/EquipmentServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/EquipmentServiceImpl.java index 8b6b013..8f15c6e 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/EquipmentServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/EquipmentServiceImpl.java @@ -1,97 +1,418 @@ package com.dite.znpt.service.impl; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; +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.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.dite.znpt.constant.Message; -import com.dite.znpt.converts.Converts; import com.dite.znpt.domain.entity.EquipmentEntity; -import com.dite.znpt.domain.entity.EquipmentUseRecordEntity; import com.dite.znpt.domain.vo.EquipmentListReq; import com.dite.znpt.domain.vo.EquipmentReq; import com.dite.znpt.domain.vo.EquipmentResp; -import com.dite.znpt.domain.vo.EquipmentUseRecordReq; import com.dite.znpt.enums.EquipmentStatusEnum; import com.dite.znpt.enums.EquipmentTypeEnum; +import com.dite.znpt.enums.HealthStatusEnum; +import com.dite.znpt.enums.LocationStatusEnum; import com.dite.znpt.exception.ServiceException; import com.dite.znpt.mapper.EquipmentMapper; import com.dite.znpt.service.EquipmentService; -import com.dite.znpt.service.EquipmentUseRecordService; -import com.dite.znpt.service.ProjectService; -import com.dite.znpt.service.UserService; -import com.dite.znpt.util.PageUtil; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; -import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.List; +import java.util.stream.Collectors; /** * @author Bear.G * @date 2025/7/23/周三 17:39 * @description */ +@Slf4j @Service public class EquipmentServiceImpl extends ServiceImpl implements EquipmentService { - @Resource - private EquipmentUseRecordService equipmentUseRecordService; - - @Resource - private UserService userService; - - @Resource - private ProjectService projectService; - @Override - public List page(EquipmentListReq req) { - PageUtil.startPage(); - return this.list(req); - } - - @Override - public List list(EquipmentListReq req) { - List list = this.baseMapper.selectEquipmentResp(req); - list.forEach(resp -> { - resp.setEquipmentTypeLabel(EquipmentTypeEnum.getDescByCode(resp.getEquipmentType())); - resp.setEquipmentStatusLabel(EquipmentStatusEnum.getDescByCode(resp.getEquipmentStatus())); - }); - return list; + public IPage page(EquipmentListReq req) { + log.info("开始执行设备分页查询,请求参数: {}", req); + + // 创建分页对象,处理null值 + Integer pageNum = req.getPage() != null ? req.getPage() : (req.getPageNum() != null ? req.getPageNum() : 1); + Integer pageSize = req.getPageSize() != null ? req.getPageSize() : 10; + Page page = new Page<>(pageNum, pageSize); + + // 构建查询条件 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + // 不需要手动添加 del_flag 条件,@TableLogic 注解会自动处理 + + // 添加搜索条件并记录日志 + int conditionCount = 0; + + if (StringUtils.hasText(req.getEquipmentName())) { + queryWrapper.like(EquipmentEntity::getEquipmentName, req.getEquipmentName()); + log.info("添加设备名称查询条件: {}", req.getEquipmentName()); + conditionCount++; + } + if (StringUtils.hasText(req.getEquipmentType())) { + queryWrapper.eq(EquipmentEntity::getEquipmentType, req.getEquipmentType()); + log.info("添加设备类型查询条件: {}", req.getEquipmentType()); + conditionCount++; + } + if (StringUtils.hasText(req.getEquipmentStatus())) { + queryWrapper.eq(EquipmentEntity::getEquipmentStatus, req.getEquipmentStatus()); + log.info("添加设备状态查询条件: {}", req.getEquipmentStatus()); + conditionCount++; + } + if (StringUtils.hasText(req.getEquipmentSn())) { + queryWrapper.like(EquipmentEntity::getEquipmentSn, req.getEquipmentSn()); + log.info("添加设备序列号查询条件: {}", req.getEquipmentSn()); + conditionCount++; + } + if (StringUtils.hasText(req.getAssetCode())) { + queryWrapper.like(EquipmentEntity::getAssetCode, req.getAssetCode()); + log.info("添加资产编号查询条件: {}", req.getAssetCode()); + conditionCount++; + } + if (StringUtils.hasText(req.getBrand())) { + queryWrapper.like(EquipmentEntity::getBrand, req.getBrand()); + log.info("添加品牌查询条件: {}", req.getBrand()); + conditionCount++; + } + if (StringUtils.hasText(req.getLocationStatus())) { + queryWrapper.eq(EquipmentEntity::getLocationStatus, req.getLocationStatus()); + log.info("添加位置状态查询条件: {}", req.getLocationStatus()); + conditionCount++; + } + if (StringUtils.hasText(req.getHealthStatus())) { + queryWrapper.eq(EquipmentEntity::getHealthStatus, req.getHealthStatus()); + log.info("添加健康状态查询条件: {}", req.getHealthStatus()); + conditionCount++; + } + if (StringUtils.hasText(req.getResponsiblePerson())) { + queryWrapper.like(EquipmentEntity::getResponsiblePerson, req.getResponsiblePerson()); + log.info("添加负责人查询条件: {}", req.getResponsiblePerson()); + conditionCount++; + } + if (StringUtils.hasText(req.getUseStatus())) { + queryWrapper.eq(EquipmentEntity::getUseStatus, req.getUseStatus()); + log.info("添加使用状态查询条件: {}", req.getUseStatus()); + conditionCount++; + } + + // 新增查询条件 + if (StringUtils.hasText(req.getEquipmentModel())) { + queryWrapper.like(EquipmentEntity::getEquipmentModel, req.getEquipmentModel()); + log.info("添加设备型号查询条件: {}", req.getEquipmentModel()); + conditionCount++; + } + if (StringUtils.hasText(req.getSpecification())) { + queryWrapper.like(EquipmentEntity::getSpecification, req.getSpecification()); + log.info("添加配置规格查询条件: {}", req.getSpecification()); + conditionCount++; + } + if (StringUtils.hasText(req.getPhysicalLocation())) { + queryWrapper.like(EquipmentEntity::getPhysicalLocation, req.getPhysicalLocation()); + log.info("添加物理位置查询条件: {}", req.getPhysicalLocation()); + conditionCount++; + } + if (StringUtils.hasText(req.getSupplierName())) { + queryWrapper.like(EquipmentEntity::getSupplierName, req.getSupplierName()); + log.info("添加供应商名称查询条件: {}", req.getSupplierName()); + conditionCount++; + } + if (StringUtils.hasText(req.getMaintenancePerson())) { + queryWrapper.like(EquipmentEntity::getMaintenancePerson, req.getMaintenancePerson()); + log.info("添加维护人员查询条件: {}", req.getMaintenancePerson()); + conditionCount++; + } + if (StringUtils.hasText(req.getInventoryBarcode())) { + queryWrapper.like(EquipmentEntity::getInventoryBarcode, req.getInventoryBarcode()); + log.info("添加库存条码查询条件: {}", req.getInventoryBarcode()); + conditionCount++; + } + if (StringUtils.hasText(req.getAssetRemark())) { + queryWrapper.like(EquipmentEntity::getAssetRemark, req.getAssetRemark()); + log.info("添加资产备注查询条件: {}", req.getAssetRemark()); + conditionCount++; + } + + // 新增字段查询条件 + if (StringUtils.hasText(req.getUsingDepartment())) { + queryWrapper.like(EquipmentEntity::getUsingDepartment, req.getUsingDepartment()); + log.info("添加使用部门/人查询条件: {}", req.getUsingDepartment()); + conditionCount++; + } + if (StringUtils.hasText(req.getInvoice())) { + queryWrapper.like(EquipmentEntity::getInvoice, req.getInvoice()); + log.info("添加发票查询条件: {}", req.getInvoice()); + conditionCount++; + } + if (StringUtils.hasText(req.getBarcode())) { + queryWrapper.like(EquipmentEntity::getBarcode, req.getBarcode()); + log.info("添加条码查询条件: {}", req.getBarcode()); + conditionCount++; + } + if (StringUtils.hasText(req.getImporter())) { + queryWrapper.like(EquipmentEntity::getImporter, req.getImporter()); + log.info("添加导入人查询条件: {}", req.getImporter()); + conditionCount++; + } + + // 新增采购相关字段查询条件 + if (StringUtils.hasText(req.getAccountNumber())) { + queryWrapper.like(EquipmentEntity::getAccountNumber, req.getAccountNumber()); + log.info("添加次户号查询条件: {}", req.getAccountNumber()); + conditionCount++; + } + if (req.getQuantity() != null) { + queryWrapper.eq(EquipmentEntity::getQuantity, req.getQuantity()); + log.info("添加数量查询条件: {}", req.getQuantity()); + conditionCount++; + } + if (req.getUnitPrice() != null) { + queryWrapper.eq(EquipmentEntity::getUnitPrice, req.getUnitPrice()); + log.info("添加单价查询条件: {}", req.getUnitPrice()); + conditionCount++; + } + if (req.getTotalPrice() != null) { + queryWrapper.eq(EquipmentEntity::getTotalPrice, req.getTotalPrice()); + log.info("添加总价查询条件: {}", req.getTotalPrice()); + conditionCount++; + } + if (StringUtils.hasText(req.getInventoryBasis())) { + queryWrapper.like(EquipmentEntity::getInventoryBasis, req.getInventoryBasis()); + log.info("添加盘点依据查询条件: {}", req.getInventoryBasis()); + conditionCount++; + } + if (StringUtils.hasText(req.getDynamicRecord())) { + queryWrapper.like(EquipmentEntity::getDynamicRecord, req.getDynamicRecord()); + log.info("添加动态记录查询条件: {}", req.getDynamicRecord()); + conditionCount++; + } + + // 时间范围查询 + if (req.getPurchaseTimeStart() != null) { + queryWrapper.ge(EquipmentEntity::getPurchaseTime, req.getPurchaseTimeStart()); + log.info("添加采购时间开始查询条件: {}", req.getPurchaseTimeStart()); + conditionCount++; + } + if (req.getPurchaseTimeEnd() != null) { + queryWrapper.le(EquipmentEntity::getPurchaseTime, req.getPurchaseTimeEnd()); + log.info("添加采购时间结束查询条件: {}", req.getPurchaseTimeEnd()); + conditionCount++; + } + if (req.getInStockTimeStart() != null) { + queryWrapper.ge(EquipmentEntity::getInStockTime, req.getInStockTimeStart()); + conditionCount++; + } + if (req.getInStockTimeEnd() != null) { + queryWrapper.le(EquipmentEntity::getInStockTime, req.getInStockTimeEnd()); + conditionCount++; + } + if (req.getActivationTimeStart() != null) { + queryWrapper.ge(EquipmentEntity::getActivationTime, req.getActivationTimeStart()); + conditionCount++; + } + if (req.getActivationTimeEnd() != null) { + queryWrapper.le(EquipmentEntity::getActivationTime, req.getActivationTimeEnd()); + conditionCount++; + } + if (req.getExpectedScrapTimeStart() != null) { + queryWrapper.ge(EquipmentEntity::getExpectedScrapTime, req.getExpectedScrapTimeStart()); + conditionCount++; + } + if (req.getExpectedScrapTimeEnd() != null) { + queryWrapper.le(EquipmentEntity::getExpectedScrapTime, req.getExpectedScrapTimeEnd()); + conditionCount++; + } + if (req.getWarrantyExpireDateStart() != null) { + queryWrapper.ge(EquipmentEntity::getWarrantyExpireDate, req.getWarrantyExpireDateStart()); + conditionCount++; + } + if (req.getWarrantyExpireDateEnd() != null) { + queryWrapper.le(EquipmentEntity::getWarrantyExpireDate, req.getWarrantyExpireDateEnd()); + conditionCount++; + } + if (req.getLastMaintenanceDateStart() != null) { + queryWrapper.ge(EquipmentEntity::getLastMaintenanceDate, req.getLastMaintenanceDateStart()); + conditionCount++; + } + if (req.getLastMaintenanceDateEnd() != null) { + queryWrapper.le(EquipmentEntity::getLastMaintenanceDate, req.getLastMaintenanceDateEnd()); + conditionCount++; + } + if (req.getNextMaintenanceDateStart() != null) { + queryWrapper.ge(EquipmentEntity::getNextMaintenanceDate, req.getNextMaintenanceDateStart()); + conditionCount++; + } + if (req.getNextMaintenanceDateEnd() != null) { + queryWrapper.le(EquipmentEntity::getNextMaintenanceDate, req.getNextMaintenanceDateEnd()); + conditionCount++; + } + if (req.getCreateTimeStart() != null) { + queryWrapper.ge(EquipmentEntity::getCreateTime, req.getCreateTimeStart()); + conditionCount++; + } + if (req.getCreateTimeEnd() != null) { + queryWrapper.le(EquipmentEntity::getCreateTime, req.getCreateTimeEnd()); + conditionCount++; + } + if (req.getUpdateTimeStart() != null) { + queryWrapper.ge(EquipmentEntity::getUpdateTime, req.getUpdateTimeStart()); + conditionCount++; + } + if (req.getUpdateTimeEnd() != null) { + queryWrapper.le(EquipmentEntity::getUpdateTime, req.getUpdateTimeEnd()); + conditionCount++; + } + + log.info("总共添加了 {} 个查询条件", conditionCount); + + // 排序处理 + if (StringUtils.hasText(req.getOrderBy())) { + String orderBy = req.getOrderBy(); + String orderDirection = "desc".equalsIgnoreCase(req.getOrderDirection()) ? "desc" : "asc"; + + switch (orderBy.toLowerCase()) { + case "equipment_name": + if ("desc".equals(orderDirection)) { + queryWrapper.orderByDesc(EquipmentEntity::getEquipmentName); + } else { + queryWrapper.orderByAsc(EquipmentEntity::getEquipmentName); + } + break; + case "equipment_sn": + if ("desc".equals(orderDirection)) { + queryWrapper.orderByDesc(EquipmentEntity::getEquipmentSn); + } else { + queryWrapper.orderByAsc(EquipmentEntity::getEquipmentSn); + } + break; + case "asset_code": + if ("desc".equals(orderDirection)) { + queryWrapper.orderByDesc(EquipmentEntity::getAssetCode); + } else { + queryWrapper.orderByAsc(EquipmentEntity::getAssetCode); + } + break; + case "equipment_type": + if ("desc".equals(orderDirection)) { + queryWrapper.orderByDesc(EquipmentEntity::getEquipmentType); + } else { + queryWrapper.orderByAsc(EquipmentEntity::getEquipmentType); + } + break; + case "equipment_status": + if ("desc".equals(orderDirection)) { + queryWrapper.orderByDesc(EquipmentEntity::getEquipmentStatus); + } else { + queryWrapper.orderByAsc(EquipmentEntity::getEquipmentStatus); + } + break; + case "brand": + if ("desc".equals(orderDirection)) { + queryWrapper.orderByDesc(EquipmentEntity::getBrand); + } else { + queryWrapper.orderByAsc(EquipmentEntity::getBrand); + } + break; + case "responsible_person": + if ("desc".equals(orderDirection)) { + queryWrapper.orderByDesc(EquipmentEntity::getResponsiblePerson); + } else { + queryWrapper.orderByAsc(EquipmentEntity::getResponsiblePerson); + } + break; + case "purchase_time": + if ("desc".equals(orderDirection)) { + queryWrapper.orderByDesc(EquipmentEntity::getPurchaseTime); + } else { + queryWrapper.orderByAsc(EquipmentEntity::getPurchaseTime); + } + break; + case "purchase_price": + if ("desc".equals(orderDirection)) { + queryWrapper.orderByDesc(EquipmentEntity::getTotalPrice); + } else { + queryWrapper.orderByAsc(EquipmentEntity::getTotalPrice); + } + break; + case "update_time": + if ("desc".equals(orderDirection)) { + queryWrapper.orderByDesc(EquipmentEntity::getUpdateTime); + } else { + queryWrapper.orderByAsc(EquipmentEntity::getUpdateTime); + } + break; + default: + // 默认按创建时间倒序 + queryWrapper.orderByDesc(EquipmentEntity::getCreateTime); + break; + } + log.info("添加排序条件: {} {}", orderBy, orderDirection); + } else { + // 默认按创建时间倒序 + queryWrapper.orderByDesc(EquipmentEntity::getCreateTime); + log.info("使用默认排序: 按创建时间倒序"); + } + + log.info("执行分页查询,SQL条件: {}", queryWrapper.getTargetSql()); + + // 执行分页查询 + IPage equipmentPage = this.page(page, queryWrapper); + + log.info("查询完成,总记录数: {}, 当前页记录数: {}", equipmentPage.getTotal(), equipmentPage.getRecords().size()); + + // 转换为响应对象 + List equipmentRespList = equipmentPage.getRecords().stream() + .map(this::convertToResp) + .collect(Collectors.toList()); + + // 创建响应分页对象 + Page respPage = new Page<>(equipmentPage.getCurrent(), equipmentPage.getSize(), equipmentPage.getTotal()); + respPage.setRecords(equipmentRespList); + + return respPage; } @Override public EquipmentResp detail(String equipmentId) { - EquipmentResp resp = this.baseMapper.getEquipmentRespByEquipmentId(equipmentId); - resp.setEquipmentTypeLabel(EquipmentTypeEnum.getDescByCode(resp.getEquipmentType())); - resp.setEquipmentStatusLabel(EquipmentStatusEnum.getDescByCode(resp.getEquipmentStatus())); - return resp; + EquipmentEntity equipment = this.getById(equipmentId); + if (equipment == null) { + throw new ServiceException(Message.EQUIPMENT_ID_NOT_EXIST); + } + return convertToResp(equipment); } @Transactional(rollbackFor = Exception.class) @Override public void save(EquipmentReq req) { - EquipmentEntity entity = Converts.INSTANCE.toEquipmentUseRecordEntity(req); - if(null != getByEquipmentSn(entity.getEquipmentSn())){ + EquipmentEntity entity = convertToEntity(req); + if (getByEquipmentSn(entity.getEquipmentSn()) != null) { throw new ServiceException(Message.EQUIPMENT_SN_EXIST); } this.save(entity); } - private EquipmentEntity getByEquipmentSn(String equipmentSn){ - return this.getOne(Wrappers.lambdaQuery(EquipmentEntity.class).eq(EquipmentEntity::getEquipmentSn, equipmentSn)); + private EquipmentEntity getByEquipmentSn(String equipmentSn) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(EquipmentEntity::getEquipmentSn, equipmentSn); + return this.getOne(queryWrapper); } @Transactional(rollbackFor = Exception.class) @Override public void update(String equipmentId, EquipmentReq req) { EquipmentEntity equipment = this.getById(equipmentId); - if(null == equipment){ + if (equipment == null) { throw new ServiceException(Message.EQUIPMENT_ID_NOT_EXIST); } - if(!equipment.getEquipmentSn().equals(req.getEquipmentSn()) && null != getByEquipmentSn(req.getEquipmentSn())){ + if (!equipment.getEquipmentSn().equals(req.getEquipmentSn()) && getByEquipmentSn(req.getEquipmentSn()) != null) { throw new ServiceException(Message.EQUIPMENT_SN_EXIST); } - EquipmentEntity entity = Converts.INSTANCE.toEquipmentUseRecordEntity(req); + EquipmentEntity entity = convertToEntity(req); entity.setEquipmentId(equipmentId); this.updateById(entity); } @@ -100,9 +421,393 @@ public class EquipmentServiceImpl extends ServiceImpl equipmentIds) { + if (equipmentIds == null || equipmentIds.isEmpty()) { + throw new ServiceException("设备ID列表不能为空"); + } + this.removeByIds(equipmentIds); + } + + @Override + public byte[] exportProcurement(EquipmentListReq req) { + // 这里可以实现导出功能,返回Excel文件的字节数组 + // 暂时返回空数组,实际项目中需要实现具体的导出逻辑 + return new byte[0]; + } + + /** + * 将EquipmentReq转换为EquipmentEntity + */ + private EquipmentEntity convertToEntity(EquipmentReq req) { + EquipmentEntity entity = new EquipmentEntity(); + entity.setAssetCode(req.getAssetCode()); + entity.setEquipmentName(req.getEquipmentName()); + entity.setEquipmentModel(req.getEquipmentModel()); + entity.setEquipmentType(req.getEquipmentType()); + entity.setEquipmentStatus(req.getEquipmentStatus()); + entity.setUseStatus(req.getUseStatus()); + entity.setEquipmentSn(req.getEquipmentSn()); + entity.setBrand(req.getBrand()); + entity.setSpecification(req.getSpecification()); + entity.setLocationStatus(req.getLocationStatus()); + entity.setPhysicalLocation(req.getPhysicalLocation()); + entity.setResponsiblePerson(req.getResponsiblePerson()); + entity.setHealthStatus(req.getHealthStatus()); + entity.setPurchaseTime(req.getPurchaseTime()); + entity.setInStockTime(req.getInStockTime()); + entity.setActivationTime(req.getActivationTime()); + entity.setExpectedScrapTime(req.getExpectedScrapTime()); + entity.setActualScrapTime(req.getActualScrapTime()); + entity.setStatusChangeTime(req.getStatusChangeTime()); + entity.setPurchaseOrder(req.getPurchaseOrder()); + entity.setSupplierName(req.getSupplierName()); + entity.setCurrentNetValue(req.getCurrentNetValue()); + entity.setDepreciationMethod(req.getDepreciationMethod()); + entity.setDepreciationYears(req.getDepreciationYears()); + entity.setSalvageValue(req.getSalvageValue()); + entity.setWarrantyExpireDate(req.getWarrantyExpireDate()); + entity.setLastMaintenanceDate(req.getLastMaintenanceDate()); + entity.setNextMaintenanceDate(req.getNextMaintenanceDate()); + entity.setMaintenancePerson(req.getMaintenancePerson()); + entity.setInventoryBarcode(req.getInventoryBarcode()); + entity.setAssetRemark(req.getAssetRemark()); + + // 新增采购相关字段 + entity.setAccountNumber(req.getAccountNumber()); + entity.setQuantity(req.getQuantity()); + entity.setUnitPrice(req.getUnitPrice()); + entity.setTotalPrice(req.getTotalPrice()); + entity.setInventoryBasis(req.getInventoryBasis()); + entity.setDynamicRecord(req.getDynamicRecord()); + + // 新增字段转换 + entity.setUsingDepartment(req.getUsingDepartment()); + entity.setBorrowingTime(req.getBorrowingTime()); + entity.setReturnTime(req.getReturnTime()); + entity.setOutStockTime(req.getOutStockTime()); + entity.setTotalUsageTime(req.getTotalUsageTime()); + entity.setDepreciationRate(req.getDepreciationRate()); + entity.setDepreciationMethodDesc(req.getDepreciationMethodDesc()); + entity.setInvoice(req.getInvoice()); + entity.setInvoiceStatus(req.getInvoiceStatus()); + entity.setAttachments(req.getAttachments()); + entity.setPhotos(req.getPhotos()); + entity.setBarcode(req.getBarcode()); + entity.setImporter(req.getImporter()); + entity.setInventoryTimeStatus1(req.getInventoryTimeStatus1()); + entity.setInventoryTimeStatus2(req.getInventoryTimeStatus2()); + entity.setInventoryTimeStatus3(req.getInventoryTimeStatus3()); + entity.setInventoryCheckTimeStatus1(req.getInventoryCheckTimeStatus1()); + entity.setInventoryCheckTimeStatus2(req.getInventoryCheckTimeStatus2()); + entity.setInventoryCheckTimeStatus3(req.getInventoryCheckTimeStatus3()); + + return entity; + } + + /** + * 转换为响应对象 + */ + private EquipmentResp convertToResp(EquipmentEntity entity) { + EquipmentResp resp = new EquipmentResp(); + resp.setEquipmentId(entity.getEquipmentId()); + resp.setAssetCode(entity.getAssetCode()); + resp.setEquipmentName(entity.getEquipmentName()); + resp.setEquipmentType(entity.getEquipmentType()); + resp.setEquipmentTypeLabel(EquipmentTypeEnum.getDescByCode(entity.getEquipmentType())); + resp.setEquipmentModel(entity.getEquipmentModel()); + resp.setEquipmentSn(entity.getEquipmentSn()); + resp.setBrand(entity.getBrand()); + resp.setSpecification(entity.getSpecification()); + resp.setEquipmentStatus(entity.getEquipmentStatus()); + resp.setEquipmentStatusLabel(EquipmentStatusEnum.getDescByCode(entity.getEquipmentStatus())); + resp.setUseStatus(entity.getUseStatus()); + resp.setLocationStatus(entity.getLocationStatus()); + resp.setLocationStatusLabel(LocationStatusEnum.getDescByCode(entity.getLocationStatus())); + resp.setPhysicalLocation(entity.getPhysicalLocation()); + resp.setResponsiblePerson(entity.getResponsiblePerson()); + resp.setHealthStatus(entity.getHealthStatus()); + resp.setHealthStatusLabel(HealthStatusEnum.getDescByCode(entity.getHealthStatus())); + resp.setPurchaseTime(entity.getPurchaseTime()); + resp.setInStockTime(entity.getInStockTime()); + resp.setActivationTime(entity.getActivationTime()); + resp.setExpectedScrapTime(entity.getExpectedScrapTime()); + resp.setActualScrapTime(entity.getActualScrapTime()); + resp.setStatusChangeTime(entity.getStatusChangeTime()); + resp.setPurchaseOrder(entity.getPurchaseOrder()); + resp.setSupplierName(entity.getSupplierName()); + resp.setCurrentNetValue(entity.getCurrentNetValue()); + resp.setDepreciationMethod(entity.getDepreciationMethod()); + resp.setDepreciationYears(entity.getDepreciationYears()); + resp.setSalvageValue(entity.getSalvageValue()); + resp.setWarrantyExpireDate(entity.getWarrantyExpireDate()); + resp.setLastMaintenanceDate(entity.getLastMaintenanceDate()); + resp.setNextMaintenanceDate(entity.getNextMaintenanceDate()); + resp.setMaintenancePerson(entity.getMaintenancePerson()); + resp.setInventoryBarcode(entity.getInventoryBarcode()); + resp.setAssetRemark(entity.getAssetRemark()); + + // 新增采购相关字段 + resp.setAccountNumber(entity.getAccountNumber()); + resp.setQuantity(entity.getQuantity()); + resp.setUnitPrice(entity.getUnitPrice()); + resp.setTotalPrice(entity.getTotalPrice()); + resp.setInventoryBasis(entity.getInventoryBasis()); + resp.setDynamicRecord(entity.getDynamicRecord()); + + // 新增字段转换 + resp.setUsingDepartment(entity.getUsingDepartment()); + resp.setBorrowingTime(entity.getBorrowingTime()); + resp.setReturnTime(entity.getReturnTime()); + resp.setOutStockTime(entity.getOutStockTime()); + resp.setTotalUsageTime(entity.getTotalUsageTime()); + resp.setDepreciationRate(entity.getDepreciationRate()); + resp.setDepreciationMethodDesc(entity.getDepreciationMethodDesc()); + resp.setInvoice(entity.getInvoice()); + resp.setInvoiceStatus(entity.getInvoiceStatus()); + resp.setAttachments(entity.getAttachments()); + resp.setPhotos(entity.getPhotos()); + resp.setBarcode(entity.getBarcode()); + resp.setImporter(entity.getImporter()); + resp.setInventoryTimeStatus1(entity.getInventoryTimeStatus1()); + resp.setInventoryTimeStatus2(entity.getInventoryTimeStatus2()); + resp.setInventoryTimeStatus3(entity.getInventoryTimeStatus3()); + resp.setInventoryCheckTimeStatus1(entity.getInventoryCheckTimeStatus1()); + resp.setInventoryCheckTimeStatus2(entity.getInventoryCheckTimeStatus2()); + resp.setInventoryCheckTimeStatus3(entity.getInventoryCheckTimeStatus3()); + + resp.setCreateTime(entity.getCreateTime()); + resp.setUpdateTime(entity.getUpdateTime()); + + return resp; + } + + // 采购管理相关方法实现 + @Override + public IPage procurementPage(EquipmentListReq req) { + log.info("开始执行设备采购记录分页查询,请求参数: {}", req); + + // 创建分页对象,处理null值 + Integer pageNum = req.getPage() != null ? req.getPage() : (req.getPageNum() != null ? req.getPageNum() : 1); + Integer pageSize = req.getPageSize() != null ? req.getPageSize() : 10; + Page page = new Page<>(pageNum, pageSize); + + // 构建查询条件,参考设备分页查询的逻辑 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + // 不需要手动添加 del_flag 条件,@TableLogic 注解会自动处理 + + // 添加搜索条件并记录日志 + int conditionCount = 0; + + if (StringUtils.hasText(req.getEquipmentName())) { + queryWrapper.like(EquipmentEntity::getEquipmentName, req.getEquipmentName()); + log.info("添加设备名称查询条件: {}", req.getEquipmentName()); + conditionCount++; + } + if (StringUtils.hasText(req.getEquipmentType())) { + queryWrapper.eq(EquipmentEntity::getEquipmentType, req.getEquipmentType()); + log.info("添加设备类型查询条件: {}", req.getEquipmentType()); + conditionCount++; + } + if (StringUtils.hasText(req.getEquipmentStatus())) { + queryWrapper.eq(EquipmentEntity::getEquipmentStatus, req.getEquipmentStatus()); + log.info("添加设备状态查询条件: {}", req.getEquipmentStatus()); + conditionCount++; + } + if (StringUtils.hasText(req.getEquipmentSn())) { + queryWrapper.like(EquipmentEntity::getEquipmentSn, req.getEquipmentSn()); + log.info("添加设备序列号查询条件: {}", req.getEquipmentSn()); + conditionCount++; + } + if (StringUtils.hasText(req.getAssetCode())) { + queryWrapper.like(EquipmentEntity::getAssetCode, req.getAssetCode()); + log.info("添加资产编号查询条件: {}", req.getAssetCode()); + conditionCount++; + } + if (StringUtils.hasText(req.getBrand())) { + queryWrapper.like(EquipmentEntity::getBrand, req.getBrand()); + log.info("添加品牌查询条件: {}", req.getBrand()); + conditionCount++; + } + if (StringUtils.hasText(req.getLocationStatus())) { + queryWrapper.eq(EquipmentEntity::getLocationStatus, req.getLocationStatus()); + log.info("添加位置状态查询条件: {}", req.getLocationStatus()); + conditionCount++; + } + if (StringUtils.hasText(req.getHealthStatus())) { + queryWrapper.eq(EquipmentEntity::getHealthStatus, req.getHealthStatus()); + log.info("添加健康状态查询条件: {}", req.getHealthStatus()); + conditionCount++; + } + if (StringUtils.hasText(req.getResponsiblePerson())) { + queryWrapper.like(EquipmentEntity::getResponsiblePerson, req.getResponsiblePerson()); + log.info("添加负责人查询条件: {}", req.getResponsiblePerson()); + conditionCount++; + } + if (StringUtils.hasText(req.getPhysicalLocation())) { + queryWrapper.like(EquipmentEntity::getPhysicalLocation, req.getPhysicalLocation()); + log.info("添加物理位置查询条件: {}", req.getPhysicalLocation()); + conditionCount++; + } + + // 添加采购相关的搜索条件 + if (StringUtils.hasText(req.getPurchaseOrder())) { + queryWrapper.like(EquipmentEntity::getPurchaseOrder, req.getPurchaseOrder()); + log.info("添加采购订单查询条件: {}", req.getPurchaseOrder()); + conditionCount++; + } + if (StringUtils.hasText(req.getSupplierName())) { + queryWrapper.like(EquipmentEntity::getSupplierName, req.getSupplierName()); + log.info("添加供应商查询条件: {}", req.getSupplierName()); + conditionCount++; + } + if (StringUtils.hasText(req.getAccountNumber())) { + queryWrapper.like(EquipmentEntity::getAccountNumber, req.getAccountNumber()); + log.info("添加次户号查询条件: {}", req.getAccountNumber()); + conditionCount++; + } + if (req.getQuantity() != null) { + queryWrapper.eq(EquipmentEntity::getQuantity, req.getQuantity()); + log.info("添加数量查询条件: {}", req.getQuantity()); + conditionCount++; + } + if (req.getUnitPrice() != null) { + queryWrapper.eq(EquipmentEntity::getUnitPrice, req.getUnitPrice()); + log.info("添加单价查询条件: {}", req.getUnitPrice()); + conditionCount++; + } + if (req.getTotalPrice() != null) { + queryWrapper.eq(EquipmentEntity::getTotalPrice, req.getTotalPrice()); + log.info("添加总价查询条件: {}", req.getTotalPrice()); + conditionCount++; + } + if (StringUtils.hasText(req.getInventoryBasis())) { + queryWrapper.like(EquipmentEntity::getInventoryBasis, req.getInventoryBasis()); + log.info("添加盘点依据查询条件: {}", req.getInventoryBasis()); + conditionCount++; + } + if (StringUtils.hasText(req.getDynamicRecord())) { + queryWrapper.like(EquipmentEntity::getDynamicRecord, req.getDynamicRecord()); + log.info("添加动态记录查询条件: {}", req.getDynamicRecord()); + conditionCount++; + } + + log.info("总共添加了 {} 个查询条件", conditionCount); + + // 按采购时间倒序排列 + queryWrapper.orderByDesc(EquipmentEntity::getPurchaseTime); + + // 执行查询 + IPage result = this.page(page, queryWrapper); + + // 转换为响应对象 + List records = result.getRecords().stream() + .map(this::convertToResp) + .collect(Collectors.toList()); + + // 创建新的分页结果 + Page respPage = new Page<>(result.getCurrent(), result.getSize(), result.getTotal()); + respPage.setRecords(records); + + log.info("设备采购记录分页查询完成,总记录数: {}", result.getTotal()); + return respPage; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void saveProcurement(EquipmentReq req) { + log.info("开始新增设备采购记录,请求参数: {}", req); + + // 验证采购订单号唯一性 + if (StringUtils.hasText(req.getPurchaseOrder())) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(EquipmentEntity::getPurchaseOrder, req.getPurchaseOrder()); + long count = this.count(queryWrapper); + if (count > 0) { + throw new ServiceException("采购订单号已存在"); + } + } + + // 设置采购相关的时间字段 + if (req.getPurchaseTime() == null) { + req.setPurchaseTime(LocalDateTime.now()); + } + + // 转换为实体并保存 + EquipmentEntity entity = convertToEntity(req); + this.save(entity); + + log.info("设备采购记录新增成功,设备ID: {}", entity.getEquipmentId()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void updateProcurement(String equipmentId, EquipmentReq req) { + log.info("开始修改设备采购记录,设备ID: {}, 请求参数: {}", equipmentId, req); + + // 检查设备是否存在 + EquipmentEntity existingEntity = this.getById(equipmentId); + if (existingEntity == null) { + throw new ServiceException("设备不存在"); + } + + // 验证采购订单号唯一性(排除当前设备) + if (StringUtils.hasText(req.getPurchaseOrder())) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(EquipmentEntity::getPurchaseOrder, req.getPurchaseOrder()) + .ne(EquipmentEntity::getEquipmentId, equipmentId); + long count = this.count(queryWrapper); + if (count > 0) { + throw new ServiceException("采购订单号已存在"); + } + } + + // 转换为实体并更新 + EquipmentEntity entity = convertToEntity(req); + entity.setEquipmentId(equipmentId); + this.updateById(entity); + + log.info("设备采购记录修改成功,设备ID: {}", equipmentId); + } + + @Override + public Object getProcurementStats() { + log.info("开始获取采购统计信息"); + + // 统计采购总金额 + LambdaQueryWrapper priceWrapper = new LambdaQueryWrapper<>(); + priceWrapper.isNotNull(EquipmentEntity::getTotalPrice); + List priceEntities = this.list(priceWrapper); + double totalAmount = priceEntities.stream() + .mapToDouble(entity -> entity.getTotalPrice() != null ? entity.getTotalPrice().doubleValue() : 0) + .sum(); + + // 统计供应商数量 + LambdaQueryWrapper supplierWrapper = new LambdaQueryWrapper<>(); + supplierWrapper.isNotNull(EquipmentEntity::getSupplierName) + .groupBy(EquipmentEntity::getSupplierName); + long supplierCount = this.count(supplierWrapper); + + // 统计采购设备数量 + LambdaQueryWrapper equipmentWrapper = new LambdaQueryWrapper<>(); + equipmentWrapper.isNotNull(EquipmentEntity::getPurchaseOrder); + long equipmentCount = this.count(equipmentWrapper); + + // 构建统计结果 + java.util.Map stats = new java.util.HashMap<>(); + stats.put("totalAmount", totalAmount); + stats.put("supplierCount", supplierCount); + stats.put("equipmentCount", equipmentCount); + stats.put("averageAmount", equipmentCount > 0 ? totalAmount / equipmentCount : 0); + + log.info("采购统计信息获取完成,总金额: {}, 供应商数: {}, 设备数: {}", totalAmount, supplierCount, equipmentCount); + return stats; + } } diff --git a/core/src/main/java/com/dite/znpt/service/impl/InMemoryVerificationCodeStore.java b/core/src/main/java/com/dite/znpt/service/impl/InMemoryVerificationCodeStore.java new file mode 100644 index 0000000..fb11b9c --- /dev/null +++ b/core/src/main/java/com/dite/znpt/service/impl/InMemoryVerificationCodeStore.java @@ -0,0 +1,74 @@ +package com.dite.znpt.service.impl; + +import lombok.Data; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class InMemoryVerificationCodeStore { + + // 存储邮箱验证码的map + private final Map emailCodeMap = new ConcurrentHashMap<>(); + + public void save(String email, String code){ + // 设置五分钟过期 + emailCodeMap.put(email,new codeInfo(code, LocalDateTime.now().plusMinutes(5))); + } + + /** + * 验证验证码 + * @param email + * @param code + * @return + */ + public boolean validate(String email, String code){ + codeInfo codeInfo = emailCodeMap.get(email); + if(codeInfo == null){ + return false; + } + + boolean isValid = false; + if(code.equals(codeInfo.getCode()) && LocalDateTime.now().isBefore(codeInfo.getExpireTime())){ + isValid = true; + } + if(isValid){ + emailCodeMap.remove(email); + return true; + } + return false; + } + + /** + * 判断邮箱是否可发送验证码 + * @param email + * @return + */ + public boolean canSend(String email){ + codeInfo codeInfo = emailCodeMap.get(email); + if(codeInfo != null && LocalDateTime.now().isBefore(codeInfo.getExpireTime())){ + return false; + } + return true; + } + + @Data + public static class codeInfo { + // 验证码 + private String code; + + // 过期时间 + private LocalDateTime expireTime; + + // 上一次发送时间 + private LocalDateTime lastSendTime; + + public codeInfo(String code, LocalDateTime expireTime){ + this.code = code; + this.expireTime = expireTime; + this.lastSendTime = LocalDateTime.now(); + } + } +} diff --git a/core/src/main/java/com/dite/znpt/service/impl/ProjectBudgetInfoServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/ProjectBudgetInfoServiceImpl.java index 5380782..d6e847f 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/ProjectBudgetInfoServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/ProjectBudgetInfoServiceImpl.java @@ -1,25 +1,24 @@ package com.dite.znpt.service.impl; import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.util.IdUtil; +import cn.hutool.core.collection.CollectionUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.dite.znpt.domain.entity.ProjectBudgetInfoEntity; +import com.dite.znpt.domain.entity.ProjectEntity; +import com.dite.znpt.domain.vo.ProjectBudgetInfoDetailResp; +import com.dite.znpt.domain.vo.ProjectBudgetInfoImportReq; import com.dite.znpt.domain.vo.ProjectBudgetInfoListReq; import com.dite.znpt.domain.vo.ProjectBudgetInfoListResp; -import com.dite.znpt.domain.vo.ProjectBudgetInfoReq; -import com.dite.znpt.domain.vo.ProjectBudgetInfoResp; -import com.dite.znpt.enums.AttachBusinessTypeEnum; -import com.dite.znpt.enums.BudgeTypeEnum; import com.dite.znpt.mapper.ProjectBudgetInfoMapper; -import com.dite.znpt.service.AttachInfoService; import com.dite.znpt.service.ProjectBudgetInfoService; +import com.dite.znpt.service.ProjectService; import com.dite.znpt.util.PageUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; -import java.util.ArrayList; +import javax.annotation.Resource; +import java.util.Collections; import java.util.List; /** @@ -30,63 +29,98 @@ import java.util.List; @Service @RequiredArgsConstructor public class ProjectBudgetInfoServiceImpl extends ServiceImpl implements ProjectBudgetInfoService { + @Resource + ProjectService projectService; - private final AttachInfoService attachInfoService; + @Resource + AttachInfoServiceImpl attachInfoService; - /** - * 功能描述:查询项目预算信息列表 - * - * @param projectBudgetInfoReq 项目预算信息信息 - * @return {@link List }<{@link ProjectBudgetInfoResp }> - * @author huise23 - * @date 2025/07/17 21:58 - **/ @Override - public List selectList(ProjectBudgetInfoListReq projectBudgetInfoReq) { - PageUtil.startPage(); - List projectBudgetInfoList= this.baseMapper.queryBySelective(projectBudgetInfoReq); - projectBudgetInfoList.forEach(resp -> { - - }); - return projectBudgetInfoList; - } - - /** - * 功能描述:根据项目id获取项目预算信息列表 - * - * @param projectId 项目id - * @return {@link List }<{@link ProjectBudgetInfoListResp }> - * @author huise23 - * @date 2025/07/17 21:58 - **/ - @Override - public List detailByProjectId(String projectId) { - List projectBudgetInfoList= this.baseMapper.detailByProjectId(projectId); - projectBudgetInfoList.forEach(resp -> { - resp.setBudgetTypeDesc(BudgeTypeEnum.getDescByCode(resp.getBudgetType())); - }); - return projectBudgetInfoList; - } - - /** - * 功能描述:新增项目预算信息 - * - * @param projectBudgetInfoReq 项目预算信息 - * @author huise23 - * @date 2025/07/17 21:58 - **/ - @Override - @Transactional(rollbackFor = Exception.class) - public void saveData(List projectBudgetInfoReq) { - List list = new ArrayList<>(); - for (ProjectBudgetInfoReq req : projectBudgetInfoReq) { - ProjectBudgetInfoEntity info = BeanUtil.copyProperties(req, ProjectBudgetInfoEntity.class); - info.setBudgetId(IdUtil.simpleUUID()); - attachInfoService.updateBusinessIdByAttachIds(info.getBudgetId(), ListUtil.of(req.getAttachId()), AttachBusinessTypeEnum.PROJECT_BUDGE); - list.add(info); + public List list(ProjectBudgetInfoListReq req) { + List list = this.baseMapper.queryBySelective(req); + if (CollectionUtil.isEmpty(list)) { + return Collections.emptyList(); // 返回不可修改的空集合 } - lambdaUpdate().eq(ProjectBudgetInfoEntity::getProjectId, list.get(0).getProjectId()).remove(); - baseMapper.insert(list); + list.forEach(item -> { + item.setProjectName(projectService.getById(item.getProjectId()).getProjectName()); + }); + return list; } + @Override + public List page(ProjectBudgetInfoListReq req) { + PageUtil.startPage(); + return this.list(req); + } + + @Override + public void saveData(ProjectBudgetInfoImportReq req) { + ProjectBudgetInfoEntity entity = BeanUtil.copyProperties(req, ProjectBudgetInfoEntity.class); + ProjectEntity projectEntity = projectService.getById(req.getProjectId()); + switch (req.getBudgetName()) { + case "人工成本": + projectEntity.setUseLaborCost(projectEntity.getUseLaborCost() + req.getBudgetAmount()); + break; + case "设备摊销": + projectEntity.setUseEquipmentAmortization(projectEntity.getUseEquipmentAmortization() + req.getBudgetAmount()); + break; + case "奖金预提": + projectEntity.setUseBonusProvision(projectEntity.getUseBonusProvision() + req.getBudgetAmount()); + break; + case "交通食宿": + projectEntity.setUseTransAccomMeals(projectEntity.getUseTransAccomMeals() + req.getBudgetAmount()); + break; + case "其他杂费": + projectEntity.setUseOthersCost(projectEntity.getUseOthersCost() + req.getBudgetAmount()); + break; + default: + break; + } + projectService.updateById(projectEntity); + this.baseMapper.insert(entity); + } + + @Override + public void saveData(ProjectBudgetInfoImportReq req, MultipartFile[] files) { + ProjectBudgetInfoEntity entity = BeanUtil.copyProperties(req, ProjectBudgetInfoEntity.class); + // 把附件存起来,使用已有的附件上传逻辑就行,但我看不懂,pass + this.baseMapper.insert(entity); + } + + @Override + public ProjectBudgetInfoDetailResp detailByProjectId(String projectId) { + ProjectEntity projectEntity = projectService.getById(projectId); + ProjectBudgetInfoDetailResp resp = new ProjectBudgetInfoDetailResp(); + BeanUtil.copyProperties(projectEntity, resp); + resp.setRestBudget(resp.getProjectBudget() - resp.getUseLaborCost() - resp.getUseEquipmentAmortization() - resp.getUseBonusProvision() - resp.getUseTransAccomMeals() - resp.getUseOthersCost()); + return resp; + } + + @Override + public void delete(String budgetId) { + ProjectBudgetInfoEntity entity = this.baseMapper.selectById(budgetId); + ProjectEntity projectEntity = projectService.getById(entity.getProjectId()); + switch (entity.getBudgetName()) { + case "人工成本": + projectEntity.setUseLaborCost(projectEntity.getUseLaborCost() - entity.getBudgetAmount()); + break; + case "设备摊销": + projectEntity.setUseEquipmentAmortization(projectEntity.getUseEquipmentAmortization() - entity.getBudgetAmount()); + break; + case "奖金预提": + projectEntity.setUseBonusProvision(projectEntity.getUseBonusProvision() - entity.getBudgetAmount()); + break; + case "交通食宿": + projectEntity.setUseTransAccomMeals(projectEntity.getUseTransAccomMeals() - entity.getBudgetAmount()); + break; + case "其他杂费": + projectEntity.setUseOthersCost(projectEntity.getUseOthersCost() - entity.getBudgetAmount()); + break; + default: + break; + } + projectService.updateById(projectEntity); + this.baseMapper.deleteById(budgetId); + // 删除附件 + } } diff --git a/core/src/main/java/com/dite/znpt/service/impl/ProjectMemberServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/ProjectMemberServiceImpl.java index 8c51d1d..9ee100e 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/ProjectMemberServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/ProjectMemberServiceImpl.java @@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -293,7 +294,51 @@ public class ProjectMemberServiceImpl extends ServiceImpl projectMembers = selectByProjectId(projectId); + resp.setProjectMembers(projectMembers); + + // 计算团队规模 + resp.setTeamSize(projectMembers.size()); + + // 获取项目经理信息 + String managerName = projectMembers.stream() + .filter(member -> "PROJECT_MANAGER".equals(member.getRoleType())) + .map(ProjectMemberResp::getUserName) + .findFirst() + .orElse(""); + resp.setManager(managerName); + + // 计算项目预算(从预算信息中汇总) + List budgets = projectBudgetInfoService.lambdaQuery() + .eq(ProjectBudgetInfoEntity::getProjectId, projectId) + .list(); + BigDecimal totalBudget = budgets.stream() + .map(budget -> BigDecimal.valueOf(budget.getBudgetAmount())) + .reduce(BigDecimal.ZERO, BigDecimal::add); + resp.setBudget(totalBudget); + + // 计算项目进度 + List tasks = projectTaskService.lambdaQuery() + .eq(ProjectTaskEntity::getProjectId, projectId) + .list(); + if (!tasks.isEmpty()) { + long completedTasks = tasks.stream() + .filter(task -> task.getStatus() != null && task.getStatus() == 2) + .count(); + resp.setProgress((int) (completedTasks * 100 / tasks.size())); + } else { + resp.setProgress(0); + } + + // 计算准备进度(基于任务状态) + if (!tasks.isEmpty()) { + long preparedTasks = tasks.stream() + .filter(task -> task.getStatus() != null && task.getStatus() >= 1) + .count(); + resp.setPreparationProgress((int) (preparedTasks * 100 / tasks.size())); + } else { + resp.setPreparationProgress(0); + } // 获取项目机组信息 List turbines = turbineService.lambdaQuery() @@ -307,9 +352,6 @@ public class ProjectMemberServiceImpl extends ServiceImpl tasks = projectTaskService.lambdaQuery() - .eq(ProjectTaskEntity::getProjectId, projectId) - .list(); resp.setTasks(tasks.stream().map(task -> { ProjectDetailResp.TaskInfo info = new ProjectDetailResp.TaskInfo(); BeanUtil.copyProperties(task, info); @@ -318,9 +360,6 @@ public class ProjectMemberServiceImpl extends ServiceImpl budgets = projectBudgetInfoService.lambdaQuery() - .eq(ProjectBudgetInfoEntity::getProjectId, projectId) - .list(); resp.setBudgets(budgets.stream().map(budget -> { ProjectDetailResp.BudgetInfo info = new ProjectDetailResp.BudgetInfo(); BeanUtil.copyProperties(budget, info); @@ -381,6 +420,22 @@ public class ProjectMemberServiceImpl extends ServiceImpl teamMembers = members.stream() + .map(member -> { + ProjectKanbanDataResp.ProjectKanbanItem.TeamMemberResp teamMember = new ProjectKanbanDataResp.ProjectKanbanItem.TeamMemberResp(); + BeanUtil.copyProperties(member, teamMember); + return teamMember; + }) + .collect(Collectors.toList()); + item.setTeamMembers(teamMembers); + // 统计机组数量 Long turbineCount = turbineService.lambdaQuery() .eq(TurbineEntity::getProjectId, project.getProjectId()) @@ -403,10 +458,32 @@ public class ProjectMemberServiceImpl extends ServiceImpl 0) { item.setProgressPercentage((int) (completedTaskCount * 100 / taskCount)); + item.setProgress((int) (completedTaskCount * 100 / taskCount)); } else { item.setProgressPercentage(0); + item.setProgress(0); } + // 计算准备进度(基于任务状态) + if (taskCount > 0) { + Long preparedTasks = projectTaskService.lambdaQuery() + .eq(ProjectTaskEntity::getProjectId, project.getProjectId()) + .ge(ProjectTaskEntity::getStatus, 1) + .count(); + item.setPreparationProgress((int) (preparedTasks * 100 / taskCount)); + } else { + item.setPreparationProgress(0); + } + + // 计算项目预算(从预算信息中汇总) + List budgets = projectBudgetInfoService.lambdaQuery() + .eq(ProjectBudgetInfoEntity::getProjectId, project.getProjectId()) + .list(); + BigDecimal totalBudget = budgets.stream() + .map(budget -> BigDecimal.valueOf(budget.getBudgetAmount())) + .reduce(BigDecimal.ZERO, BigDecimal::add); + item.setBudget(totalBudget); + return item; }).collect(Collectors.toList()); } diff --git a/core/src/main/java/com/dite/znpt/service/impl/UserPostServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/UserPostServiceImpl.java index e505d14..71746fc 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/UserPostServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/UserPostServiceImpl.java @@ -10,6 +10,7 @@ import com.dite.znpt.domain.entity.PostEntity; import com.dite.znpt.domain.entity.UserEntity; import com.dite.znpt.domain.entity.UserPostEntity; import com.dite.znpt.domain.vo.PostResp; +import com.dite.znpt.domain.vo.UserResp; import com.dite.znpt.exception.ServiceException; import com.dite.znpt.mapper.UserPostMapper; import com.dite.znpt.service.PostService; @@ -88,4 +89,14 @@ public class UserPostServiceImpl extends ServiceImpl getUsersByPostId(String postId) { + List userIds = this.list(Wrappers.lambdaQuery(UserPostEntity.class).eq(UserPostEntity::getPostId, postId)).stream().map(UserPostEntity::getUserId).toList(); + if (CollUtil.isEmpty(userIds)) { + return new ArrayList<>(); + } + List users = userService.listByIds(userIds).stream().filter(user -> Constants.DEL_FLAG_0.equals(user.getDelFlag()) && Constants.STATUS_0.equals(user.getStatus())).toList(); + return Converts.INSTANCE.toUserResp(users); + } } diff --git a/core/src/main/java/com/dite/znpt/service/impl/UserRoleServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/UserRoleServiceImpl.java index 1299612..f4dea0e 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/UserRoleServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/UserRoleServiceImpl.java @@ -10,6 +10,7 @@ import com.dite.znpt.domain.entity.RoleEntity; import com.dite.znpt.domain.entity.UserEntity; import com.dite.znpt.domain.entity.UserRoleEntity; import com.dite.znpt.domain.vo.RoleResp; +import com.dite.znpt.domain.vo.UserResp; import com.dite.znpt.domain.vo.UserRoleReq; import com.dite.znpt.exception.ServiceException; import com.dite.znpt.mapper.UserRoleMapper; @@ -88,4 +89,14 @@ public class UserRoleServiceImpl extends ServiceImpl getUsersByRoleId(String roleId) { + List userIds = this.list(Wrappers.lambdaQuery(UserRoleEntity.class).eq(UserRoleEntity::getRoleId, roleId)).stream().map(UserRoleEntity::getUserId).toList(); + if (CollUtil.isEmpty(userIds)) { + return new ArrayList<>(); + } + List users = userService.listByIds(userIds).stream().filter(user -> Constants.DEL_FLAG_0.equals(user.getDelFlag()) && Constants.STATUS_0.equals(user.getStatus())).toList(); + return Converts.INSTANCE.toUserResp(users); + } } diff --git a/core/src/main/java/com/dite/znpt/service/impl/UserServiceImpl.java b/core/src/main/java/com/dite/znpt/service/impl/UserServiceImpl.java index cd4c41d..d3516d1 100644 --- a/core/src/main/java/com/dite/znpt/service/impl/UserServiceImpl.java +++ b/core/src/main/java/com/dite/znpt/service/impl/UserServiceImpl.java @@ -107,7 +107,7 @@ public class UserServiceImpl extends ServiceImpl impleme UserEntity entity = this.getById(userId); UserResp userResp= Converts.INSTANCE.toUserResp(entity); if(StrUtil.isNotBlank(userResp.getDeptId())){ - userResp.setUserName(entity.getName()); + userResp.setName(entity.getName()); userResp.setDeptName(deptService.getById(userResp.getDeptId()).getDeptName()); } userResp.setUserTypeLabel(UserTypeEnum.getDescByCode(userResp.getUserType())); @@ -198,6 +198,9 @@ public class UserServiceImpl extends ServiceImpl impleme if(CollUtil.isNotEmpty(req.getPostIds())){ userPostService.bindUserPost(userId, req.getPostIds()); } + if(CollUtil.isNotEmpty(req.getRoleIds())){ + userRoleService.bindUserRole(new UserRoleReq().setUserId(userId).setRoleIds(req.getRoleIds())); + } this.updateById(entity); } diff --git a/core/src/main/resources/mapper/BusinessDataFileMapper.xml b/core/src/main/resources/mapper/BusinessDataFileMapper.xml new file mode 100644 index 0000000..3a129f4 --- /dev/null +++ b/core/src/main/resources/mapper/BusinessDataFileMapper.xml @@ -0,0 +1,50 @@ + + + + + + delete from business_data_part_file + + + and file_id = #{fileId} + + + and folder_id = #{folderId} + + + + + + + insert into business_data_part_file (file_id, folder_id, file_name, file_path, file_type, file_size, upload_time, uploader_id, is_deleted) + values (#{fileId}, #{folderId}, #{fileName}, #{filePath}, #{fileType}, #{fileSize}, #{uploadTime}, #{uploaderId}, #{isDeleted}) + + + + + update business_data_part_file + + file_name = #{newFileName}, + file_path = #{newFilePath}, + + where file_id = #{fileId} + + + diff --git a/core/src/main/resources/mapper/BusinessDataMapper.xml b/core/src/main/resources/mapper/BusinessDataMapper.xml new file mode 100644 index 0000000..f714eae --- /dev/null +++ b/core/src/main/resources/mapper/BusinessDataMapper.xml @@ -0,0 +1,55 @@ + + + + + insert into business_data_part + + folder_name, + parent_id, + creator_id, + create_time, + update_time, + is_deleted, + folder_path, + + + #{folderName}, + #{parentId}, + #{creatorId}, + #{createTime}, + #{updateTime}, + #{isDeleted}, + #{folderPath}, + + + + + + + + + delete from business_data_part where folder_id = #{folderId} + + + update business_data_part + + folder_name = #{folderName}, + update_time = #{updateTime}, + folder_path = #{folderPath}, + + where folder_id = #{folderId} + + + + diff --git a/core/src/main/resources/mapper/ContractMapper.xml b/core/src/main/resources/mapper/ContractMapper.xml index c2a698d..078d6e2 100644 --- a/core/src/main/resources/mapper/ContractMapper.xml +++ b/core/src/main/resources/mapper/ContractMapper.xml @@ -38,9 +38,6 @@ and a.department_id like concat ('%', #{departmentId}, '%') - - and a.sign_date = #{signDate} - and a.duration like concat ('%', #{duration}, '%') @@ -50,12 +47,6 @@ and a.product_service like concat ('%', #{productService}, '%') - - and a.payment_date = #{paymentDate} - - - and a.performance_deadline = #{performanceDeadline} - and a.payment_address like concat ('%', #{paymentAddress}, '%') diff --git a/core/src/main/resources/mapper/ProjectBudgetInfoMapper.xml b/core/src/main/resources/mapper/ProjectBudgetInfoMapper.xml index 5425a2b..d8cd25a 100644 --- a/core/src/main/resources/mapper/ProjectBudgetInfoMapper.xml +++ b/core/src/main/resources/mapper/ProjectBudgetInfoMapper.xml @@ -3,47 +3,24 @@ - a.budget_id, a.project_id, a.budget_name, a.budget_type, - a.budget_amount, a.budget_desc, a.update_by, a.create_time, - a.create_by, a.update_time + a.budget_id, a.project_id, a.budget_name, + a.budget_amount, a.budget_desc, a.attach - - diff --git a/core/src/main/resources/mapper/ProjectMapper.xml b/core/src/main/resources/mapper/ProjectMapper.xml index a603780..5f9a8e5 100644 --- a/core/src/main/resources/mapper/ProjectMapper.xml +++ b/core/src/main/resources/mapper/ProjectMapper.xml @@ -16,7 +16,7 @@ GROUP BY prj.project_id ) SELECT - prj.project_id, prj.project_name, prj.farm_name, prj.status, prj.cover_url, prj.farm_address, prj.client, prj.client_contact, prj.client_phone, prj.inspection_unit, + prj.project_id, prj.project_origin, prj.project_budget, prj.project_name, prj.farm_name, prj.status, prj.cover_url, prj.farm_address, prj.client, prj.client_contact, prj.client_phone, prj.inspection_unit, prj.inspection_contact, prj.inspection_phone, prj.scale, prj.turbine_model, prj.project_manager_id, pm.name AS project_manager_name, prj.constructor_ids, prj.start_date, prj.end_date, con.constructor_name, tp.taskPendingCount, tp.task_progress_count, tp.task_complete_count, tp.task_count, prj.auditor_id, prj.construct_team_leader_id, prj.quality_officer_id diff --git a/core/src/main/resources/mapper/UserMapper.xml b/core/src/main/resources/mapper/UserMapper.xml index 36b314a..abec525 100644 --- a/core/src/main/resources/mapper/UserMapper.xml +++ b/core/src/main/resources/mapper/UserMapper.xml @@ -8,37 +8,55 @@ diff --git a/flowable/pom.xml b/flowable/pom.xml index edd8467..d11bf74 100644 --- a/flowable/pom.xml +++ b/flowable/pom.xml @@ -11,6 +11,10 @@ flowable 1.0.0-SNAPSHOT + + UTF-8 + + com.dite.znpt diff --git a/pom.xml b/pom.xml index 4991235..fcdc462 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,7 @@ core + sip web flowable diff --git a/web/pom.xml b/web/pom.xml index 4dd81fc..c6d6423 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -20,11 +20,11 @@ - - - - - + + com.dite.znpt + sip + 1.0.0-SNAPSHOT + com.dite.znpt core diff --git a/web/src/main/java/com/dite/znpt/web/controller/BusinessDataController.java b/web/src/main/java/com/dite/znpt/web/controller/BusinessDataController.java new file mode 100644 index 0000000..13fb7eb --- /dev/null +++ b/web/src/main/java/com/dite/znpt/web/controller/BusinessDataController.java @@ -0,0 +1,58 @@ +package com.dite.znpt.web.controller; + +import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.dto.FolderDto; +import com.dite.znpt.domain.page.PageBean; +import com.dite.znpt.service.BusinessDataService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +/* + * @Description: 商务资料管理,文件夹层 + * 虽然文件夹本质也是文件,但我们页面上很多地方,光查文件信息,根据文件夹ID + * 获取文件列表,如果文件夹和文件都放一张表,不好区分,接口也不好区分,这样分开写方便 + */ +@Api(tags = "文件夹接口") +@RestController +@Slf4j +@RequestMapping("/businessData/folder") +@ApiOperation("商务资料管理,文件夹contrller层") +public class BusinessDataController { + @Resource + private BusinessDataService businessDataService; + + @ApiOperation(value = "分页查询文件夹", httpMethod = "GET") + @GetMapping("/list") + public Result pageSelect(@RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String folderName) { + PageBean pageBean = businessDataService.pageSelect(page, pageSize, folderName); + return Result.ok(pageBean); + } + + + @ApiOperation(value = "增加文件夹", httpMethod = "POST") + @PostMapping("/creatFolder") + public Result createFolder(@RequestBody FolderDto folderDto) { + return businessDataService.createFolder(folderDto.getName(), folderDto.getParentId()); + } + + @ApiOperation(value = "删除文件夹", httpMethod = "DELETE") + @DeleteMapping("/delete") + public Result delete(@RequestParam Long folderId) { + + return businessDataService.delete(folderId); + } + + @ApiOperation(value = "重命名文件夹", httpMethod = "PUT") + @PutMapping("/rename") + public Result Rename(@RequestParam Long folderId, @RequestParam String newName) { + + return businessDataService.reName(folderId, newName); + } + +} diff --git a/web/src/main/java/com/dite/znpt/web/controller/BusinessDataFileController.java b/web/src/main/java/com/dite/znpt/web/controller/BusinessDataFileController.java new file mode 100644 index 0000000..a54e8f2 --- /dev/null +++ b/web/src/main/java/com/dite/znpt/web/controller/BusinessDataFileController.java @@ -0,0 +1,137 @@ +package com.dite.znpt.web.controller; + +import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.entity.BusinessDataFileEntity; +import com.dite.znpt.domain.page.PageBean; +import com.dite.znpt.mapper.BusinessDataFileMapper; +import com.dite.znpt.service.BusinessDataFileService; +import com.dite.znpt.service.BusinessDataService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLEncoder; +import java.util.Date; + +/* + * @Description: 商务资料管理,文件夹层 + */ + +@Api(tags = "文件接口") +@RestController +@Slf4j +@RequestMapping("/businessData/file") +@ApiOperation("商务资料管理,文件层") +public class BusinessDataFileController { + @Resource + private BusinessDataFileService businessDataFileService; + @Resource + private BusinessDataService businessDataService; + @Resource + private BusinessDataFileMapper businessDataFileMapper; + + @ApiOperation(value = "分页查询文件", httpMethod = "GET") + @GetMapping("/list") + public Result pageSelect(@RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(defaultValue = "0") Long folderId, + @RequestParam(required = false) String fileName) { + PageBean pageBean = businessDataFileService.pageSelect(page, pageSize, folderId, fileName); + return Result.ok(pageBean); + } + + @ApiOperation(value = "增加文件") + @PostMapping("/add") + public Result add(@RequestParam("file") MultipartFile file, + @RequestParam Long folderId) { + + if (file.isEmpty()) { + return Result.error("上传文件为空"); + } + // TODO 以后可以优化,就算文件名一样,加个(1),(2)这种 + + try { + byte[] bytes = file.getBytes(); + String uploadDir = businessDataService.getPath(folderId); + + File uploadedFile = new File(uploadDir + "\\" + file.getOriginalFilename()); + if (uploadedFile.exists()) { + return Result.error("文件已存在"); + } + file.transferTo(uploadedFile); + + // 保存文件信息到数据 + BusinessDataFileEntity fileEntity = new BusinessDataFileEntity(); + fileEntity.setFolderId(folderId); + fileEntity.setFileName(file.getOriginalFilename()); + fileEntity.setFilePath(uploadDir + "\\" + file.getOriginalFilename()); + fileEntity.setFileType(file.getContentType()); + fileEntity.setFileSize(file.getSize()); + fileEntity.setUploadTime(new Date()); + fileEntity.setUploaderId(0L); + System.out.println(uploadDir + "\\" + file.getOriginalFilename()); + businessDataFileService.add(fileEntity); + + return Result.okM("上传成功"); + } catch (IOException e) { + e.printStackTrace(); + return Result.error("上传失败"); + } + } + + @ApiOperation(value = "删除文件") + @DeleteMapping("/delete") + public Result delete(@RequestParam Long fileId) { + businessDataFileService.delete(fileId, null); + return Result.okM("删除成功"); + } + + @ApiOperation(value = "下载文件") + @GetMapping("/download") + public void download(@RequestParam("fileId") Long fileId, HttpServletResponse response) { + String path = businessDataFileService.getPath(fileId); + try { + // path是指想要下载的文件的路径 + File file = new File(path); + log.info(file.getPath()); + // 获取文件名 + String filename = file.getName(); + // 获取文件后缀名 + String ext = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase(); + log.info("文件后缀名:" + ext); + + // 将文件写入输入流 + FileInputStream fileInputStream = new FileInputStream(file); + InputStream fis = new BufferedInputStream(fileInputStream); + byte[] buffer = new byte[fis.available()]; + fis.read(buffer); + fis.close(); + + // 清空response + response.reset(); + // 设置response的Header + response.setCharacterEncoding("UTF-8"); + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8")); + // 告知浏览器文件的大小 + response.addHeader("Content-Length", "" + file.length()); + OutputStream outputStream = new BufferedOutputStream(response.getOutputStream()); + response.setContentType("application/octet-stream"); + outputStream.write(buffer); + outputStream.flush(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + @ApiOperation(value = "重命名文件", httpMethod = "PUT") + @PutMapping("/rename") + public Result reName(@RequestParam Long fileId, @RequestParam String newFileName) { + return businessDataFileService.reName(fileId, newFileName); + } + +} diff --git a/web/src/main/java/com/dite/znpt/web/controller/CommonController.java b/web/src/main/java/com/dite/znpt/web/controller/CommonController.java index 5371ed3..b609870 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/CommonController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/CommonController.java @@ -175,5 +175,11 @@ public class CommonController { return Result.ok(EquipmentTypeEnum.listAll()); } + @ApiOperation(value = "查询合同状态", httpMethod = "GET") + @GetMapping("/list/contract-status") + public Result listContractStatus(){ + return Result.ok(ContractStatusEnum.listAll()); + } + } diff --git a/web/src/main/java/com/dite/znpt/web/controller/EmailController.java b/web/src/main/java/com/dite/znpt/web/controller/EmailController.java new file mode 100644 index 0000000..ab3007a --- /dev/null +++ b/web/src/main/java/com/dite/znpt/web/controller/EmailController.java @@ -0,0 +1,46 @@ +//package com.dite.znpt.web.controller; +// +//import com.dite.znpt.domain.Result; +//import com.dite.znpt.domain.entity.UserEntity; +//import com.dite.znpt.service.EmailService; +//import com.dite.znpt.service.UserService; +//import io.swagger.annotations.Api; +//import io.swagger.annotations.ApiOperation; +//import org.springframework.web.bind.annotation.PostMapping; +//import org.springframework.web.bind.annotation.RequestParam; +//import org.springframework.web.bind.annotation.RestController; +// +//import javax.annotation.Resource; +// +//@Api(tags = "邮件") +//@RestController("/email") +//public class EmailController { +// @Resource +// private EmailService emailService; +// +// @Resource +// private UserService userService; +// +// @ApiOperation(value = "发送邮箱验证码", httpMethod = "POST") +// @PostMapping("/send") +// public Result send(@RequestParam("email") String email) { +// boolean send = emailService.sendVerificationCode(email, emailService.generateCode(email)); +// if (send) { +// return Result.ok("发送成功"); +// } +// return Result.error("发送失败"); +// } +// +// @ApiOperation(value = "验证邮箱验证码", httpMethod = "POST") +// @PostMapping("/verify") +// public Result verify(@RequestParam("userId") String userId, @RequestParam("email") String email, @RequestParam("code") String code) { +// boolean verify = emailService.verifyCode(email, code); +// if (verify) { +// UserEntity userEntity = userService.getById(userId); +// userEntity.setEmail(email); +// userService.updateById(userEntity); +// return Result.ok("验证成功"); +// } +// return Result.error("验证失败"); +// } +//} diff --git a/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java b/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java index 238e447..bbfdccb 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/EquipmentController.java @@ -1,5 +1,6 @@ package com.dite.znpt.web.controller; +import com.baomidou.mybatisplus.core.metadata.IPage; import com.dite.znpt.domain.PageResult; import com.dite.znpt.domain.Result; import com.dite.znpt.domain.vo.EquipmentListReq; @@ -8,35 +9,33 @@ import com.dite.znpt.domain.vo.EquipmentResp; import com.dite.znpt.service.EquipmentService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; -import java.util.List; /** * @author Bear.G * @date 2025/7/23/周三 17:41 * @description */ -@Api(tags = "设备信息") +@Api(tags = "设备信息----------------------------") @RestController @RequestMapping("/equipment") public class EquipmentController { + private static final Logger log = LoggerFactory.getLogger(EquipmentController.class); + @Resource private EquipmentService equipmentService; @ApiOperation(value = "分页查询设备信息列表", httpMethod = "GET") @GetMapping("/page") public PageResult page(EquipmentListReq req) { - return PageResult.ok(equipmentService.page(req)); - } - - @ApiOperation(value = "查询设备信息列表", httpMethod = "GET") - @GetMapping("/list") - public Result> list(EquipmentListReq req) { - return Result.ok(equipmentService.list(req)); + IPage page = equipmentService.page(req); + return PageResult.ok(page.getRecords(), page.getTotal()); } @ApiOperation(value = "查询设备信息详情", httpMethod = "GET") @@ -66,4 +65,47 @@ public class EquipmentController { return Result.ok(); } + @ApiOperation(value = "查询设备采购记录", httpMethod = "GET") + @GetMapping("/procurement/page") + public PageResult procurementPage(EquipmentListReq req) { + log.info("=== 设备采购记录查询接口被调用 ==="); + log.info("接收到的请求参数: {}", req); + log.info("设备名称: {}", req.getEquipmentName()); + log.info("设备类型: {}", req.getEquipmentType()); + log.info("供应商名称: {}", req.getSupplierName()); + log.info("采购订单号: {}", req.getPurchaseOrder()); + log.info("次户号: {}", req.getAccountNumber()); + log.info("数量: {}", req.getQuantity()); + log.info("单价: {}", req.getUnitPrice()); + log.info("总价: {}", req.getTotalPrice()); + log.info("盘点依据: {}", req.getInventoryBasis()); + log.info("动态记录: {}", req.getDynamicRecord()); + log.info("页码: {}", req.getPage()); + log.info("每页大小: {}", req.getPageSize()); + log.info("排序字段: {}", req.getOrderBy()); + log.info("排序方向: {}", req.getOrderDirection()); + + IPage page = equipmentService.procurementPage(req); + return PageResult.ok(page.getRecords(), page.getTotal()); + } + + @ApiOperation(value = "新增设备采购记录", httpMethod = "POST") + @PostMapping("/procurement") + public Result addProcurement(@Validated @RequestBody EquipmentReq req){ + equipmentService.saveProcurement(req); + return Result.ok(); + } + + @ApiOperation(value = "修改设备采购记录", httpMethod = "PUT") + @PutMapping("/procurement/{equipmentId}") + public Result editProcurement(@PathVariable String equipmentId, @Validated @RequestBody EquipmentReq req){ + equipmentService.updateProcurement(equipmentId, req); + return Result.ok(); + } + + @ApiOperation(value = "获取采购统计信息", httpMethod = "GET") + @GetMapping("/procurement/stats") + public Result getProcurementStats(){ + return Result.ok(equipmentService.getProcurementStats()); + } } diff --git a/web/src/main/java/com/dite/znpt/web/controller/PostController.java b/web/src/main/java/com/dite/znpt/web/controller/PostController.java index 9d9b49c..f9231f0 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/PostController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/PostController.java @@ -1,11 +1,15 @@ package com.dite.znpt.web.controller; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.dite.znpt.domain.PageResult; import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.entity.UserPostEntity; import com.dite.znpt.domain.vo.PostListReq; import com.dite.znpt.domain.vo.PostReq; import com.dite.znpt.domain.vo.PostResp; +import com.dite.znpt.domain.vo.UserResp; import com.dite.znpt.service.PostService; +import com.dite.znpt.service.UserPostService; import com.dite.znpt.util.ValidationGroup; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -27,6 +31,9 @@ public class PostController { @Resource private PostService postService; + @Resource + private UserPostService userPostService; + @ApiOperation(value = "分页查询岗位信息列表", httpMethod = "GET") @GetMapping("/page") public PageResult page(PostListReq req){ @@ -62,8 +69,14 @@ public class PostController { @ApiOperation(value = "删除岗位信息", httpMethod = "DELETE") @DeleteMapping("/{postId}") public Result remove(@PathVariable String postId){ + userPostService.remove(new QueryWrapper().eq("post_id", postId)); postService.deleteById(postId); return Result.ok(); } + @ApiOperation(value="查询属于该岗位的用户", httpMethod = "GET") + @GetMapping("/{postId}/user") + public Result> listUser(@PathVariable String postId){ + return Result.ok(userPostService.getUsersByPostId(postId)); + } } diff --git a/web/src/main/java/com/dite/znpt/web/controller/ProjectBudgetInfoController.java b/web/src/main/java/com/dite/znpt/web/controller/ProjectBudgetInfoController.java index 3771898..c59a9ec 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/ProjectBudgetInfoController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/ProjectBudgetInfoController.java @@ -3,10 +3,10 @@ package com.dite.znpt.web.controller; import com.dite.znpt.domain.PageResult; import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.vo.ProjectBudgetInfoDetailResp; +import com.dite.znpt.domain.vo.ProjectBudgetInfoImportReq; import com.dite.znpt.domain.vo.ProjectBudgetInfoListReq; import com.dite.znpt.domain.vo.ProjectBudgetInfoListResp; -import com.dite.znpt.domain.vo.ProjectBudgetInfoReq; -import com.dite.znpt.domain.vo.ProjectBudgetInfoResp; import com.dite.znpt.service.ProjectBudgetInfoService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -26,22 +26,35 @@ public class ProjectBudgetInfoController { @Resource private ProjectBudgetInfoService projectBudgetInfoService; - @ApiOperation(value = "获取项目预算信息列表", httpMethod = "GET") + @ApiOperation(value = "筛选项目预算信息列表", httpMethod = "GET") @GetMapping("/list") - public PageResult list(ProjectBudgetInfoListReq projectBudgetInfoReq) { - return PageResult.ok(projectBudgetInfoService.selectList(projectBudgetInfoReq)); + public Result> list(ProjectBudgetInfoListReq projectBudgetInfoListReq) { + return Result.ok(projectBudgetInfoService.list(projectBudgetInfoListReq)); } - @ApiOperation(value = "根据项目id获取项目预算信息列表", httpMethod = "GET") - @GetMapping("/detail/{projectId}") - public PageResult detailByProjectId(@PathVariable String projectId) { - return PageResult.ok(projectBudgetInfoService.detailByProjectId(projectId)); + @ApiOperation(value = "分页筛选项目预算信息列表", httpMethod = "GET") + @GetMapping("/page") + public PageResult page(ProjectBudgetInfoListReq projectBudgetInfoListReq) { + return PageResult.ok(projectBudgetInfoService.page(projectBudgetInfoListReq)); } - @ApiOperation(value = "保存项目预算信息", httpMethod = "POST") - @PostMapping - public Result add(@RequestBody List projectBudgetInfoReq) { - projectBudgetInfoService.saveData(projectBudgetInfoReq); + @ApiOperation(value = "新增项目预算信息", httpMethod = "POST") + @PostMapping("/save") + public Result save(@RequestBody ProjectBudgetInfoImportReq req) { + projectBudgetInfoService.saveData(req); + return Result.ok(); + } + + @ApiOperation(value = "项目预算信息详情", httpMethod = "GET") + @GetMapping("/{projectId}/detail") + public Result detail(@PathVariable String projectId) { + return Result.ok(projectBudgetInfoService.detailByProjectId(projectId)); + } + + @ApiOperation(value = "删除项目预算信息", httpMethod = "DELETE") + @DeleteMapping("/{budgetId}") + public Result delete(@PathVariable String budgetId) { + projectBudgetInfoService.delete(budgetId); return Result.ok(); } } diff --git a/web/src/main/java/com/dite/znpt/web/controller/ProjectMemberController.java b/web/src/main/java/com/dite/znpt/web/controller/ProjectMemberController.java index 85d786a..4a670f7 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/ProjectMemberController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/ProjectMemberController.java @@ -2,12 +2,7 @@ package com.dite.znpt.web.controller; import com.dite.znpt.domain.PageResult; import com.dite.znpt.domain.Result; -import com.dite.znpt.domain.vo.ProjectMemberListReq; -import com.dite.znpt.domain.vo.ProjectMemberReq; -import com.dite.znpt.domain.vo.ProjectMemberResp; -import com.dite.znpt.domain.vo.ProjectKanbanStatsResp; -import com.dite.znpt.domain.vo.ProjectKanbanDataResp; -import com.dite.znpt.domain.vo.ProjectDetailResp; +import com.dite.znpt.domain.vo.*; import com.dite.znpt.service.ProjectMemberService; import com.dite.znpt.util.PageUtil; import com.dite.znpt.util.ValidationGroup; @@ -24,6 +19,7 @@ import java.util.List; * @date 2025/08/05 * @Description: 项目人员管理Controller */ + @Api(tags = "项目人员管理") @RestController @RequestMapping("/project-member") diff --git a/web/src/main/java/com/dite/znpt/web/controller/RoleController.java b/web/src/main/java/com/dite/znpt/web/controller/RoleController.java index 3be61d0..a9813b4 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/RoleController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/RoleController.java @@ -1,10 +1,13 @@ package com.dite.znpt.web.controller; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.dite.znpt.domain.PageResult; import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.entity.UserRoleEntity; import com.dite.znpt.domain.vo.RoleMenuReq; import com.dite.znpt.domain.vo.RoleReq; import com.dite.znpt.domain.vo.RoleResp; +import com.dite.znpt.domain.vo.UserResp; import com.dite.znpt.service.RoleMenuService; import com.dite.znpt.service.RoleService; import com.dite.znpt.service.UserRoleService; @@ -14,8 +17,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -34,6 +35,9 @@ public class RoleController { @Resource private RoleMenuService roleMenuService; + @Resource + private UserRoleService userRoleService; + @ApiOperation(value = "分页查询角色信息列表", httpMethod = "GET") @GetMapping("/page") public PageResult page(@RequestParam(value = "roleName", required = false) String roleName){ @@ -69,6 +73,7 @@ public class RoleController { @ApiOperation(value = "删除角色信息", httpMethod = "DELETE") @DeleteMapping("/{roleId}") public Result delete(@PathVariable String roleId){ + userRoleService.remove(new QueryWrapper().eq("role_id", roleId)); roleService.deleteById(roleId); return Result.ok(); } @@ -79,4 +84,10 @@ public class RoleController { roleMenuService.bindRoleMenu(req); return Result.ok(); } + + @ApiOperation(value="查询属于该角色的用户", httpMethod = "GET") + @GetMapping("/{roleId}/user") + public Result> listUser(@PathVariable String roleId){ + return Result.ok(userRoleService.getUsersByRoleId(roleId)); + } } diff --git a/web/src/main/java/com/dite/znpt/web/controller/UserController.java b/web/src/main/java/com/dite/znpt/web/controller/UserController.java index 18bde3a..057e6b5 100644 --- a/web/src/main/java/com/dite/znpt/web/controller/UserController.java +++ b/web/src/main/java/com/dite/znpt/web/controller/UserController.java @@ -1,9 +1,13 @@ package com.dite.znpt.web.controller; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.dite.znpt.domain.PageResult; import com.dite.znpt.domain.Result; +import com.dite.znpt.domain.entity.UserPostEntity; +import com.dite.znpt.domain.entity.UserRoleEntity; import com.dite.znpt.domain.vo.*; +import com.dite.znpt.service.UserPostService; import com.dite.znpt.service.UserRoleService; import com.dite.znpt.service.UserService; import com.dite.znpt.util.ValidationGroup; @@ -26,6 +30,8 @@ public class UserController { private UserService userService; @Resource private UserRoleService userRoleService; + @Resource + private UserPostService userPostService; @ApiOperation(value = "分页查询用户信息列表", httpMethod = "GET") @GetMapping("/page") @@ -68,6 +74,8 @@ public class UserController { @ApiOperation(value = "删除用户信息", httpMethod = "DELETE") @DeleteMapping("/{userId}") public Result remove(@PathVariable String userId) { + userRoleService.remove(new QueryWrapper().eq("user_id", userId)); + userPostService.remove(new QueryWrapper().eq("user_id", userId)); userService.deleteById(userId); return Result.ok(); } diff --git a/web/src/main/resources/application-dev.yml b/web/src/main/resources/application-dev.yml index 24e7b09..b9cbe6f 100644 --- a/web/src/main/resources/application-dev.yml +++ b/web/src/main/resources/application-dev.yml @@ -105,39 +105,39 @@ sa-token: # 是否输出操作日志 is-log: true -#sip-config: -# name: 信令服务 -# ip: 127.0.0.1 -# port: 1074 -# charset: gb2312 -# domain: 3402000000 -# id: 34020000002000000001 -# password: 123456 -# mediaType: mp4 -# -#zlm-config: -# # 流媒体名称 -# mediaName: 媒体服务 -# # 流媒体服务商 -# mediaService: ZLMdia -# # 公网ip -# publicHost: -# # 接口ip -# apiHost: 127.0.0.1 -# # 接口端口 -# apiPort: 8080 -# # 密钥 -# secretKey: 6Q76ivvVOQDsnnfOSKbtVzcYpbgy4n1G -# # 流id前缀 -# streamPrefix: -# # rtp ip -# rtpHost: 127.0.0.1 -# # rtp 端口 -# rtpPort: 8080 -# # 动态端口起始值 -# dynamicPortStart: 30150 -# # 动态端口结束值 -# dynamicPortEnd: 30185 +sip-config: + name: 信令服务 + ip: 127.0.0.1 + port: 1074 + charset: gb2312 + domain: 3402000000 + id: 34020000002000000001 + password: 123456 + mediaType: mp4 + +zlm-config: + # 流媒体名称 + mediaName: 媒体服务 + # 流媒体服务商 + mediaService: ZLMdia + # 公网ip + publicHost: + # 接口ip + apiHost: 127.0.0.1 + # 接口端口 + apiPort: 8080 + # 密钥 + secretKey: 6Q76ivvVOQDsnnfOSKbtVzcYpbgy4n1G + # 流id前缀 + streamPrefix: + # rtp ip + rtpHost: 127.0.0.1 + # rtp 端口 + rtpPort: 8080 + # 动态端口起始值 + dynamicPortStart: 30150 + # 动态端口结束值 + dynamicPortEnd: 30185 upload: # 此处仅定义总的父路径,细节定义到 FilePathEnum