From 129c36ed8092c9041cae78101fdbce0266c58588 Mon Sep 17 00:00:00 2001 From: acgist <289547414@qq.com> Date: Sat, 25 Feb 2023 13:31:57 +0800 Subject: [PATCH] =?UTF-8?q?[+]=20=E6=95=B4=E4=BD=93=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 生产者接入完成 媒体作为信令服务的终端注册 --- README.md | 2 - docs/Deploy.md | 10 +- taoyao-client-harmony/README.md | 3 + taoyao-client-media/README.md | 47 +- taoyao-client-media/mediasoup | 2 +- taoyao-client-media/package.json | 1 - taoyao-client-media/src/Config.js | 52 +- taoyao-client-media/src/Server.js | 130 +--- taoyao-client-media/src/Signal.js | 722 +++++++++++++++--- taoyao-client-media/src/certs/privateKey.pem | 28 - taoyao-client-media/src/certs/publicKey.pem | 22 - taoyao-client-media/src/index.html | 15 - taoyao-client-web/src/App.vue | 26 +- taoyao-client-web/src/components/Config.js | 13 +- .../src/components/RemoteClient.vue | 14 +- .../src/components/SettingRoom.vue | 37 +- .../src/components/SettingSignal.vue | 4 +- taoyao-client-web/src/components/Taoyao.js | 203 +++-- taoyao-signal-server/pom.xml | 5 - .../taoyao/boot/annotation/Listener.java | 22 + .../acgist/taoyao/boot/config/Constant.java | 72 +- .../boot/config/IpRewriteProperties.java | 30 + .../boot/config/IpRewriteRuleProperties.java | 27 + .../taoyao/boot/config/MediaProperties.java | 4 - .../boot/config/MediaServerProperties.java | 39 - .../taoyao/boot/config/ScriptProperties.java | 4 - .../taoyao/boot/config/TaoyaoProperties.java | 2 - .../configuration/BootAutoConfiguration.java | 51 +- .../SpringDocAutoConfiguration.java | 35 +- .../WebMvcConfigurerAutoConfiguration.java | 23 +- .../com/acgist/taoyao/boot/model/Header.java | 2 +- .../com/acgist/taoyao/boot/model/Message.java | 31 +- .../acgist/taoyao/boot/service/IdService.java | 9 +- .../acgist/taoyao/boot/service/IpService.java | 48 -- .../boot/service/impl/IdServiceImpl.java | 35 +- .../boot/service/impl/IpServiceImpl.java | 145 ---- .../acgist/taoyao/boot/utils/ErrorUtils.java | 35 +- .../acgist/taoyao/boot/utils/JSONUtils.java | 4 +- .../acgist/taoyao/boot/utils/MapUtils.java | 106 +++ .../acgist/taoyao/boot/utils/NetUtils.java | 198 +++++ .../taoyao-boot/src/main/resources/banner.txt | 4 +- .../src/main/resources/logback-spring.xml | 34 +- .../taoyao/boot/utils/NetUtilsTest.java | 72 ++ taoyao-signal-server/taoyao-server/pom.xml | 4 + .../TaoyaoAutoConfiguration.java | 23 +- .../taoyao/controller/ConfigController.java | 16 +- .../taoyao/controller/ControlController.java | 88 ++- .../interceptor/SecurityInterceptor.java | 15 +- .../taoyao/interceptor/SlowInterceptor.java | 11 +- ...ientListener.java => QnjrcmzListener.java} | 12 +- .../taoyao/listener/ThyjxcfListener.java | 29 + .../acgist/taoyao/main/TaoyaoApplication.java | 1 + .../main/resources/META-INF/spring.factories | 2 +- .../src/main/resources/application-dev.yml | 20 +- .../src/main/resources/application-prd.yml | 2 + .../src/main/resources/application.yml | 40 +- .../acgist/taoyao/service/IdServiceTest.java | 1 + .../acgist/taoyao/service/IpServiceTest.java | 77 -- .../acgist/taoyao/signal/MapBodyGetter.java | 103 --- .../acgist/taoyao/signal/client/Client.java | 35 +- .../taoyao/signal/client/ClientAdapter.java | 85 ++- .../taoyao/signal/client/ClientManager.java | 74 +- .../taoyao/signal/client/ClientStatus.java | 81 +- .../taoyao/signal/client/ClientType.java | 62 +- .../signal/client/socket/SocketClient.java | 27 +- .../socket/SocketSignalMessageHandler.java | 4 +- .../client/websocket/WebSocketClient.java | 9 +- .../client/websocket/WebSocketSignal.java | 11 +- .../WebSocketSignalConfigurator.java | 10 +- .../MediaClientAutoConfiguration.java | 30 - .../ScriptAutoConfiguration.java | 37 +- .../SignalAutoConfiguration.java | 6 +- .../SocketSignalAutoConfiguration.java | 7 +- .../WebSocketSignalAutoConfiguration.java | 4 +- .../signal/controller/ClientController.java | 31 +- .../signal/controller/ProtocolController.java | 21 +- .../signal/controller/RoomController.java | 16 +- .../signal/event/ApplicationEventAdapter.java | 34 +- .../event/{client => }/ClientCloseEvent.java | 5 +- .../signal/event/ClientEventAdapter.java | 12 +- .../event/MediaClientRegisterEvent.java | 18 + .../signal/event/MediaEventAdapter.java | 34 - .../signal/event/MediaProduceEvent.java | 30 + .../taoyao/signal/event/RoomEventAdapter.java | 14 +- .../taoyao/signal/media/MediaClient.java | 309 -------- .../signal/media/MediaClientManager.java | 71 -- .../com/acgist/taoyao/signal/media/Peer.java | 44 -- .../com/acgist/taoyao/signal/media/Room.java | 143 ---- .../acgist/taoyao/signal/media/Transport.java | 53 -- .../taoyao/signal/protocol/Protocol.java | 12 +- .../signal/protocol/ProtocolAdapter.java | 31 +- .../protocol/ProtocolClientAdapter.java | 23 +- .../protocol/ProtocolControlAdapter.java | 47 ++ .../signal/protocol/ProtocolManager.java | 41 +- .../signal/protocol/ProtocolMediaAdapter.java | 47 -- .../signal/protocol/ProtocolRoomAdapter.java | 57 +- .../protocol/client/ClientAlarmProtocol.java | 54 ++ .../client/ClientBroadcastProtocol.java | 25 +- .../protocol/client/ClientCloseProtocol.java | 23 +- .../protocol/client/ClientConfigProtocol.java | 52 +- .../client/ClientHeartbeatProtocol.java | 20 +- .../protocol/client/ClientListProtocol.java | 31 +- .../client/ClientOfflineProtocol.java | 9 - .../protocol/client/ClientOnlineProtocol.java | 21 +- .../client/ClientRegisterProtocol.java | 91 ++- .../protocol/client/ClientStatusProtocol.java | 10 +- .../client/ClientUnicastProtocol.java | 5 +- .../protocol/client/ClientWakeupProtocol.java | 23 +- .../protocol/control/ControlBellProtocol.java | 44 ++ .../control/ControlPhotographProtocol.java | 44 ++ .../protocol/control/ControlPtzProtocol.java | 68 ++ .../control/ControlRecordProtocol.java | 45 ++ .../MediaAudioActiveSpeakerProtocol.java | 19 +- .../protocol/media/MediaConsumeProtocol.java | 83 ++ .../media/MediaIceRestartProtocol.java | 29 + .../protocol/media/MediaListProtocol.java | 52 -- .../protocol/media/MediaProduceProtocol.java | 74 +- .../media/MediaProducerScoreProtocol.java | 32 +- .../protocol/media/MediaRebootProtocol.java | 53 -- .../protocol/media/MediaRecordProtocol.java | 5 - .../protocol/media/MediaRegisterProtocol.java | 70 -- .../MediaRouterRtpCapabilitiesProtocol.java | 17 +- .../protocol/media/MediaShutdownProtocol.java | 51 -- .../MediaTransportWebRtcConnectProtocol.java | 26 +- .../MediaTransportWebRtcCreateProtocol.java | 75 +- .../platform/PlatformErrorProtocol.java | 61 +- .../platform/PlatformRebootProtocol.java | 15 +- .../platform/PlatformScriptProtocol.java | 8 +- .../platform/PlatformShutdownProtocol.java | 26 +- .../protocol/room/RoomCloseProtocol.java | 16 +- .../protocol/room/RoomCreateProtocol.java | 55 +- .../protocol/room/RoomEnterProtocol.java | 31 +- .../protocol/room/RoomExpelProtocol.java | 5 + .../protocol/room/RoomInviteProtocol.java | 5 + .../protocol/room/RoomLeaveProtocol.java | 5 + .../protocol/room/RoomListProtocol.java | 9 +- .../protocol/room/RoomStatusProtocol.java | 5 + .../protocol/room/RoomSubscribeProtocol.java | 10 + .../room/RoomUnsubscribeProtocol.java | 10 + .../system/SystemDiskspaceProtocol.java | 10 + .../protocol/system/SystemRebootProtocol.java | 15 +- .../system/SystemShutdownProtocol.java | 15 +- .../service/impl/SecurityServiceImpl.java | 14 +- .../signal/terminal/media/ClientWrapper.java | 101 +++ .../signal/terminal/media/Consumer.java | 20 + .../signal/terminal/media/DataConsumer.java | 5 + .../signal/terminal/media/DataProducer.java | 9 + .../taoyao/signal/terminal/media/Kind.java | 35 + .../signal/terminal/media/Producer.java | 47 ++ .../taoyao/signal/terminal/media/Room.java | 182 +++++ .../{ => terminal}/media/RoomManager.java | 63 +- .../{ => terminal}/media/RoomStatus.java | 6 +- .../signal/terminal/media/Transport.java | 71 ++ .../taoyao/signal/terminal/package-info.java | 4 + .../wrapper/WebrtcPropertiesWrapper.java | 79 -- ...ot.autoconfigure.AutoConfiguration.imports | 1 - 156 files changed, 3659 insertions(+), 2817 deletions(-) create mode 100644 taoyao-client-harmony/README.md delete mode 100644 taoyao-client-media/src/certs/privateKey.pem delete mode 100644 taoyao-client-media/src/certs/publicKey.pem delete mode 100644 taoyao-client-media/src/index.html create mode 100644 taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/annotation/Listener.java create mode 100644 taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java create mode 100644 taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java delete mode 100644 taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaServerProperties.java delete mode 100644 taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IpService.java delete mode 100644 taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IpServiceImpl.java create mode 100644 taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/MapUtils.java create mode 100644 taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java create mode 100644 taoyao-signal-server/taoyao-boot/src/test/java/com/acgist/taoyao/boot/utils/NetUtilsTest.java rename taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/{HTTPClientListener.java => QnjrcmzListener.java} (59%) create mode 100644 taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/ThyjxcfListener.java delete mode 100644 taoyao-signal-server/taoyao-server/src/test/java/com/acgist/taoyao/service/IpServiceTest.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/MapBodyGetter.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/MediaClientAutoConfiguration.java rename taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/{client => }/ClientCloseEvent.java (67%) create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaClientRegisterEvent.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaEventAdapter.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaProduceEvent.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/MediaClient.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/MediaClientManager.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Peer.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Room.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Transport.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolControlAdapter.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolMediaAdapter.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientAlarmProtocol.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlBellProtocol.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlPhotographProtocol.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlPtzProtocol.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlRecordProtocol.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaConsumeProtocol.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaListProtocol.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRebootProtocol.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRecordProtocol.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRegisterProtocol.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaShutdownProtocol.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomSubscribeProtocol.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomUnsubscribeProtocol.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemDiskspaceProtocol.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/ClientWrapper.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Consumer.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/DataConsumer.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/DataProducer.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Kind.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Producer.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Room.java rename taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/{ => terminal}/media/RoomManager.java (68%) rename taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/{ => terminal}/media/RoomStatus.java (88%) create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Transport.java create mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/package-info.java delete mode 100644 taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/wrapper/WebrtcPropertiesWrapper.java diff --git a/README.md b/README.md index 7d358e5..c87d680 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,6 @@ |taoyao-client-harmony|鸿蒙终端|鸿蒙智能终端接入| |taoyao-signal-server|信令服务|终端信令控制| -> 注意:只有Web实现完成信令控制 - ## 模式 监控模式、直播模式、会议模式、屏幕共享模式 diff --git a/docs/Deploy.md b/docs/Deploy.md index 2aa9653..5b66967 100644 --- a/docs/Deploy.md +++ b/docs/Deploy.md @@ -246,7 +246,7 @@ git checkout taoyao cd .. npm install -# 配置服务 +# 配置服务:服务名称必须和配置终端标识一致否则不能执行重启和关闭信令 pm2 start npm --name "taoyao-client-media" -- run dev | prd pm2 save @@ -255,7 +255,7 @@ pm2 ecosystem pm2 start | reload ecosystem.config.json pm2 save -# 管理服务 +# 管理服务:服务名称必须和配置终端标识一致否则不能执行重启和关闭信令 pm2 start | stop | restart taoyao-client-media ``` @@ -364,6 +364,9 @@ pm2 save # 管理服务 pm2 start | stop | restart taoyao-client-web +# 打包代码 +npm run build + # Nginx配置 vim /etc/nginx/taoyao-client-web.cnf @@ -393,8 +396,6 @@ firewall-cmd --zone=public --add-port=8443/tcp --permanent firewall-cmd --zone=public --add-port=8888/tcp --permanent # 信令服务(Socket):没有启用不用添加规则 firewall-cmd --zone=public --add-port=9999/tcp --permanent -# 媒体服务(控制):只暴露给信令服务 -firewall-cmd --zone=public --add-rich-rule="rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="9443" accept" --permanent # 媒体服务(数据) firewall-cmd --zone=public --add-port=40000-49999/udp --permanent @@ -405,7 +406,6 @@ firewall-cmd --list-ports #firewall-cmd --zone=public --remove-port=8443/tcp --permanent #firewall-cmd --zone=public --remove-port=8888/tcp --permanent #firewall-cmd --zone=public --remove-port=9999/tcp --permanent -#firewall-cmd --zone=public --remove-rich-rule="rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="9443" accept" --permanent #firewall-cmd --zone=public --remove-port=40000-49999/udp --permanent ``` diff --git a/taoyao-client-harmony/README.md b/taoyao-client-harmony/README.md new file mode 100644 index 0000000..20089e5 --- /dev/null +++ b/taoyao-client-harmony/README.md @@ -0,0 +1,3 @@ +# 鸿蒙终端 + +提供嵌入式开发能力 diff --git a/taoyao-client-media/README.md b/taoyao-client-media/README.md index c519853..6022ec8 100644 --- a/taoyao-client-media/README.md +++ b/taoyao-client-media/README.md @@ -1,4 +1,4 @@ -# 媒体 +# 媒体终端 只要负责媒体处理,不要添加任何业务逻辑,所有业务逻辑都由[taoyao-signal-server](../taoyao-signal-server)处理。 @@ -16,50 +16,9 @@ make make -C worker ``` -## 事件 +## 节点配置 -``` -mediasoup.observer.on("newworker", fn(worker)); -worker.on("died", fn(error)); -worker.observer.on("close", fn()); -worker.observer.on("newrouter", fn(router)); -worker.observer.on("newwebrtcserver", fn(router)); -router.on(“workerclose”, fn()); -router.observer.on(“close”, fn()); -router.observer.on(“newtransport”, fn(transport)); -router.observer.on(“newrtpobserver”, fn(rtpObserver)); -transport.on("trace", fn(trace)); -transport.on(“routerclose”, fn()); -transport.on(“listenserverclose”, fn()); -transport.on(“trace”, fn(trace)); -webRtcTransport.on(“icestatechange”, fn(iceState)) -webRtcTransport.on(“iceselectedtuplechange”, fn(iceSelectedTuple)) -webRtcTransport.on(“dtlsstatechange”, fn(dtlsState)) -webRtcTransport.on(“sctpstatechange”, fn(sctpState)) -plainTransport.on(“tuple”, fn(tuple)) -plainTransport.on(“rtcptuple”, fn(rtcpTuple)) -plainTransport.on(“sctpstatechange”, fn(sctpState)) -pipeTransport.on(“sctpstatechange”, fn(sctpState)) -directTransport.on(“rtcp”, fn(rtcpPacket)) -transport.observer.on(“close”, fn()) -transport.observer.on(“newproducer”, fn(producer)) -transport.observer.on(“newconsumer”, fn(consumer)) -transport.observer.on(“newdataproducer”, fn(dataProducer)) -transport.observer.on(“newdataconsumer”, fn(dataConsumer)) -transport.observer.on(“trace”, fn(trace)) -webRtcTransport.observer.on(“icestatechange”, fn(iceState)) -webRtcTransport.observer.on(“iceselectedtuplechange”, fn(iceSelectedTuple)) -webRtcTransport.observer.on(“dtlsstatechange”, fn(dtlsState)) -webRtcTransport.observer.on(“sctpstatechange”, fn(sctpState)) -plainTransport.observer.on(“tuple”, fn(tuple)) -plainTransport.observer.on(“rtcptuple”, fn(rtcpTuple)) -plainTransport.observer.on(“sctpstatechange”, fn(sctpState)) -pipeTransport.observer.on(“sctpstatechange”, fn(sctpState)) -``` - -## 安全 - -默认媒体服务只要暴露媒体`UDP`端口,信令接口不用暴露,所以使用简单鉴权。 +需要保证`src/Config.js`中的`clientId`和`ecosystem.config.json`中的`name`保持一致,否者重启和关闭信令无效。 ## 动态调节码率 diff --git a/taoyao-client-media/mediasoup b/taoyao-client-media/mediasoup index f02edc6..64851a8 160000 --- a/taoyao-client-media/mediasoup +++ b/taoyao-client-media/mediasoup @@ -1 +1 @@ -Subproject commit f02edc62e764b061917cbd215528db62e27fc9bd +Subproject commit 64851a8a813dc59f1269ea92c444fa36cceaf961 diff --git a/taoyao-client-media/package.json b/taoyao-client-media/package.json index 6a6159a..d9e8e2a 100644 --- a/taoyao-client-media/package.json +++ b/taoyao-client-media/package.json @@ -10,7 +10,6 @@ }, "dependencies": { "ws": "^8.12.0", - "moment": "^2.29.4", "mediasoup": "file:./mediasoup" } } diff --git a/taoyao-client-media/src/Config.js b/taoyao-client-media/src/Config.js index 6b5bfb2..783c7c6 100644 --- a/taoyao-client-media/src/Config.js +++ b/taoyao-client-media/src/Config.js @@ -6,27 +6,28 @@ const os = require("os"); module.exports = { // 服务名称 name: "taoyao-client-media", - // 服务版本 - version: "1.0.0", - // 欢迎页面 - welcome: `${__dirname}/index.html`, - // 日志级别 - logLevel: "DEBUG", - // 录像目录 - recordStoragePath: "/data/record", - // 信令服务 - https: { - // 信令服务地址端口 - listenIp: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0", - listenPort: process.env.HTTPS_LISTEN_PORT || 9443, - // 信令服务安全配置 + // 保存目录 + storagePath: "/data/storage", + // 图片目录 + storageImagePath: "/data/storage/image", + // 视频目录 + storageVideoPath: "/data/storage/video", + // 信令配置 + signal: { + // 服务版本 + version: "1.0.0", + // 终端标识 + clientId: "taoyao-client-media", + // 地址 + host: "127.0.0.1", + // 端口 + port: 8888, + // 协议 + scheme: "wss", + // 信令用户 username: "taoyao", + // 信令密码 password: "taoyao", - // 信令服务证书配置 - tls: { - cert: process.env.HTTPS_CERT_PUBLIC_KEY || `${__dirname}/certs/publicKey.pem`, - key: process.env.HTTPS_CERT_PRIVATE_KEY || `${__dirname}/certs/privateKey.pem`, - }, }, // 水印 watermark: { @@ -42,8 +43,7 @@ module.exports = { workerSize: Object.keys(os.cpus()).length, // Worker:https://mediasoup.org/documentation/v3/mediasoup/api/#WorkerSettings workerSettings: { - // 级别:debug | warn | error | none - logLevel: "warn", + // 记录标记 logTags: [ "bwe", "ice", @@ -59,6 +59,8 @@ module.exports = { "message", "simulcast", ], + // 级别:debug | warn | error | none + logLevel: "warn", rtcMinPort: process.env.MEDIASOUP_MIN_PORT || 40000, rtcMaxPort: process.env.MEDIASOUP_MAX_PORT || 49999, }, @@ -120,14 +122,14 @@ module.exports = { ip: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0", port: 44444, // 公网地址 - announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP, + announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP || "192.168.1.110", }, { protocol: "tcp", ip: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0", port: 44444, // 公网地址 - announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP, + announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP || "192.168.1.110", }, ], }, @@ -137,7 +139,7 @@ module.exports = { { ip: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0", // 公网地址 - announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP, + announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP || "192.168.1.110", }, ], initialAvailableOutgoingBitrate: 1000000, @@ -150,7 +152,7 @@ module.exports = { listenIp: { ip: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0", // 公网地址 - announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP, + announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP || "192.168.1.110", }, maxSctpMessageSize: 262144, }, diff --git a/taoyao-client-media/src/Server.js b/taoyao-client-media/src/Server.js index a8135cf..6966b18 100644 --- a/taoyao-client-media/src/Server.js +++ b/taoyao-client-media/src/Server.js @@ -1,49 +1,44 @@ #!/usr/bin/env node -const fs = require("fs"); -const ws = require("ws"); -const https = require("https"); -const mediasoup = require("mediasoup"); const config = require("./Config"); -const Signal = require("./Signal"); +const mediasoup = require("mediasoup"); +const { Signal, signalChannel } = require("./Signal"); // 线程名称 process.title = config.name; +// 禁止校验无效证书 +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; -// 无效信令终端列表 -const clients = []; // Mediasoup Worker列表 const mediasoupWorkers = []; - -// HTTPS server -let httpsServer; -// WebSocket server -let webSocketServer; -// 信令 +// 信令服务 const signal = new Signal(mediasoupWorkers); /** - * 启动Mediasoup Worker + * 创建Mediasoup Worker列表 */ async function buildMediasoupWorkers() { + // 可配置的事件 + // mediasoup.observer.on("newworker", fn(worker)); const { workerSize } = config.mediasoup; - console.info("启动Worker", workerSize); + console.info("创建Mediasoup Worker数量:", workerSize); for (let index = 0; index < workerSize; index++) { - // 新建Worker const worker = await mediasoup.createWorker({ - logLevel: config.mediasoup.workerSettings.logLevel, logTags: config.mediasoup.workerSettings.logTags, + logLevel: config.mediasoup.workerSettings.logLevel, rtcMinPort: Number(config.mediasoup.workerSettings.rtcMinPort), rtcMaxPort: Number(config.mediasoup.workerSettings.rtcMaxPort), }); - // 监听Worker事件 - worker.on("died", () => { - console.warn("Worker停止服务", worker.pid); + worker.on("died", (error) => { + console.warn("Mediasoup Worker停止服务:", worker.pid, error); setTimeout(() => process.exit(1), 2000); }); worker.observer.on("close", () => { - console.warn("Worker关闭服务", worker.pid); + console.warn("Mediasoup Worker关闭服务:", worker.pid); }); + // 可配置的事件 + // worker.observer.on("newrouter", fn(router)); + // worker.observer.on("newwebrtcserver", fn(router)); // 配置WebRTC服务 const webRtcServerOptions = JSON.parse( JSON.stringify(config.mediasoup.webRtcServerOptions) @@ -53,101 +48,34 @@ async function buildMediasoupWorkers() { } const webRtcServer = await worker.createWebRtcServer(webRtcServerOptions); worker.appData.webRtcServer = webRtcServer; - // 加入Worker队列 mediasoupWorkers.push(worker); } } /** - * 启动信令服务 + * 连接信令服务 */ -async function buildSignalServer() { - const tls = { - cert: fs.readFileSync(config.https.tls.cert), - key: fs.readFileSync(config.https.tls.key), - }; - // 配置HTTPS Server - httpsServer = https.createServer(tls, (request, response) => { - response.writeHead(200); - response.end(fs.readFileSync(config.welcome)); - }); - // 配置WebSocket Server - webSocketServer = new ws.Server({ server: httpsServer }); - webSocketServer.on("connection", (session) => { - console.info("打开信令通道", session._socket.remoteAddress); - session.datetime = Date.now(); - session.authorize = false; - clients.push(session); - session.on("close", (code) => { - console.info("关闭信令通道", session._socket.remoteAddress, code); - }); - session.on("error", (error) => { - console.error("信令通道异常", session._socket.remoteAddress, error); - }); - session.on("message", (message) => { - console.debug("处理信令消息", message.toString()); - try { - signal.on(JSON.parse(message), session); - } catch (error) { - console.error("处理信令消息异常", message.toString(), error); - } - }); - }); - // 打开监听 - httpsServer.listen( - Number(config.https.listenPort), - config.https.listenIp, - () => { - console.info("信令服务启动完成"); +async function connectSignalServer() { + await signalChannel.connect( + `wss://${config.signal.host}:${config.signal.port}/websocket.signal`, + function (message) { + signal.on(message); } ); } -/** - * 定时任务 - */ -async function buildInterval() { - // 定时打印使用情况 - setInterval(async () => { - signal.usage(); - }, 300 * 1000); - // 定时清理过期无效终端 - setInterval(() => { - let failSize = 0; - let silentSize = 0; - let successSize = 0; - const datetime = Date.now(); - for (let index = 0; index < clients.length; index++) { - const session = clients[index]; - if (session.authorize) { - clients.splice(index, 1); - successSize++; - index--; - } else if (datetime - session.datetime >= 5000) { - clients.splice(index, 1); - session.close(); - failSize++; - index--; - } else { - silentSize++; - } - } - console.info("定时清理无效信令终端(无效|静默|成功|现存)", failSize, silentSize, successSize, clients.length); - }, 60 * 1000); -} - /** * 启动方法 */ async function main() { - console.info("桃之夭夭,灼灼其华。") - console.info("之子于归,宜其室家。") - console.info("开始启动", config.name); + console.log(` + 桃之夭夭,灼灼其华。 + 之子于归,宜其室家。 + `); + console.info("开始启动:", config.name); await buildMediasoupWorkers(); - await buildSignalServer(); - await buildInterval(); - console.info("启动完成", config.name); + await connectSignalServer(); + console.info("启动完成:", config.name); } -// 启动服务 main(); diff --git a/taoyao-client-media/src/Signal.js b/taoyao-client-media/src/Signal.js index 5923037..5ee5de5 100644 --- a/taoyao-client-media/src/Signal.js +++ b/taoyao-client-media/src/Signal.js @@ -1,50 +1,216 @@ const config = require("./Config"); +const process = require("child_process"); +const WebSocket = require("ws"); /** * 信令协议 */ const protocol = { // 当前索引 - index: 100000, - // 最小索引 - minIndex: 100000, + index: 0, // 最大索引 - maxIndex: 999999, + maxIndex: 1000, /** * @returns 索引 */ - buildId: function () { - if (this.index++ >= this.maxIndex) { - this.index = this.minIndex; + buildId() { + if (++this.index >= this.maxIndex) { + this.index = 0; } - return Date.now() + "" + this.index; + return Date.now() * 1000 + this.index; }, /** * 生成信令消息 * * @param {*} signal 信令标识 - * @param {*} roomId 房间标识 * @param {*} body 信令消息 * @param {*} id ID * * @returns 信令消息 */ - buildMessage: function (signal, roomId, body = {}, id) { + buildMessage(signal, body = {}, id) { const message = { header: { - v: config.version, + v: config.signal.version, id: id || this.buildId(), signal: signal, }, - body: { - roomId, - ...body, - }, + body: body, }; return message; }, }; +/** + * 信令通道 + */ +const signalChannel = { + // 通道 + channel: null, + // 地址 + address: null, + // 回调 + callback: null, + // 心跳时间 + heartbeatTime: 30 * 1000, + // 心跳定时器 + heartbeatTimer: null, + // 是否重连 + reconnection: true, + // 重连定时器 + reconnectTimer: null, + // 防止重复重连 + lockReconnect: false, + // 当前重连时间 + connectionTimeout: 5 * 1000, + // 最小重连时间 + minReconnectionDelay: 5 * 1000, + // 最大重连时间 + maxReconnectionDelay: 60 * 1000, + /** + * 心跳 + */ + heartbeat() { + const self = this; + if (self.heartbeatTimer) { + clearTimeout(self.heartbeatTimer); + } + self.heartbeatTimer = setTimeout(async function () { + if (self.channel && self.channel.readyState === WebSocket.OPEN) { + // TODO:信号强度、电池信息 + self.push( + protocol.buildMessage("client::heartbeat", { + signal: 100, + battery: 100, + charging: true, + }) + ); + self.heartbeat(); + } else { + console.warn("发送心跳失败:", self.address); + } + }, self.heartbeatTime); + }, + /** + * 连接 + * + * @param {*} address 地址 + * @param {*} callback 回调 + * @param {*} reconnection 是否重连 + * + * @returns Promise + */ + async connect(address, callback, reconnection = true) { + const self = this; + if (self.channel && self.channel.readyState === WebSocket.OPEN) { + return new Promise((resolve, reject) => { + resolve(self.channel); + }); + } + self.address = address; + self.callback = callback; + self.reconnection = reconnection; + return new Promise((resolve, reject) => { + console.debug("连接信令通道:", self.address); + self.channel = new WebSocket(self.address, { handshakeTimeout: 5000 }); + self.channel.on("open", async function () { + console.info("打开信令通道:", self.address); + // 注册终端 + // TODO:信号强度、电池信息 + self.push( + protocol.buildMessage("client::register", { + name: "桃夭媒体服务", + clientId: config.signal.clientId, + clientType: "MEDIA", + signal: 100, + battery: 100, + charging: true, + username: config.signal.username, + password: config.signal.password, + }) + ); + // 重置时间 + self.connectionTimeout = self.minReconnectionDelay; + // 开始心跳 + self.heartbeat(); + // 成功回调 + resolve(self.channel); + }); + self.channel.on("close", async function () { + console.warn("信令通道关闭:", self.address); + if (self.reconnection) { + self.reconnect(); + } + // 不要失败回调 + }); + self.channel.on("error", async function (e) { + console.error("信令通道异常:", self.address, e); + if (self.reconnection) { + self.reconnect(); + } + // 不要失败回调 + }); + self.channel.on("message", async function (data) { + try { + const content = data.toString(); + console.debug("信令通道消息:", content); + self.callback(JSON.parse(content)); + } catch (error) { + console.error("处理信令消息异常:", content, error); + } + }); + }); + }, + /** + * 重连 + */ + reconnect() { + const self = this; + if ( + self.lockReconnect || + (self.channel && self.channel.readyState === WebSocket.OPEN) + ) { + return; + } + self.lockReconnect = true; + if (self.reconnectTimer) { + clearTimeout(self.reconnectTimer); + } + // 定时重连 + self.reconnectTimer = setTimeout(function () { + console.info("信令通道重连:", self.address); + self.connect(self.address, self.callback, self.reconnection); + self.lockReconnect = false; + }, self.connectionTimeout); + self.connectionTimeout = Math.min( + self.connectionTimeout + self.minReconnectionDelay, + self.maxReconnectionDelay + ); + }, + /** + * 推送消息 + * + * @param {*} message 消息 + */ + push(message) { + try { + this.channel.send(JSON.stringify(message)); + } catch (error) { + console.error("推送消息异常:", message, error); + } + }, + /** + * 关闭通道 + */ + close() { + const self = this; + self.reconnection = false; + self.channel.close(); + clearTimeout(self.heartbeatTimer); + clearTimeout(self.reconnectTimer); + }, +}; + /** * 房间 */ @@ -63,6 +229,8 @@ class Room { audioLevelObserver = null; // 音频监控 activeSpeakerObserver = null; + // 消费者复制数量 + consumerReplicas = 0; // 通道 transports = new Map(); // 生产者 @@ -94,60 +262,83 @@ class Room { this.handleActiveSpeakerObserver(); } + /** + * 声音监控 + */ handleAudioLevelObserver() { + const self = this; // 声音 - this.audioLevelObserver.on("volumes", (volumes) => { + self.audioLevelObserver.on("volumes", (volumes) => { for (const value of volumes) { const { producer, volume } = value; - this.signal.push( - protocol.buildMessage("audio::active::speaker", this.roomId, { - peerId: producer.appData.peerId, + signalChannel.push( + protocol.buildMessage("media::audio::active::speaker", { + roomId: self.roomId, + clientId: producer.clientId, volume: volume, }) ); } }); // 静音 - this.audioLevelObserver.on("silence", () => { - this.signal.push( - protocol.buildMessage("audio::active::speaker", this.roomId, { - peerId: null, + self.audioLevelObserver.on("silence", () => { + signalChannel.push( + protocol.buildMessage("media::audio::active::speaker", { + roomId: self.roomId, }) ); }); } + /** + * 说话监控 + */ handleActiveSpeakerObserver() { - this.activeSpeakerObserver.on("dominantspeaker", (dominantSpeaker) => { - console.debug("dominantspeaker", dominantSpeaker.producer.id); + const self = this; + self.activeSpeakerObserver.on("dominantspeaker", (dominantSpeaker) => { + console.debug( + "dominantspeaker:", + dominantSpeaker.producer.id, + dominantSpeaker.producer.clientId + ); }); } + /** + * 使用情况 + */ usage() { - console.info("房间标识", this.roomId); - console.info("房间通道数量", this.transports.size); - console.info("房间生产者数量", this.producers.size); - console.info("房间消费者数量", this.consumers.size); - console.info("房间数据生产者数量", this.dataProducers.size); - console.info("房间数据消费者数量", this.dataConsumers.size); + console.info("房间标识:", this.roomId); + console.info("房间通道数量:", this.transports.size); + console.info("房间生产者数量:", this.producers.size); + console.info("房间消费者数量:", this.consumers.size); + console.info("房间数据生产者数量:", this.dataProducers.size); + console.info("房间数据消费者数量:", this.dataConsumers.size); } + /** + * 关闭资源 + */ close() { - this.close = true; - if (this.mediasoupRouter) { - this.mediasoupRouter.close(); + const self = this; + if (self.close) { + return; + } + self.close = true; + if (self.mediasoupRouter) { + self.mediasoupRouter.close(); } } } /** - * 信令 + * 信令服务 */ class Signal { // 房间列表 rooms = new Map(); - // 信令终端列表 - clients = []; + // 回调事件 + callbackMapping = new Map(); // Worker列表 mediasoupWorkers = []; // Worker索引 @@ -155,51 +346,55 @@ class Signal { constructor(mediasoupWorkers) { this.mediasoupWorkers = mediasoupWorkers; + // 定时打印使用情况 + setInterval(async () => { + this.usage(); + }, 300 * 1000); } /** - * 处理事件 + * 处理信令消息 * * @param {*} message 消息 - * @param {*} session websocket */ - on(message, session) { - // 授权验证 - if (!session.authorize) { - if ( - message?.header?.signal === "media::register" && - message?.body?.username === config.https.username && - message?.body?.password === config.https.password - ) { - console.debug("授权成功", session._socket.remoteAddress); - this.clients.push(session); - session.authorize = true; - message.code = "0000"; - message.message = "授权成功"; - message.body.username = undefined; - message.body.password = undefined; - } else { - console.warn("授权失败", session._socket.remoteAddress); - message.code = "3401"; - message.message = "授权失败"; + on(message) { + // 请求回调 + if (this.callbackMapping.has(message.header.id)) { + try { + this.callbackMapping.get(message.header.id)(message); + } finally { + this.callbackMapping.delete(message.header.id); } - this.push(message, session); return; } - // 处理信令 const body = message.body; switch (message.header.signal) { + case "client::reboot": + this.clientReboot(message, body); + break; + case "client::shutdown": + this.clientShutdown(message, body); + break; + case "media::ice::restart": + this.mediaIceRestart(message, body); + break; + case "media::consume": + this.mediaConsume(message, body); + break; + case "media::produce": + this.mediaProduce(message, body); + break; case "media::router::rtp::capabilities": - this.mediaRouterRtpCapabilities(session, message, body); + this.mediaRouterRtpCapabilities(message, body); break; case "media::transport::webrtc::connect": - this.mediaTransportWebrtcConnect(session, message, body); + this.mediaTransportWebrtcConnect(message, body); break; case "media::transport::webrtc::create": - this.mediaTransportWebrtcCreate(session, message, body); + this.mediaTransportWebrtcCreate(message, body); break; case "room::create": - this.roomCreate(session, message, body); + this.roomCreate(message, body); break; } } @@ -208,23 +403,41 @@ class Signal { * 通知信令 * * @param {*} message 消息 - * @param {*} session 信令通道 */ - push(message, session) { - if (session) { + push(message) { + signalChannel.push(message); + } + + /** + * 同步请求 + * + * @param {*} message 消息 + * + * @returns Promise + */ + async request(message) { + const self = this; + return new Promise((resolve, reject) => { + let done = false; + // 注册回调 + self.callbackMapping.set(message.header.id, (response) => { + resolve(response); + done = true; + }); + // 发送消息 try { - session.send(JSON.stringify(message)); + self.channel.send(JSON.stringify(message)); } catch (error) { - console.error( - "通知信令失败", - session._socket.remoteAddress, - message, - error - ); + console.error("请求消息异常:", message, error); } - } else { - this.clients.forEach((session) => this.push(message, session)); - } + // 设置超时 + setTimeout(() => { + if (!done) { + self.callbackMapping.delete(message.header.id); + reject("请求超时", message); + } + }, 5000); + }); } /** @@ -233,11 +446,11 @@ class Signal { async usage() { for (const worker of this.mediasoupWorkers) { const usage = await worker.getResourceUsage(); - console.info("Worker使用情况", worker.pid, usage); + console.info("Worker使用情况:", worker.pid, usage); } - console.info("路由数量", this.mediasoupWorkers.length); - console.info("房间数量", this.rooms.size); - Array.from(this.rooms.values()).forEach(room => room.usage()); + console.info("路由数量:", this.mediasoupWorkers.length); + console.info("房间数量:", this.rooms.size); + Array.from(this.rooms.values()).forEach((room) => room.usage()); } /** @@ -252,39 +465,286 @@ class Signal { } /** - * 路由RTP能力信令 + * 重启终端信令 * - * @param {*} session 信令通道 * @param {*} message 消息 * @param {*} body 消息主体 */ - mediaRouterRtpCapabilities(session, message, body) { - const { roomId } = body; - const room = this.rooms.get(roomId); - message.body = room.mediasoupRouter.rtpCapabilities; - this.push(message, session); - } - - async mediaTransportWebrtcConnect(session, message, body) { - const { roomId, transportId, dtlsParameters } = body; - const room = this.rooms.get(roomId); - const transport = room.transports.get(transportId); - if (!transport) { - throw new Error(`transport with id "${transportId}" not found`); - } - await transport.connect({ dtlsParameters }); - message.body = { roomId }; - this.push(message, session); + clientReboot(message, body) { + process.exec( + `pm2 restart ${config.signal.clientId}`, + function (error, stdout, stderr) { + console.info("重启媒体服务:", error, stdout, stderr); + } + ); + // this.push(message); } /** - * @param {*} session 信令通道 + * 关闭终端信令 + * * @param {*} message 消息 * @param {*} body 消息主体 */ - async mediaTransportWebrtcCreate(session, message, body) { + clientShutdown(message, body) { + process.exec( + `pm2 stop ${config.signal.clientId}`, + function (error, stdout, stderr) { + console.info("关闭媒体服务:", error, stdout, stderr); + } + ); + // this.push(message); + } + + /** + * 媒体重启ICE信令 + * + * @param {*} message 消息 + * @param {*} body 消息主体 + */ + async mediaIceRestart(message, body) { + const { roomId, transportId } = body; + const room = this.rooms.get(roomId); + const transport = room.transports.get(transportId); + const iceParameters = await transport.restartIce(); + message.body.iceParameters = iceParameters; + this.push(message); + } + + async mediaProduce(message, body) { const self = this; - const { roomId, forceTcp, producing, consuming, sctpCapabilities } = body; + const { + kind, + roomId, + clientId, + streamId, + appData, + transportId, + rtpParameters, + } = body; + const room = self.rooms.get(roomId); + const transport = room.transports.get(transportId); + const producer = await transport.produce({ + kind, + appData, + rtpParameters, + // keyFrameRequestDelay: 5000 + }); + producer.clientId = clientId; + producer.streamId = streamId; + room.producers.set(producer.id, producer); + // 打分 + producer.on("score", (score) => { + self.push( + protocol.buildMessage("media::producer::score", { + roomId: roomId, + producerId: producer.id, + score, + }) + ); + }); + + producer.on("videoorientationchange", (videoOrientation) => { + logger.debug( + 'producer "videoorientationchange" event [producerId:%s, videoOrientation:%o]', + producer.id, + videoOrientation + ); + }); + + producer.on("trace", (trace) => { + logger.debug( + 'producer "trace" event [producerId:%s, trace.type:%s, trace:%o]', + producer.id, + trace.type, + trace + ); + }); + message.body = { kind: kind, producerId: producer.id }; + this.push(message); + if (producer.kind === "audio") { + room.audioLevelObserver + .addProducer({ producerId: producer.id }) + .catch(() => {}); + room.activeSpeakerObserver + .addProducer({ producerId: producer.id }) + .catch(() => {}); + } + } + + async mediaConsume(message, body) { + const { roomId, producerId, transportId, rtpCapabilities } = body; + const room = this.rooms.get(roomId); + const producer = room.producers.get(producerId); + const transport = room.transports.get(transportId); + if ( + !room || + !producer || + !transport || + !rtpCapabilities || + !room.mediasoupRouter.canConsume({ + producerId: producerId, + rtpCapabilities: rtpCapabilities, + }) + ) { + console.warn( + "不能消费媒体:", + roomId, + producerId, + transportId, + rtpCapabilities + ); + return; + } + const promises = []; + const consumerCount = 1 + room.consumerReplicas; + for (let i = 0; i < consumerCount; i++) { + promises.push( + (async () => { + let consumer; + try { + consumer = await transport.consume({ + producerId: producerId, + rtpCapabilities: rtpCapabilities, + // 暂停 + paused: true, + }); + } catch (error) { + console.error( + "创建消费者异常:", + roomId, + producerId, + transportId, + rtpCapabilities, + error + ); + return; + } + room.consumers.set(consumer.id, consumer); + consumer.on("transportclose", () => { + room.consumers.delete(consumer.id); + }); + consumer.on("producerclose", () => { + room.consumers.delete(consumer.id); + this.push( + protocol.buildMessage("media::consumer::close", { + consumerId: consumer.id, + }) + ); + }); + consumer.on("producerpause", () => { + this.push( + protocol.buildMessage("media::consumer::pause", { + consumerId: consumer.id, + }) + ); + }); + consumer.on("producerresume", () => { + this.push( + protocol.buildMessage("media::consumer::resume", { + consumerId: consumer.id, + }) + ); + }); + consumer.on("score", (score) => { + this.push( + protocol.buildMessage("media::consumer::score", { + consumerId: consumer.id, + score, + }) + ); + }); + consumer.on("layerschange", (layers) => { + this.push( + protocol.buildMessage("media::consumer::layers::change", { + consumerId: consumer.id, + spatialLayer: layers ? layers.spatialLayer : null, + temporalLayer: layers ? layers.temporalLayer : null, + }) + ); + }); + consumer.on("trace", (trace) => { + logger.debug( + 'consumer "trace" event [producerId:%s, trace.type:%s, trace:%o]', + consumer.id, + trace.type, + trace + ); + }); + // TODO:改为同步 + this.push(protocol.buildMessage("media::consume", { + //await this.request("media::consume", { + kind: consumer.kind, + type: consumer.type, + roomId: roomId, + producerId: producerId, + consumerId: consumer.id, + rtpParameters: consumer.rtpParameters, + appData: producer.appData, + producerPaused: consumer.producerPaused, + })); + await consumer.resume(); + this.push( + protocol.buildMessage("media::consumer::score", { + consumerId: consumer.id, + score: consumer.score, + }) + ); + })() + ); + } + + try { + await Promise.all(promises); + } catch (error) { + console.warn("_createConsumer() | failed:%o", error); + } + } + + /** + * 路由RTP能力信令 + * + * @param {*} message 消息 + * @param {*} body 消息主体 + */ + mediaRouterRtpCapabilities(message, body) { + const { roomId } = body; + const room = this.rooms.get(roomId); + message.body.rtpCapabilities = room.mediasoupRouter.rtpCapabilities; + this.push(message); + } + + /** + * 连接WebRTC通道信令 + * + * @param {*} message 消息 + * @param {*} body 消息主体 + */ + async mediaTransportWebrtcConnect(message, body) { + const { roomId, transportId, dtlsParameters } = body; + const room = this.rooms.get(roomId); + const transport = room.transports.get(transportId); + await transport.connect({ dtlsParameters }); + message.body = { roomId: roomId, transportId: transport.id }; + this.push(message); + } + + /** + * 创建WebRTC通道信令 + * + * @param {*} message 消息 + * @param {*} body 消息主体 + */ + async mediaTransportWebrtcCreate(message, body) { + const self = this; + const { + roomId, + clientId, + forceTcp, + producing, + consuming, + sctpCapabilities, + } = body; const webRtcTransportOptions = { ...config.mediasoup.webRtcTransportOptions, appData: { producing, consuming }, @@ -300,29 +760,41 @@ class Signal { ...webRtcTransportOptions, webRtcServer: room.webRtcServer, }); + transport.clientId = clientId; + transport.on("icestatechange", (iceState) => { + console.debug( + "WebRtcTransport icestatechange event:", + iceState, + transport.id + ); + }); transport.on("dtlsstatechange", (dtlsState) => { console.debug( - 'WebRtcTransport dtlsstatechange event', - dtlsState + "WebRtcTransport dtlsstatechange event:", + dtlsState, + transport.id ); }); transport.on("sctpstatechange", (sctpState) => { console.debug( - 'WebRtcTransport sctpstatechange event', - sctpState + "WebRtcTransport sctpstatechange event:", + sctpState, + transport.id ); }); - // await transport.enableTraceEvent([ 'probation', 'bwe' ]); await transport.enableTraceEvent(["bwe"]); transport.on("trace", (trace) => { - console.debug( - 'transport trace event', - trace, - trace.type, - transport.id, - ); + console.debug("transport trace event:", trace, trace.type, transport.id); }); - // Store the WebRtcTransport into the protoo Peer data Object. + // 可配置的事件 + // transport.on("routerclose", fn()); + // transport.on("listenserverclose", fn()); + // transport.observer.on("close", fn()); + // transport.observer.on("newproducer", fn(producer)); + // transport.observer.on("newconsumer", fn(consumer)); + // transport.observer.on("newdataproducer", fn(dataProducer)); + // transport.observer.on("newdataconsumer", fn(dataConsumer)); + // transport.observer.on("trace", fn(trace)); room.transports.set(transport.id, transport); message.body = { transportId: transport.id, @@ -331,10 +803,7 @@ class Signal { dtlsParameters: transport.dtlsParameters, sctpParameters: transport.sctpParameters, }; - self.push( - message, - session - ); + self.push(message); const { maxIncomingBitrate } = config.mediasoup.webRtcTransportOptions; // If set, apply max incoming bitrate limit. if (maxIncomingBitrate) { @@ -347,28 +816,27 @@ class Signal { /** * 创建房间信令 * - * @param {*} session 信令通道 * @param {*} message 消息 * @param {*} body 消息主体 * * @returns 房间 */ - async roomCreate(session, message, body) { + async roomCreate(message, body) { const roomId = body.roomId; let room = this.rooms.get(roomId); if (room) { - this.push(message, session); + this.push(message); return room; } const mediasoupWorker = this.nextMediasoupWorker(); const { mediaCodecs } = config.mediasoup.routerOptions; const mediasoupRouter = await mediasoupWorker.createRouter({ mediaCodecs }); - mediasoupRouter.on("workerclose", () => { - // TODO:通知房间关闭 - }); mediasoupRouter.observer.on("close", () => { // TODO:通知房间关闭 }); + // 可配置的事件 + // mediasoupRouter.on("workerclose", () => {}); + // mediasoupRouter.observer.on("newtransport", fn(transport)); // TODO:下面两个监控改为配置启用 const audioLevelObserver = await mediasoupRouter.createAudioLevelObserver({ maxEntries: 1, @@ -388,9 +856,9 @@ class Signal { }); this.rooms.set(roomId, room); console.info("创建房间", roomId); - this.push(message, session); + this.push(message); return room; } } -module.exports = Signal; +module.exports = { Signal, signalChannel }; diff --git a/taoyao-client-media/src/certs/privateKey.pem b/taoyao-client-media/src/certs/privateKey.pem deleted file mode 100644 index b326ca4..0000000 --- a/taoyao-client-media/src/certs/privateKey.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCfmqQa2T/dGQIk -cXYiRE47csmhtOEmDL9e1uncyFOYaDLopqma+z0OWV71HrT9m5lEAbK3HZy56CWL -HNlFmnIztT84jTc11b3996cd6G1/5nffRj8D3FcSps9qVsRwAaEnKE6tGrCJhjWB -+/qKIZDP3MmGOkf1guQMTPeZBOEzo+JmL2MOwaLk8dXbb4KHbrye31UnvoXoANs3 -1IbdAE3gRMDc40yBoVHeWOQ0h4c/wg1+rMhYPaUtgFkyqFr7zX1YvTa7BwZE4oVf -l7f5k+FRWO8OKn++kHIlE9H3pjdaGKWIuYFRYCxNC9IfGazhPSfYHVgg0q/8UUKg -jFqgbjIJAgMBAAECggEANs6ateGOjbUtyCfyQjgkiUOUu+PqQO+1s7KnYjqkgjyd -5sh8i4zk3Y2RDyl5S3FoQzM2FK2liS2P3uKMNdugheMij5/mqqT4dkLZ72pGV9pj -pZdwwjmi6PPBXCnpkPDuTw0HX2g/4SnmK/nEgjSejtKpnV9cIJHPD+5KRBCp6No+ -JLgVFdhCCMEmyzTOU9ASxgRuw0sxGmPsdg3ZewUmoDb94mGDDot500rCB+wnYHue -ACegbaalrTWhY2DzYyNCfnR+F/mshIeqDjMVLsdPoj8MahdQbIoFcyNF+ts3pFVl -MXUUuO3bhZtrSVIT+4r07u82XoXaBufyPzK6Mo1vNQKBgQC9uebhZ1jCyYgGro5o -xuPuW+B5tHKGtlElJ0e7D/XjDvPWj3eGEkVCD5BH2L3qG9oqpKxX+hpmjc5ey21Q -zcnAGG2gC9o+uGQxxjix8sbZ0HEr2J6EdRlKzIZ/N2EnTmU+K8WWWI4g2PEqsId9 -yt39EwU/D81bc0b5pcMGJTMk1wKBgQDXWxhywNFtPVreCeerXvvHLIQEtiZUMdhm -8zWtqHNhdojYoeaFPifVcajBo41Jl9qSVCot8cmdhIzmPwbRk1Ob7MtBC8t7OtzT -UmwT9nuxC5iSaGLJNBMtP3M0TzV7+qvvg+Az13kYtje9475LMEYdBLnCb4Mz1Y/R -QOR0mhWkHwKBgBxoepajl9nKvVBq0K4Fodlt7mWqzD85i1rpz8bFtAaklYQ6BSaR -E8e5dtwbKwyj0P3znE6sB0n1z8HH6f1gYuYdgkSloa8kgvQk/xY+COJSYK+1Br9E -nV3i0/y2eRiel3BAs5w4dEec1DeVKSR/vM+JCo8PuasIzsbQuCvyY/8PAoGBALWH -kT0xsZcej9j4inMXNq62pHYAQKDZ/2sQed/vTYsLSuEo39LTCOrPywum3LL7MQAF -uCRQWr3PfKGc4ReJ04FtAgvLcHNos7niET5ml+8uMia/nP2zSrLqeCbQ2emu7H2S -MUwhxm8BMk17iu2APKm7UQZHz1XDIF6oD6sGM1XLAoGALFpdCO486AbHYts2NHey -vQ39u3WDlrgzIM6hs8BI0FEZIdtuNWa/wpZYPaiXUvwTPsfQA1eCqXXuQGzH0fFn -g1M8RxsZ8XNjfQVpqSceZp+qFkTrR8zrbbQiZwBUm9WBdKfryMZHLhFraGLShFdM -ONu5qg3tXgMfFpNcMyiGgeA= ------END PRIVATE KEY----- \ No newline at end of file diff --git a/taoyao-client-media/src/certs/publicKey.pem b/taoyao-client-media/src/certs/publicKey.pem deleted file mode 100644 index 1099efa..0000000 --- a/taoyao-client-media/src/certs/publicKey.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDnzCCAoegAwIBAgIJAIKYVI9RPbj+MA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV -BAYTAkNOMQswCQYDVQQIEwJHRDELMAkGA1UEBxMCR1oxDzANBgNVBAoTBnRhb3lh -bzEPMA0GA1UECxMGYWNnaXN0MRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMjIxMTA5 -MDEyMTM3WhcNMzIxMTA2MDEyMTM3WjBdMQswCQYDVQQGEwJDTjELMAkGA1UECBMC -R0QxCzAJBgNVBAcTAkdaMQ8wDQYDVQQKEwZ0YW95YW8xDzANBgNVBAsTBmFjZ2lz -dDESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAn5qkGtk/3RkCJHF2IkROO3LJobThJgy/Xtbp3MhTmGgy6Kapmvs9Dlle -9R60/ZuZRAGytx2cueglixzZRZpyM7U/OI03NdW9/fenHehtf+Z330Y/A9xXEqbP -albEcAGhJyhOrRqwiYY1gfv6iiGQz9zJhjpH9YLkDEz3mQThM6PiZi9jDsGi5PHV -22+Ch268nt9VJ76F6ADbN9SG3QBN4ETA3ONMgaFR3ljkNIeHP8INfqzIWD2lLYBZ -Mqha+819WL02uwcGROKFX5e3+ZPhUVjvDip/vpByJRPR96Y3WhiliLmBUWAsTQvS -Hxms4T0n2B1YINKv/FFCoIxaoG4yCQIDAQABo2IwYDAdBgNVHQ4EFgQUPpT59FzS -UUzHsxrKeGOQ/YeaqpswDgYDVR0PAQH/BAQDAgWgMBoGA1UdEQQTMBGCCWxvY2Fs -aG9zdIcEfwAAATATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOC -AQEAKsoYmBr9EmYev6sWVet8x+/YDZgXGZolZff3agPT+uFL/6mtyAqnFD/ncPOh -n206l4RimSChVlEVx3pE4r5sBiLzPaX/dcCRoZNxEQMtjfYCk+4iRfkxhIvpqLzf -ZsEGbJCh9JodG2xkYNViPF2AqR8OchEMRttYQa2dDkk2oDVMg0bmqgZgSD7vEjdk -ovBhEIQ1Rhgv0yi9IT+kXYa+nTpc+/9m/GmmejtaFaVdpj+WuNqPf/WyzR+3JQWZ -Y/O7ESF7tUcw0HSxNv/pk6Z13RQClUo6bPHzPF4JJw1tAIbkuyKZ6ZpErQePUEpk -dVZPy7rDD3N/BI//0vBZmy0v1w== ------END CERTIFICATE----- \ No newline at end of file diff --git a/taoyao-client-media/src/index.html b/taoyao-client-media/src/index.html deleted file mode 100644 index 0fbe2cb..0000000 --- a/taoyao-client-media/src/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - 桃夭媒体服务 - - - -

taoyao-client-media

-

acgist

- - \ No newline at end of file diff --git a/taoyao-client-web/src/App.vue b/taoyao-client-web/src/App.vue index 0aff1df..6b9bbe4 100644 --- a/taoyao-client-web/src/App.vue +++ b/taoyao-client-web/src/App.vue @@ -1,6 +1,12 @@ @@ -15,11 +21,14 @@ export default { return { taoyao: {}, roomVisible: false, - signalVisible: true, + signalVisible: false, }; }, mounted() { - console.info("桃夭终端开始启动"); + console.info(` + 中庭地白树栖鸦,冷露无声湿桂花。 + 今夜月明人尽望,不知秋思落谁家。 + `); }, methods: { buildSignal(config) { @@ -28,19 +37,24 @@ export default { self.signalVisible = false; self.taoyao.buildSignal(self.callback); }, - buildMedia(roomId) { + produceMedia() { let self = this; - self.taoyao.buildMedia(roomId); + self.taoyao.produceMedia(); }, /** * 信令回调 * * @param {*} data 消息 + * @param {*} error 异常 * * @return 是否继续执行 */ - callback(data) { + callback(data, error) { let self = this; + if(data.header.signal === "platform::error") { + console.error("发生异常:", data, error); + return false; + } switch (data.header.signal) { case "client::config": self.roomVisible = true; diff --git a/taoyao-client-web/src/components/Config.js b/taoyao-client-web/src/components/Config.js index 8fa3ce0..437a453 100644 --- a/taoyao-client-web/src/components/Config.js +++ b/taoyao-client-web/src/components/Config.js @@ -3,20 +3,17 @@ */ const protocol = { // 当前索引 - index: 100000, - // 最小索引 - minIndex: 100000, + index: 0, // 最大索引 - maxIndex: 999999, + maxIndex: 1000, /** * @returns 索引 */ buildId() { - const self = this; - if (self.index++ >= self.maxIndex) { - self.index = self.minIndex; + if (++this.index >= this.maxIndex) { + this.index = 0; } - return Date.now() + "" + self.index; + return Date.now() * 1000 + this.index; }, /** * @param {*} signal 信令标识 diff --git a/taoyao-client-web/src/components/RemoteClient.vue b/taoyao-client-web/src/components/RemoteClient.vue index 9bc74de..d8b1ce5 100644 --- a/taoyao-client-web/src/components/RemoteClient.vue +++ b/taoyao-client-web/src/components/RemoteClient.vue @@ -1,2 +1,14 @@ - + + + \ No newline at end of file diff --git a/taoyao-client-web/src/components/SettingRoom.vue b/taoyao-client-web/src/components/SettingRoom.vue index c0a34f9..000123b 100644 --- a/taoyao-client-web/src/components/SettingRoom.vue +++ b/taoyao-client-web/src/components/SettingRoom.vue @@ -16,7 +16,7 @@ @@ -26,10 +26,10 @@ @@ -62,6 +62,7 @@ export default { return { room: {}, rooms: [], + medias: [], activeName: "enter", localVisible: false, }; @@ -77,28 +78,26 @@ export default { }, methods: { async init() { - const response = await this.taoyao.request( + const roomResponse = await this.taoyao.request( protocol.buildMessage("room::list") ); - this.rooms = response.body; + this.rooms = roomResponse.body; + const mediaResponse = await this.taoyao.request( + protocol.buildMessage("client::list", {clientType:"MEDIA"}) + ); + this.medias = mediaResponse.body; }, async enter() { - await this.taoyao.request( - protocol.buildMessage("room::enter", { - ...this.room, - }) - ); + await this.taoyao.enter(this.room.roomId); this.localVisible = false; - this.$emit("buildMedia", this.room.roomId); + this.$emit("produceMedia"); }, async create() { - const response = await this.taoyao.request( - protocol.buildMessage("room::create", { - ...this.room, - }) - ); + const room = await this.taoyao.create(this.room); + this.room.roomId = room.roomId; + await this.enter(room.roomId); this.localVisible = false; - this.$emit("buildMedia", response.body.roomId); + this.$emit("produceMedia"); }, }, }; diff --git a/taoyao-client-web/src/components/SettingSignal.vue b/taoyao-client-web/src/components/SettingSignal.vue index 95688d3..6c3747b 100644 --- a/taoyao-client-web/src/components/SettingSignal.vue +++ b/taoyao-client-web/src/components/SettingSignal.vue @@ -42,11 +42,11 @@ export default { username: "taoyao", password: "taoyao", }, - localVisible: true, + localVisible: false, }; }, props: { - signalVisible: true, + signalVisible: false, }, watch: { signalVisible() { diff --git a/taoyao-client-web/src/components/Taoyao.js b/taoyao-client-web/src/components/Taoyao.js index 9d5d317..81efbf7 100644 --- a/taoyao-client-web/src/components/Taoyao.js +++ b/taoyao-client-web/src/components/Taoyao.js @@ -51,6 +51,7 @@ const signalChannel = { self.heartbeatTimer = setTimeout(async function () { if (self.channel && self.channel.readyState === WebSocket.OPEN) { const battery = await navigator.getBattery(); + // TODO:信号强度 self.push( protocol.buildMessage("client::heartbeat", { signal: 100, @@ -60,7 +61,7 @@ const signalChannel = { ); self.heartbeat(); } else { - console.warn("发送心跳失败", self.channel); + console.warn("发送心跳失败:", self.address); } }, self.heartbeatTime); }, @@ -76,22 +77,26 @@ const signalChannel = { async connect(address, callback, reconnection = true) { const self = this; if (self.channel && self.channel.readyState === WebSocket.OPEN) { - return; + return new Promise((resolve, reject) => { + resolve(self.channel); + }); } self.address = address; self.callback = callback; self.reconnection = reconnection; return new Promise((resolve, reject) => { - console.debug("连接信令通道", address); - self.channel = new WebSocket(address); - self.channel.onopen = async function (e) { - console.debug("打开信令通道", e); + console.debug("连接信令通道:", self.address); + self.channel = new WebSocket(self.address); + self.channel.onopen = async function () { + console.debug("打开信令通道:", self.address); // 注册终端 + // TODO:信号强度 const battery = await navigator.getBattery(); self.push( protocol.buildMessage("client::register", { - ip: "localhost", + name: "桃夭Web", clientId: self.taoyao.clientId, + clientType: "WEB", signal: 100, battery: battery.level * 100, charging: battery.charging, @@ -104,21 +109,21 @@ const signalChannel = { // 开始心跳 self.heartbeat(); // 成功回调 - resolve(e); + resolve(self.channel); }; - self.channel.onclose = function (e) { - console.error("信令通道关闭", self.channel, e); + self.channel.onclose = async function () { + console.warn("信令通道关闭:", self.channel); if (self.reconnection) { self.reconnect(); } - reject(e); + // 不要失败回调 }; - self.channel.onerror = function (e) { - console.error("信令通道异常", self.channel, e); + self.channel.onerror = async function (e) { + console.error("信令通道异常:", self.channel, e); if (self.reconnection) { self.reconnect(); } - reject(e); + // 不要失败回调 }; /** * 回调策略: @@ -127,7 +132,7 @@ const signalChannel = { * 3. 如果前面所有回调没有返回true执行默认回调。 */ self.channel.onmessage = function (e) { - console.debug("信令通道消息", e.data); + console.debug("信令通道消息:", e.data); let done = false; const message = JSON.parse(e.data); // 请求回调 @@ -154,22 +159,20 @@ const signalChannel = { */ reconnect() { const self = this; - if (self.lockReconnect) { + if ( + self.lockReconnect || + (self.channel && self.channel.readyState === WebSocket.OPEN) + ) { return; } self.lockReconnect = true; - // 关闭旧的通道 - if (self.channel && self.channel.readyState === WebSocket.OPEN) { - self.channel.close(); - self.channel = null; - } if (self.reconnectTimer) { clearTimeout(self.reconnectTimer); } - // 打开定时重连 + // 定时重连 self.reconnectTimer = setTimeout(function () { - console.info("信令通道重连", self.address); - self.connect(self.address, self.callback, true); + console.info("信令通道重连:", self.address); + self.connect(self.address, self.callback, self.reconnection); self.lockReconnect = false; }, self.connectionTimeout); self.connectionTimeout = Math.min( @@ -190,7 +193,11 @@ const signalChannel = { self.callbackMapping.set(message.header.id, callback); } // 发送消息 - self.channel.send(JSON.stringify(message)); + try { + self.channel.send(JSON.stringify(message)); + } catch (error) { + console.error("推送消息异常:", message, error); + } }, /** * 同步请求 @@ -203,17 +210,23 @@ const signalChannel = { const self = this; return new Promise((resolve, reject) => { let done = false; - // 设置回调 + // 注册回调 self.callbackMapping.set(message.header.id, (response) => { resolve(response); done = true; + // 返回true不要继续执行回调 return true; }); - // 发送请求 - self.channel.send(JSON.stringify(message)); + // 发送消息 + try { + self.channel.send(JSON.stringify(message)); + } catch (error) { + console.error("请求消息异常:", message, error); + } // 设置超时 setTimeout(() => { if (!done) { + self.callbackMapping.delete(message.header.id); reject("请求超时", message); } }, 5000); @@ -223,7 +236,7 @@ const signalChannel = { * 关闭通道 */ close() { - let self = this; + const self = this; self.reconnection = false; self.channel.close(); clearTimeout(self.heartbeatTimer); @@ -244,11 +257,14 @@ const signalChannel = { case "client::reboot": self.defaultClientReboot(message); break; + case "client::shutdown": + self.defaultClientShutdown(message); + break; case "client::register": console.info("终端注册成功"); break; case "platform::error": - console.error("信令异常", message); + self.callbackError(message); break; } }, @@ -262,13 +278,11 @@ const signalChannel = { self.taoyao.audio = { ...defaultAudioConfig, ...message.body.media.audio }; self.taoyao.video = { ...defaultVideoConfig, ...message.body.media.video }; self.taoyao.webrtc = message.body.webrtc; - self.taoyao.mediaServerList = message.body.media.mediaServerList; console.debug( "终端配置", self.taoyao.audio, self.taoyao.video, - self.taoyao.webrtc, - self.taoyao.mediaServerList + self.taoyao.webrtc ); }, /** @@ -280,6 +294,15 @@ const signalChannel = { console.info("重启终端"); location.reload(); }, + /** + * 终端重启默认回调 + * + * @param {*} message 消息 + */ + defaultClientShutdown(message) { + console.info("关闭终端"); + window.close(); + }, }; /** @@ -298,14 +321,14 @@ class Taoyao { username; // 信令密码 password; + // 回调事件 + callback; // 音频媒体配置 audio; // 视频媒体配置 video; // WebRTC配置 webrtc; - // 媒体服务配置 - mediaServerList; // 发送信令 push; // 请求信令 @@ -382,8 +405,9 @@ class Taoyao { */ async buildSignal(callback) { const self = this; - signalChannel.taoyao = self; + self.callback = callback; self.signalChannel = signalChannel; + signalChannel.taoyao = self; // 不能直接this.push = this.signalChannel.push这样导致this对象错误 self.push = function (data, pushCallback) { self.signalChannel.push(data, pushCallback); @@ -397,27 +421,59 @@ class Taoyao { ); } /** - * 打开媒体通道 - * TODO:共享 navigator.mediaDevices.getDisplayMedia(); + * 错误回调 */ - async buildMedia(roomId) { - let self = this; - // 释放资源 - self.closeMedia(); - if (roomId) { - self.roomId = roomId; + callbackError(message, error) { + const self = this; + if (!self.callback) { + console.warn("没有注册回调:", message, error); } + // 错误回调 + self.callback( + protocol.buildMessage("platform::error", { message }, -9999), + error + ); + } + /** + * 创建房间 + */ + async create(room) { + const self = this; + const response = await self.request( + protocol.buildMessage("room::create", room) + ); + return response.body; + } + async enter(roomId) { + const self = this; + if (!roomId) { + this.callbackError("无效房间"); + return; + } + self.roomId = roomId; self.mediasoupDevice = new mediasoupClient.Device(); const response = await self.request( protocol.buildMessage("media::router::rtp::capabilities", { - roomId: roomId, + roomId: self.roomId, + }) + ); + const routerRtpCapabilities = response.body.rtpCapabilities; + await self.mediasoupDevice.load({ routerRtpCapabilities }); + await self.request( + protocol.buildMessage("room::enter", { + roomId: roomId, + rtpCapabilities: self.consume + ? self.mediasoupDevice.rtpCapabilities + : undefined, + sctpCapabilities: + self.consume && self.useDataChannel + ? self.mediasoupDevice.sctpCapabilities + : undefined, }) ); - const routerRtpCapabilities = response.body; - self.mediasoupDevice.load({ routerRtpCapabilities }); - self.produceMedia(); } /** + * TODO:共享 navigator.mediaDevices.getDisplayMedia(); * 生产媒体 * TODO:验证API试试修改媒体 * audioTrack.getSettings @@ -426,7 +482,14 @@ class Taoyao { */ async produceMedia() { const self = this; + if (!self.roomId) { + this.callbackError("无效房间"); + return; + } + // 检查设备 self.checkDevice(); + // 释放资源 + self.closeMedia(); // TODO:暂时不知道为什么? { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); @@ -491,15 +554,16 @@ class Taoyao { "produce", async ({ kind, appData, rtpParameters }, callback, errback) => { try { - const { id } = await self.request( + const { producerId } = await self.request( protocol.buildMessage("media::produce", { kind, + roomId: self.roomId, appData, transportId: self.sendTransport.id, rtpParameters, }) ); - callback({ id }); + callback({ id: producerId }); } catch (error) { errback(error); } @@ -591,10 +655,11 @@ class Taoyao { } /** * 生产音频 + * TODO:重复点击 */ async produceAudio() { const self = this; - if (this.produceAudio && this.mediasoupDevice.canProduce("audio")) { + if (self.audioProduce && self.mediasoupDevice.canProduce("audio")) { if (this.audioProducer) { return; } @@ -631,13 +696,13 @@ class Taoyao { this.closeAudioProducer().catch(() => {}); }); } catch (error) { - console.error("打开麦克风异常", error); + self.callbackError("麦克风打开异常", error); if (track) { track.stop(); } } } else { - console.warn("音频打开失败"); + self.callbackError("麦克风打开失败"); } } async closeAudioProducer() { @@ -689,6 +754,7 @@ class Taoyao { /** * 生产视频 + * TODO:重复点击 */ async produceVideo() {} /** @@ -719,15 +785,40 @@ class Taoyao { } }); if (!audioEnabled && self.audioProduce) { - throw new Error("没有音频媒体设备"); + self.callbackError("没有音频媒体设备"); } if (!videoEnabled && self.videoProduce) { - throw new Error("没有视频媒体设备"); + self.callbackError("没有视频媒体设备"); } } else { - throw new Error("没有媒体设备"); + self.callbackError("没有媒体设备"); } } + + async restartIce() { + const self = this; + try { + if (self.sendTransport) { + const response = await self.request("media::ice::restart", { + roomId: self.roomId, + transportId: self.sendTransport.id, + }); + const iceParameters = response.data.iceParameters; + await self.sendTransport.restartIce({ iceParameters }); + } + if (self.recvTransport) { + const response = await self.request("media::ice::restart", { + roomId: self.roomId, + transportId: self.recvTransport.id, + }); + const iceParameters = response.data.iceParameters; + await self.recvTransport.restartIce({ iceParameters }); + } + } catch (error) { + self.callbackError("重启ICE失败", error); + } + } + /** * 关闭媒体 */ diff --git a/taoyao-signal-server/pom.xml b/taoyao-signal-server/pom.xml index 6d51827..30fe99b 100644 --- a/taoyao-signal-server/pom.xml +++ b/taoyao-signal-server/pom.xml @@ -72,11 +72,6 @@ org.springframework.boot spring-boot-starter-aop - - - org.springframework.boot - spring-boot-autoconfigure - org.springframework.boot diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/annotation/Listener.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/annotation/Listener.java new file mode 100644 index 0000000..c5cac65 --- /dev/null +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/annotation/Listener.java @@ -0,0 +1,22 @@ +package com.acgist.taoyao.boot.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.stereotype.Component; + +/** + * 监听器 + * + * @author acgist + */ +@Target(ElementType.TYPE) +@Component +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Listener { + +} diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/Constant.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/Constant.java index 3ebf288..a65d158 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/Constant.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/Constant.java @@ -20,9 +20,29 @@ public interface Constant { */ String TO = "to"; /** - * 发送方 + * 状态 */ - String FROM = "from"; + String STATUS = "status"; + /** + * 配置 + */ + String CONFIG = "config"; + /** + * 纬度 + */ + String LATITUDE = "latitude"; + /** + * 经度 + */ + String LONGITUDE = "longitude"; + /** + * 湿度 + */ + String HUMIDITY = "humidity"; + /** + * 温度 + */ + String TEMPERATURE = "temperature"; /** * 信号强度(0~100) */ @@ -31,10 +51,18 @@ public interface Constant { * 电池电量(0~100) */ String BATTERY = "battery"; + /** + * 是否正在运行 + */ + String RUNNING = "running"; /** * 是否正在充电 */ String CHARGING = "charging"; + /** + * 是否正在录像 + */ + String RECORDING = "recording"; /** * 地址 */ @@ -75,6 +103,10 @@ public interface Constant { * 名称 */ String NAME = "name"; + /** + * 类型 + */ + String CLIENT_TYPE = "clientType"; /** * 脚本 */ @@ -83,6 +115,10 @@ public interface Constant { * 结果 */ String RESULT = "result"; + /** + * 消息 + */ + String MESSAGE = "message"; /** * 请求 */ @@ -95,6 +131,10 @@ public interface Constant { * 密码 */ String PASSWORD = "password"; + /** + * 媒体类型 + */ + String KIND = "kind"; /** * 媒体 */ @@ -107,12 +147,6 @@ public interface Constant { * 日期时间 */ String DATETIME = "datetime"; - /** - * PeerId - * - * @see #CLIENT_ID - */ - String PEER_ID = "peerId"; /** * 房间ID */ @@ -121,10 +155,12 @@ public interface Constant { * 媒体服务ID */ String MEDIA_ID = "mediaId"; + /** + * 媒体流ID + */ + String STREAM_ID = "streamId"; /** * 终端ID - * - * @see #PEER_ID */ String CLIENT_ID = "clientId"; /** @@ -171,5 +207,21 @@ public interface Constant { * SCTP参数 */ String SCTP_PARAMETERS = "sctpParameters"; + /** + * RTP能力 + */ + String RTP_CAPABILITIES = "rtpCapabilities"; + /** + * SCTP能力 + */ + String SCTP_CAPABILITIES = "sctpCapabilities"; + /** + * 生产者 + */ + String CONSUMING = "consuming"; + /** + * 消费者 + */ + String PRODUCING = "producing"; } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java new file mode 100644 index 0000000..99b1262 --- /dev/null +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java @@ -0,0 +1,30 @@ +package com.acgist.taoyao.boot.config; + +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +/** + * 地址重写 + * 内外网多网卡环境重写网络号保留主机号,通过重新地址实现多网互通。 + * + * @author acgist + */ +@Getter +@Setter +@Schema(title = "地址重写", description = "地址重写") +@ConfigurationProperties(prefix = "taoyao.ip-rewrite") +public class IpRewriteProperties { + + @Schema(title = "是否启用", description = "是否启用") + private Boolean enabled; + @Schema(title = "子网掩码", description = "子网掩码:主机号的长度") + private Integer prefix; + @Schema(title = "重写规则", description = "重写规则") + private List rule; + +} diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java new file mode 100644 index 0000000..8df45f4 --- /dev/null +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java @@ -0,0 +1,27 @@ +package com.acgist.taoyao.boot.config; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +/** + * 重写规则 + * + * @author acgist + */ +/** + * WebRTC STUN配置 + * + * @author acgist + */ +@Getter +@Setter +@Schema(title = "重写规则", description = "重写规则") +public class IpRewriteRuleProperties { + + @Schema(title = "网络号", description = "网络号:匹配终端IP") + private String network; + @Schema(title = "目标地址", description = "目标地址:没有配置等于网络号加上原始主机号") + private String targetHost; + +} diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaProperties.java index f4ea7d3..3690878 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaProperties.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaProperties.java @@ -1,7 +1,5 @@ package com.acgist.taoyao.boot.config; -import java.util.List; - import org.springframework.boot.context.properties.ConfigurationProperties; import io.swagger.v3.oas.annotations.media.Schema; @@ -33,7 +31,5 @@ public class MediaProperties { private MediaVideoProperties hdVideo; @Schema(title = "标清视频配置", description = "标清视频配置") private MediaVideoProperties sdVideo; - @Schema(title = "媒体服务配置", description = "媒体服务配置") - private List mediaServerList; } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaServerProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaServerProperties.java deleted file mode 100644 index 0d79543..0000000 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/MediaServerProperties.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.acgist.taoyao.boot.config; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; -import lombok.Setter; - -/** - * 媒体服务配置 - * - * @author acgist - */ -@Getter -@Setter -@Schema(title = "媒体服务配置", description = "媒体服务配置") -public class MediaServerProperties { - - @Schema(title = "媒体服务标识", description = "媒体服务标识") - private String mediaId; - @Schema(title = "是否启用", description = "是否启用") - private Boolean enabled; - @Schema(title = "是否启用重写本地IP", description = "内外网多网卡环境重写IP地址") - private Boolean rewriteIp; - @Schema(title = "主机", description = "主机") - private String host; - @Schema(title = "端口", description = "端口") - private Integer port; - @Schema(title = "协议", description = "协议") - private String schema; - @Schema(title = "用户", description = "用户") - private String username; - @Schema(title = "密码", description = "密码") - private String password; - - @Schema(title = "完整地址", description = "完整地址") - public String getAddress() { - return this.schema + "://" + this.host + ":" + this.port; - } - -} diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/ScriptProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/ScriptProperties.java index 1137e16..bd4b8a3 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/ScriptProperties.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/ScriptProperties.java @@ -19,10 +19,6 @@ public class ScriptProperties { @Schema(title = "是否启用", description = "是否启用") private Boolean enabled; - @Schema(title = "重启媒体服务", description = "重启媒体服务") - private String mediaReboot; - @Schema(title = "关闭媒体服务", description = "关闭媒体服务") - private String mediaShutdown; @Schema(title = "重启系统", description = "重启系统") private String systemReboot; @Schema(title = "关闭系统", description = "关闭系统") diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/TaoyaoProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/TaoyaoProperties.java index 13642d6..5e52fae 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/TaoyaoProperties.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/TaoyaoProperties.java @@ -25,8 +25,6 @@ public class TaoyaoProperties { private String version; @Schema(title = "项目描述", description = "项目描述") private String description; - @Schema(title = "子网掩码", description = "子网掩码") - private Integer ipMask; @Schema(title = "超时时间", description = "超时时间") private Long timeout; diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/BootAutoConfiguration.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/BootAutoConfiguration.java index c24e1ff..d0c005b 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/BootAutoConfiguration.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/BootAutoConfiguration.java @@ -10,9 +10,8 @@ import org.slf4j.ILoggerFactory; import org.slf4j.LoggerFactory; import org.springframework.beans.ConversionNotSupportedException; import org.springframework.beans.TypeMismatchException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration; @@ -21,7 +20,6 @@ import org.springframework.boot.task.TaskExecutorBuilder; import org.springframework.boot.task.TaskSchedulerBuilder; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; @@ -46,6 +44,7 @@ import org.springframework.web.multipart.support.MissingServletRequestPartExcept import org.springframework.web.servlet.NoHandlerFoundException; import com.acgist.taoyao.boot.config.IdProperties; +import com.acgist.taoyao.boot.config.IpRewriteProperties; import com.acgist.taoyao.boot.config.MediaProperties; import com.acgist.taoyao.boot.config.ScriptProperties; import com.acgist.taoyao.boot.config.SecurityProperties; @@ -57,18 +56,19 @@ import com.acgist.taoyao.boot.controller.TaoyaoErrorController; import com.acgist.taoyao.boot.model.MessageCode; import com.acgist.taoyao.boot.runner.OrderedCommandLineRunner; import com.acgist.taoyao.boot.service.IdService; -import com.acgist.taoyao.boot.service.IpService; import com.acgist.taoyao.boot.service.impl.IdServiceImpl; -import com.acgist.taoyao.boot.service.impl.IpServiceImpl; import com.acgist.taoyao.boot.utils.ErrorUtils; import com.acgist.taoyao.boot.utils.FileUtils; import com.acgist.taoyao.boot.utils.HTTPUtils; import com.acgist.taoyao.boot.utils.JSONUtils; +import com.acgist.taoyao.boot.utils.NetUtils; import com.fasterxml.jackson.databind.ObjectMapper; import ch.qos.logback.classic.LoggerContext; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.ValidationException; import lombok.extern.slf4j.Slf4j; /** @@ -80,8 +80,8 @@ import lombok.extern.slf4j.Slf4j; @Order(Ordered.HIGHEST_PRECEDENCE) @Import({ TaskExecutionAutoConfiguration.class, TaskSchedulingAutoConfiguration.class }) @EnableAsync -@Configuration @EnableScheduling +@AutoConfiguration @EnableAspectJAutoProxy(exposeProxy = false) @EnableConfigurationProperties({ IdProperties.class, @@ -90,26 +90,23 @@ import lombok.extern.slf4j.Slf4j; SocketProperties.class, TaoyaoProperties.class, WebrtcProperties.class, - SecurityProperties.class + SecurityProperties.class, + IpRewriteProperties.class }) public class BootAutoConfiguration { - - @Value("${spring.application.name:taoyao}") - private String name; - @Autowired - private ApplicationContext applicationContext; + private final TaoyaoProperties taoyaoProperties; + private final ApplicationContext applicationContext; - @Bean - @ConditionalOnMissingBean - public IdService idService() { - return new IdServiceImpl(); + public BootAutoConfiguration(TaoyaoProperties taoyaoProperties, ApplicationContext applicationContext) { + this.taoyaoProperties = taoyaoProperties; + this.applicationContext = applicationContext; } @Bean @ConditionalOnMissingBean - public IpService ipService() { - return new IpServiceImpl(); + public IdService idService(IdProperties idProperties) { + return new IdServiceImpl(idProperties); } @Bean @@ -146,21 +143,17 @@ public class BootAutoConfiguration { } @Bean - @Autowired public CommandLineRunner successCommandLineRunner( TaskExecutor taskExecutor, - TaoyaoProperties taoyaoProperties + TaoyaoProperties taoyaoProperties, + IpRewriteProperties ipRewriteProperties ) { return new OrderedCommandLineRunner() { - @Override - public int getOrder() { - return Integer.MAX_VALUE; - } @Override public void run(String ... args) throws Exception { + NetUtils.init(ipRewriteProperties); HTTPUtils.init(taoyaoProperties.getTimeout(), taskExecutor); BootAutoConfiguration.this.registerException(); - log.info("项目启动成功:{}", BootAutoConfiguration.this.name); } }; } @@ -190,10 +183,10 @@ public class BootAutoConfiguration { log.info("临时目录:{}", System.getProperty("java.io.tmpdir")); log.info("文件编码:{}", System.getProperty("file.encoding")); this.applicationContext.getBeansOfType(TaskExecutor.class).forEach((k, v) -> { - log.info("系统任务线程池:{}-{}", k, v); + log.info("系统任务线程池:{} - {}", k, v); }); this.applicationContext.getBeansOfType(TaskScheduler.class).forEach((k, v) -> { - log.info("系统定时任务线程池:{}-{}", k, v); + log.info("系统定时任务线程池:{} - {}", k, v); }); } @@ -202,9 +195,11 @@ public class BootAutoConfiguration { */ public void registerException() { ErrorUtils.register(MessageCode.CODE_3400, BindException.class); + ErrorUtils.register(MessageCode.CODE_3400, ValidationException.class); ErrorUtils.register(MessageCode.CODE_3400, TypeMismatchException.class); ErrorUtils.register(MessageCode.CODE_3404, NoHandlerFoundException.class); ErrorUtils.register(MessageCode.CODE_3503, AsyncRequestTimeoutException.class); + ErrorUtils.register(MessageCode.CODE_3400, ConstraintViolationException.class); ErrorUtils.register(MessageCode.CODE_3500, MissingPathVariableException.class); ErrorUtils.register(MessageCode.CODE_3400, ServletRequestBindingException.class); ErrorUtils.register(MessageCode.CODE_3400, HttpMessageNotReadableException.class); @@ -220,7 +215,7 @@ public class BootAutoConfiguration { @PreDestroy public void destroy() { - log.info("系统关闭:{}", this.name); + log.info("系统关闭:{}", this.taoyaoProperties.getName()); // 刷出日志缓存 final ILoggerFactory factory = LoggerFactory.getILoggerFactory(); if (factory instanceof LoggerContext context) { diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/SpringDocAutoConfiguration.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/SpringDocAutoConfiguration.java index 9ad2632..f025526 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/SpringDocAutoConfiguration.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/SpringDocAutoConfiguration.java @@ -3,12 +3,11 @@ package com.acgist.taoyao.boot.configuration; import java.util.List; import org.springdoc.core.models.GroupedOpenApi; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import com.acgist.taoyao.boot.config.TaoyaoProperties; @@ -27,20 +26,20 @@ import io.swagger.v3.oas.models.security.SecurityScheme; * @author acgist */ @Profile({ "dev", "sit" }) -@Configuration +@AutoConfiguration @ConditionalOnClass(OpenAPI.class) public class SpringDocAutoConfiguration { - - /** - * Basic认证 - */ - private static final String BASIC = "Basic"; - - @Value("${server.port:8888}") - private Integer port; - - @Autowired - private TaoyaoProperties taoyaoProperties; + + @Value("${server.port:8888}") + private Integer port; + @Value("${taoyao.security.scheme:Basic}") + private String scheme; + + private final TaoyaoProperties taoyaoProperties; + + public SpringDocAutoConfiguration(TaoyaoProperties taoyaoProperties) { + this.taoyaoProperties = taoyaoProperties; + } @Bean public GroupedOpenApi signalApi() { @@ -106,7 +105,7 @@ public class SpringDocAutoConfiguration { private List buildSecurity() { return List.of( new SecurityRequirement() - .addList(BASIC) + .addList(this.scheme) ); } @@ -115,7 +114,7 @@ public class SpringDocAutoConfiguration { */ private Components buildComponents() { return new Components() - .addSecuritySchemes(BASIC, this.buildSecurityScheme()); + .addSecuritySchemes(this.scheme, this.buildSecurityScheme()); } /** @@ -123,8 +122,8 @@ public class SpringDocAutoConfiguration { */ private SecurityScheme buildSecurityScheme() { return new SecurityScheme() - .name(BASIC) - .scheme(BASIC) + .name(this.scheme) + .scheme(this.scheme) .in(SecurityScheme.In.HEADER) .type(SecurityScheme.Type.HTTP); } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/WebMvcConfigurerAutoConfiguration.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/WebMvcConfigurerAutoConfiguration.java index bf646fd..f1d9f4f 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/WebMvcConfigurerAutoConfiguration.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/configuration/WebMvcConfigurerAutoConfiguration.java @@ -1,8 +1,7 @@ package com.acgist.taoyao.boot.configuration; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -13,16 +12,21 @@ import lombok.extern.slf4j.Slf4j; /** * MVC自动配置 * + * TODO:ErrorPageRegistrar拦截400错误https://localhost:8888/?\%3E%3C + * * @author acgist */ @Slf4j -@Configuration +@AutoConfiguration public class WebMvcConfigurerAutoConfiguration implements WebMvcConfigurer { - @Autowired - private ApplicationContext applicationContext; + private final ApplicationContext applicationContext; - @Override + public WebMvcConfigurerAutoConfiguration(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override public void addInterceptors(InterceptorRegistry registry) { this.applicationContext.getBeansOfType(InterceptorAdapter.class).entrySet().stream() .sorted((a, z) -> a.getValue().compareTo(z.getValue())) @@ -32,5 +36,10 @@ public class WebMvcConfigurerAutoConfiguration implements WebMvcConfigurer { registry.addInterceptor(value).addPathPatterns(value.pathPattern()); }); } - + +// @Override +// public void addCorsMappings(CorsRegistry registry) { +// WebMvcConfigurer.super.addCorsMappings(registry); +// } + } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Header.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Header.java index d025dee..463d92d 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Header.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Header.java @@ -33,7 +33,7 @@ public class Header implements Serializable { * 消息标识 */ @Schema(title = "消息标识", description = "消息标识") - private String id; + private Long id; /** * 信令标识 */ diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java index 714a287..14909bf 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java @@ -1,7 +1,7 @@ package com.acgist.taoyao.boot.model; import java.io.Serializable; -import java.util.HashMap; +import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -170,26 +170,27 @@ public class Message implements Cloneable, Serializable { return new Message(this.code, this.message, this.header.clone(), this.body); } - /** - * 克隆消息设置Map消息主体 - * - * @return 克隆消息 - */ - public Message CloneWithMapBody() { - final Message message = this.clone(); - message.setBody(new HashMap<>()); - return message; - } - /** * 克隆消息排除消息主体 * * @return 克隆消息 */ public Message cloneWithoutBody() { - final Message message = this.clone(); - message.setBody(null); - return message; + return new Message(this.code, this.message, this.header.clone(), null); + } + + /** + * @return Map消息主体 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Map mapBody() { + if(this.body instanceof Map map) { + return map; + } else if(this.body == null) { + return Map.of(); + } else { + throw MessageCodeException.of("信令主体类型错误:" + this.body); + } } @Override diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IdService.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IdService.java index 7b7a63e..3840739 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IdService.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IdService.java @@ -2,23 +2,24 @@ package com.acgist.taoyao.boot.service; /** * ID生成器 + * 只能用于生成消息标识 * * @author acgist */ public interface IdService { /** - * 生成十九位的ID:YYMMddHHmmss(12) + index(1) + xxxxxx(6) + * 生成十六位的ID:ddHHmmss(8) + index(2) + xxxxxx(6) * * @return ID */ long buildId(); /** - * @see #buildId() + * 生成唯一ID * * @return ID */ - String buildIdToString(); - + String buildUuid(); + } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IpService.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IpService.java deleted file mode 100644 index dd21d19..0000000 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IpService.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.acgist.taoyao.boot.service; - -/** - * 内外网多网卡环境IP服务 - * 支持配置域名 - * - * @author acgist - */ -public interface IpService { - - /** - * @see #subnetIp(String, String, String) - */ - default boolean subnetIp(String sourceIp, String clientIp) { - return this.subnetIp(sourceIp, clientIp, null); - } - - /** - * 内网是否相同网段 - * - * @param sourceIp 原始IP - * @param clientIp 终端IP - * @param defaultSourceIp 默认原始IP - * - * @return 是否匹配 - */ - boolean subnetIp(String sourceIp, String clientIp, String defaultSourceIp); - - /** - * @see #rewriteIp(String, String, String) - */ - default String rewriteIp(String sourceIp, String clientIp) { - return this.rewriteIp(sourceIp, clientIp, null); - } - - /** - * 重写IP地址 - * 内网环境重写IP地址:修改网络号保留主机号 - * - * @param sourceIp 原始IP - * @param clientIp 终端IP - * @param defaultSourceIp 默认原始IP - * - * @return 替换IP - */ - String rewriteIp(String sourceIp, String clientIp, String defaultSourceIp); - -} diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IdServiceImpl.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IdServiceImpl.java index 77dd103..81114d0 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IdServiceImpl.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IdServiceImpl.java @@ -1,23 +1,25 @@ package com.acgist.taoyao.boot.service.impl; import java.time.LocalDateTime; - -import org.springframework.beans.factory.annotation.Autowired; +import java.util.UUID; import com.acgist.taoyao.boot.config.IdProperties; import com.acgist.taoyao.boot.service.IdService; public class IdServiceImpl implements IdService { + private final IdProperties idProperties; + + public IdServiceImpl(IdProperties idProperties) { + this.idProperties = idProperties; + } + /** * 当前索引 */ private int index; - - @Autowired - private IdProperties idProperties; - @Override + @Override public long buildId() { synchronized (this) { if (++this.index > this.idProperties.getMaxIndex()) { @@ -26,21 +28,18 @@ public class IdServiceImpl implements IdService { } final LocalDateTime time = LocalDateTime.now(); return - 100000000000000000L * (time.getYear() % 100) + - 1000000000000000L * time.getMonthValue() + - 10000000000000L * time.getDayOfMonth() + - 100000000000L * time.getHour() + - 1000000000L * time.getMinute() + - 10000000L * time.getSecond() + - // 机器序号一位 + 100000000000000L * time.getDayOfMonth() + + 1000000000000L * time.getHour() + + 10000000000L * time.getMinute() + + 100000000L * time.getSecond() + 1000000L * this.idProperties.getIndex() + // 每秒并发数量 this.index; } - - @Override - public String buildIdToString() { - return String.valueOf(this.buildId()); - } + + @Override + public String buildUuid() { + return UUID.randomUUID().toString(); + } } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IpServiceImpl.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IpServiceImpl.java deleted file mode 100644 index d3ae136..0000000 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IpServiceImpl.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.acgist.taoyao.boot.service.impl; - -import java.math.BigInteger; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.BitSet; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; - -import com.acgist.taoyao.boot.config.TaoyaoProperties; -import com.acgist.taoyao.boot.service.IpService; - -import jakarta.annotation.PostConstruct; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class IpServiceImpl implements IpService { - - @Autowired - private TaoyaoProperties taoyaoProperties; - - /** - * 本机IP - */ - private String localIp; - /** - * 本机环回IP - */ - private String loopbackIp; - - @PostConstruct - public void init() { - try { - final InetAddress localHost = InetAddress.getLocalHost(); - final InetAddress loopbackAddress = InetAddress.getLoopbackAddress(); - this.localIp = localHost.getHostAddress(); - this.loopbackIp = loopbackAddress.getHostAddress(); - log.info("本机IP:{}", this.localIp); - log.info("环回IP:{}", this.loopbackIp); - } catch (UnknownHostException e) { - log.error("获取本机IP地址异常", e); - } - } - - @Override - public boolean subnetIp(final String sourceIp, final String clientIp, final String defaultSourceIp) { - try { - InetAddress clientAddress = InetAddress.getByName(clientIp); - // 通配地址或者本地地址替换本机IP - if(this.localAnyOrLoopAddress(clientAddress)) { - clientAddress = InetAddress.getByName(this.localIp); - } - InetAddress sourceAddress = InetAddress.getByName(sourceIp); - // 通配地址或者本地换回地址替换默认地址 - if(StringUtils.isNotEmpty(defaultSourceIp) && this.localAnyOrLoopAddress(sourceAddress)) { - sourceAddress = InetAddress.getByName(defaultSourceIp); - } - if(this.localAddress(sourceAddress) && this.localAddress(clientAddress)) { - final byte[] sourceBytes = sourceAddress.getAddress(); - final byte[] clientBytes = clientAddress.getAddress(); - final int length = (sourceBytes.length & clientBytes.length) * Byte.SIZE; - final BitSet sourceBit = BitSet.valueOf(sourceBytes); - final BitSet clientBit = BitSet.valueOf(clientBytes); - sourceBit.set(this.taoyaoProperties.getIpMask(), length, true); - clientBit.set(this.taoyaoProperties.getIpMask(), length, true); - final BigInteger source = new BigInteger(sourceBit.toByteArray()); - final BigInteger client = new BigInteger(clientBit.toByteArray()); - return source.equals(client); - } - } catch (Exception e) { - log.error("IP地址转换异常:{}-{}", sourceIp, clientIp, e); - } - return true; - } - - @Override - public String rewriteIp(final String sourceIp, final String clientIp, final String defaultSourceIp) { - try { - InetAddress clientAddress = InetAddress.getByName(clientIp); - // 通配地址或者本地地址替换本机IP - if(this.localAnyOrLoopAddress(clientAddress)) { - clientAddress = InetAddress.getByName(this.localIp); - } - InetAddress sourceAddress = InetAddress.getByName(sourceIp); - // 通配地址或者本地换回地址替换默认地址 - if(this.localAnyOrLoopAddress(sourceAddress) && StringUtils.isNotEmpty(defaultSourceIp)) { - sourceAddress = InetAddress.getByName(defaultSourceIp); - } - if(this.localAddress(sourceAddress) && this.localAddress(clientAddress)) { - final byte[] sourceBytes = sourceAddress.getAddress(); - final byte[] clientBytes = clientAddress.getAddress(); - final int length = (sourceBytes.length & clientBytes.length) * Byte.SIZE; - final BitSet sourceBit = BitSet.valueOf(sourceBytes); - final BitSet clientBit = BitSet.valueOf(clientBytes); - // 替换网络号保留主机号 - for (int index = 0; index < this.taoyaoProperties.getIpMask(); index++) { - sourceBit.set(index, clientBit.get(index)); - } - final byte[] bytes = sourceBit.toByteArray(); - if(bytes.length < length) { - final byte[] fillBytes = new byte[length / Byte.SIZE]; - System.arraycopy(bytes, 0, fillBytes, 0, bytes.length); - return InetAddress.getByAddress(fillBytes).getHostAddress(); - } else { - return InetAddress.getByAddress(bytes).getHostAddress(); - } - } - } catch (Exception e) { - log.error("IP地址转换异常:{}-{}", sourceIp, clientIp, e); - } - return sourceIp; - } - - /** - * @param inetAddress IP地址 - * - * @return 通配或者换回地址 - */ - private boolean localAnyOrLoopAddress(InetAddress inetAddress) { - return - inetAddress.isAnyLocalAddress() || - inetAddress.isLoopbackAddress(); - } - - /** - * @param inetAddress IP地址 - * - * @return 本地IP - */ - private boolean localAddress(InetAddress inetAddress) { - return - // 通配地址:0.0.0.0 - inetAddress.isAnyLocalAddress() || - // 环回地址:127.0.0.1 | 0:0:0:0:0:0:0:1 - inetAddress.isLoopbackAddress() || - // 链接地址:虚拟网卡 - inetAddress.isLinkLocalAddress() || - // 组播地址 - inetAddress.isMulticastAddress() || - // 本地地址:A/B/C类本地地址 - inetAddress.isSiteLocalAddress(); - } - -} diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java index 6c6ddab..a81297c 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java @@ -1,7 +1,6 @@ package com.acgist.taoyao.boot.utils; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -9,7 +8,6 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.springframework.validation.BindException; import org.springframework.validation.ObjectError; -import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; @@ -19,6 +17,8 @@ import com.acgist.taoyao.boot.model.MessageCodeException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; /** @@ -65,7 +65,7 @@ public final class ErrorUtils { * @param clazz 异常类型 */ public static final void register(MessageCode code, Class clazz) { - log.info("注册状态编码异常映射:{}-{}", code, clazz); + log.debug("注册状态编码异常映射:{} - {}", code, clazz); synchronized (CODE_MAPPING) { CODE_MAPPING.put(clazz, code); } @@ -140,6 +140,11 @@ public final class ErrorUtils { 原始信息:{} """, path, query, method, message, status, globalError); } +// final Map body = new HashMap<>(); +// body.put("url", path); +// body.put("query", query); +// body.put("method", method); +// message.setBody(body); return message; } @@ -204,24 +209,26 @@ public final class ErrorUtils { * @return 异常信息 */ public static final String message(MessageCode messageCode, Throwable throwable) { - // 数据校验异常:ValidationException - if( - throwable instanceof BindException || - throwable instanceof MethodArgumentNotValidException - ) { - final BindException bindException = (BindException) throwable; - final List allErrors = bindException.getAllErrors(); - return allErrors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(",")); + if(throwable instanceof BindException bindException) { + return bindException.getAllErrors().stream() + .map(ObjectError::getDefaultMessage) + .collect(Collectors.joining(" && ")); + } + if(throwable instanceof ConstraintViolationException violationException) { + return violationException.getConstraintViolations().stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.joining(" && ")); } - // 为了系统安全建议不要直接返回 final String message = throwable.getMessage(); - if(StringUtils.isNotEmpty(message) && messageCode == MessageCode.CODE_9999) { + // 不是未知系统异常 + if(StringUtils.isNotEmpty(message) && messageCode != MessageCode.CODE_9999) { return message; } - // 匹配含有中文 + // 包含中文直接返回:自定义的错误 if(StringUtils.isNotEmpty(message) && message.matches(".*[\\u4e00-\\u9fa5]+.*")) { return message; } + // 其他情况不能直接返回异常信息 return messageCode.getMessage(); } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/JSONUtils.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/JSONUtils.java index 42c0127..e9a8b6e 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/JSONUtils.java +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/JSONUtils.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; @@ -211,7 +210,8 @@ public final class JSONUtils { */ private static final Module buildCustomModule() { final SimpleModule customModule = new SimpleModule("CustomModule"); - customModule.addSerializer(Long.class, ToStringSerializer.instance); + // 注意不能转换Long类型数据:请求数据类型变化 +// customModule.addSerializer(Long.class, ToStringSerializer.instance); return customModule; } diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/MapUtils.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/MapUtils.java new file mode 100644 index 0000000..6f3b953 --- /dev/null +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/MapUtils.java @@ -0,0 +1,106 @@ +package com.acgist.taoyao.boot.utils; + +import java.util.Map; + +/** + * Map工具 + * + * @author acgist + */ +public final class MapUtils { + + private MapUtils() { + } + + /** + * @param 参数泛型 + * + * @param body 消息主体 + * @param key 参数名称 + * + * @return 参数值 + */ + @SuppressWarnings("unchecked") + public static final T get(Map body, String key) { + if(body == null) { + return null; + } + return (T) body.get(key); + } + + /** + * @param 参数泛型 + * + * @param body 消息主体 + * @param key 参数名称 + * @param defaultValue 参数默认值 + * + * @return 参数值 + */ + @SuppressWarnings("unchecked") + public static final T get(Map body, String key, T defaultValue) { + if(body == null) { + return defaultValue; + } + final T t = (T) body.get(key); + return t == null ? defaultValue : t; + } + + /** + * @param body 消息主体 + * @param key 参数名称 + * + * @return 参数值 + */ + public static final Long getLong(Map body, String key) { + if(body == null) { + return null; + } + final Object object = body.get(key); + if(object == null) { + return null; + } else if(object instanceof Long value) { + return value; + } + return Long.valueOf(object.toString()); + } + + /** + * @param body 消息主体 + * @param key 参数名称 + * + * @return 参数值 + */ + public static final Integer getInteger(Map body, String key) { + if(body == null) { + return null; + } + final Object object = body.get(key); + if(object == null) { + return null; + } else if(object instanceof Integer value) { + return value; + } + return Integer.valueOf(object.toString()); + } + + /** + * @param body 消息主体 + * @param key 参数名称 + * + * @return 参数值 + */ + public static final Boolean getBoolean(Map body, String key) { + if(body == null) { + return null; + } + final Object object = body.get(key); + if(object == null) { + return null; + } else if(object instanceof Boolean value) { + return value; + } + return Boolean.valueOf(object.toString()); + } + +} diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java new file mode 100644 index 0000000..c11e210 --- /dev/null +++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java @@ -0,0 +1,198 @@ +package com.acgist.taoyao.boot.utils; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.BitSet; + +import org.apache.commons.lang3.StringUtils; + +import com.acgist.taoyao.boot.config.IpRewriteProperties; +import com.acgist.taoyao.boot.config.IpRewriteRuleProperties; + +import lombok.extern.slf4j.Slf4j; + +/** + * 网络工具 + * + * @author acgist + */ +@Slf4j +public final class NetUtils { + + private NetUtils() { + } + + /** + * 本机IP + */ + private static String localIp; + /** + * 环回IP + */ + private static String loopbackIp; + /** + * 地址重写 + */ + private static IpRewriteProperties ipRewriteProperties; + + /** + * 加载配置 + */ + public static final void init(IpRewriteProperties ipRewriteProperties) { + try { + NetUtils.ipRewriteProperties = ipRewriteProperties; + final InetAddress localHost = InetAddress.getLocalHost(); + final InetAddress loopbackAddress = InetAddress.getLoopbackAddress(); + NetUtils.localIp = localHost.getHostAddress(); + NetUtils.loopbackIp = loopbackAddress.getHostAddress(); + log.info("本机IP:{}", NetUtils.localIp); + log.info("环回IP:{}", NetUtils.loopbackIp); + } catch (UnknownHostException e) { + log.error("加载网络配置异常", e); + } + } + + /** + * @return 本机IP + */ + public static final String localIp() { + return NetUtils.localIp; + } + + /** + * @return 环回IP + */ + public static final String loopbackIp() { + return NetUtils.loopbackIp; + } + + /** + * 内网是否相同网段 + * + * @param sourceIp 原始IP + * @param clientIp 终端IP + * + * @return 是否匹配 + */ + public static final boolean subnetIp(final String sourceIp, final String clientIp) { + try { + final InetAddress sourceAddress = NetUtils.realAddress(sourceIp); + final InetAddress clientAddress = NetUtils.realAddress(clientIp); + if(NetUtils.localAddress(sourceAddress) && NetUtils.localAddress(clientAddress)) { + final byte[] sourceBytes = sourceAddress.getAddress(); + final byte[] clientBytes = clientAddress.getAddress(); + final int length = (sourceBytes.length & clientBytes.length) * Byte.SIZE; + final BitSet sourceBit = BitSet.valueOf(sourceBytes); + final BitSet clientBit = BitSet.valueOf(clientBytes); + sourceBit.set(NetUtils.ipRewriteProperties.getPrefix(), length, true); + clientBit.set(NetUtils.ipRewriteProperties.getPrefix(), length, true); + final BigInteger source = new BigInteger(sourceBit.toByteArray()); + final BigInteger client = new BigInteger(clientBit.toByteArray()); + return source.equals(client); + } + } catch (UnknownHostException e) { + log.error("IP地址转换异常:{}-{}", sourceIp, clientIp, e); + } + return true; + } + + /** + * 重写地址 + * + * @param sourceIp 原始IP + * @param clientIp 终端IP + * + * @return 替换IP + */ + public static final String rewriteIp(final String sourceIp, final String clientIp) { + if(Boolean.FALSE.equals(NetUtils.ipRewriteProperties.getEnabled())) { + return sourceIp; + } + try { + final InetAddress sourceAddress = NetUtils.realAddress(sourceIp); + final InetAddress clientAddress = NetUtils.realAddress(clientIp); + if(NetUtils.localAddress(sourceAddress) && NetUtils.localAddress(clientAddress)) { + final IpRewriteRuleProperties rule = NetUtils.ipRewriteProperties.getRule().stream() + .filter(v -> NetUtils.subnetIp(v.getNetwork(), clientIp)) + .findFirst() + .orElse(null); + if(rule == null) { + return sourceIp; + } + if(StringUtils.isNotEmpty(rule.getTargetHost())) { + return rule.getTargetHost(); + } + final byte[] sourceBytes = sourceAddress.getAddress(); + final byte[] clientBytes = clientAddress.getAddress(); + final int length = (sourceBytes.length & clientBytes.length) * Byte.SIZE; + final BitSet sourceBit = BitSet.valueOf(sourceBytes); + final BitSet clientBit = BitSet.valueOf(clientBytes); + // 替换网络号保留主机号 + for (int index = 0; index < NetUtils.ipRewriteProperties.getPrefix(); index++) { + sourceBit.set(index, clientBit.get(index)); + } + final byte[] bytes = sourceBit.toByteArray(); + if(bytes.length < length) { + final byte[] fillBytes = new byte[length / Byte.SIZE]; + System.arraycopy(bytes, 0, fillBytes, 0, bytes.length); + return InetAddress.getByAddress(fillBytes).getHostAddress(); + } else { + return InetAddress.getByAddress(bytes).getHostAddress(); + } + } + } catch (UnknownHostException e) { + log.error("IP地址转换异常:{}-{}", sourceIp, clientIp, e); + } + return sourceIp; + } + + /** + * @param ip IP + * + * @return 真实地址 + * + * @throws UnknownHostException 地址转换异常 + */ + public static final InetAddress realAddress(String ip) throws UnknownHostException { + final InetAddress address = InetAddress.getByName(ip); + if(NetUtils.localAnyOrLoopAddress(address)) { + // 通配地址或者换回地址使用本机地址 + return InetAddress.getByName(NetUtils.localIp); + } + return address; + } + + /** + * @param inetAddress IP地址 + * + * @return 通配地址或者换回地址 + */ + public static final boolean localAnyOrLoopAddress(InetAddress inetAddress) { + return + // 通配地址:0.0.0.0 + inetAddress.isAnyLocalAddress() || + // 环回地址:127.0.0.1 | 0:0:0:0:0:0:0:1 + inetAddress.isLoopbackAddress(); + } + + /** + * @param inetAddress IP地址 + * + * @return 本地IP + */ + public static final boolean localAddress(InetAddress inetAddress) { + return + // 通配地址:0.0.0.0 + inetAddress.isAnyLocalAddress() || + // 环回地址:127.0.0.1 | 0:0:0:0:0:0:0:1 + inetAddress.isLoopbackAddress() || + // 链接地址:虚拟网卡 + inetAddress.isLinkLocalAddress() || + // 组播地址 + inetAddress.isMulticastAddress() || + // 本地地址:A/B/C类本地地址 + inetAddress.isSiteLocalAddress(); + } + +} diff --git a/taoyao-signal-server/taoyao-boot/src/main/resources/banner.txt b/taoyao-signal-server/taoyao-boot/src/main/resources/banner.txt index f62737c..bfc6249 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/resources/banner.txt +++ b/taoyao-signal-server/taoyao-boot/src/main/resources/banner.txt @@ -9,5 +9,5 @@ :: Spring Boot : ${spring-boot.formatted-version} :: ${spring.application.name} : https://gitee.com/acgist/taoyao -中庭地白树栖鸦,冷露无声湿桂花。 -今夜月明人尽望,不知秋思落谁家。 +去年今日此门中,人面桃花相映红。 +人面不知何处去,桃花依旧笑春风。 diff --git a/taoyao-signal-server/taoyao-boot/src/main/resources/logback-spring.xml b/taoyao-signal-server/taoyao-boot/src/main/resources/logback-spring.xml index a040590..818eb81 100644 --- a/taoyao-signal-server/taoyao-boot/src/main/resources/logback-spring.xml +++ b/taoyao-signal-server/taoyao-boot/src/main/resources/logback-spring.xml @@ -4,6 +4,7 @@ ${log.name} + @@ -11,6 +12,16 @@ + + + ${log.charset} + ${log.pattern} + + + INFO + + + ${log.path}/${log.name}.debug.log @@ -79,35 +90,16 @@ true 0 - - - - ${log.charset} - ${log.pattern} - - - INFO - - - + - - - - - - - - - - + diff --git a/taoyao-signal-server/taoyao-boot/src/test/java/com/acgist/taoyao/boot/utils/NetUtilsTest.java b/taoyao-signal-server/taoyao-boot/src/test/java/com/acgist/taoyao/boot/utils/NetUtilsTest.java new file mode 100644 index 0000000..d003cd4 --- /dev/null +++ b/taoyao-signal-server/taoyao-boot/src/test/java/com/acgist/taoyao/boot/utils/NetUtilsTest.java @@ -0,0 +1,72 @@ +package com.acgist.taoyao.boot.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.acgist.taoyao.boot.config.IpRewriteProperties; +import com.acgist.taoyao.boot.config.IpRewriteRuleProperties; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class NetUtilsTest { + + private void init() { + final IpRewriteRuleProperties ipRewriteRuleProperties1 = new IpRewriteRuleProperties(); + ipRewriteRuleProperties1.setNetwork("192.168.1.0"); + final IpRewriteRuleProperties ipRewriteRuleProperties10 = new IpRewriteRuleProperties(); + ipRewriteRuleProperties10.setNetwork("192.168.10.0"); + final IpRewriteProperties ipRewriteProperties = new IpRewriteProperties(); + ipRewriteProperties.setEnabled(true); + ipRewriteProperties.setPrefix(24); + ipRewriteProperties.setRule(List.of(ipRewriteRuleProperties1, ipRewriteRuleProperties10)); + NetUtils.init(ipRewriteProperties); + } + + @Test + public void testSubnetIp() { + this.init(); + assertTrue(NetUtils.subnetIp("192.168.8.1", "192.168.8.100")); + assertTrue(NetUtils.subnetIp("192.168.100.1", "192.168.100.100")); + assertFalse(NetUtils.subnetIp("192.168.1.1", "192.168.8.100")); + assertFalse(NetUtils.subnetIp("192.168.80.1", "192.168.8.100")); + assertTrue(NetUtils.subnetIp("fe80::9ff9:2da9:9759:17e9", "fe80::9ff9:2da9:9759:17e9")); + assertTrue(NetUtils.subnetIp("fe80::9ff9:2da9:9759:17ee", "fe80::9ff9:2da9:9759:17e9")); + assertFalse(NetUtils.subnetIp("fe81::9ff9:2da9:9759:17e9", "fe80::9ff9:2da9:9759:17e9")); + assertFalse(NetUtils.subnetIp("fe81::9ff9:2da9:9759:17ee", "fe80::9ff9:2da9:9759:17e9")); + } + + @Test + public void testRewriteIp() { + this.init(); + assertNotEquals("192.168.1.0", NetUtils.rewriteIp("0.0.0.0", "192.168.1.1")); + assertEquals("192.168.1.100", NetUtils.rewriteIp("192.168.8.100", "192.168.1.1")); + assertEquals("192.168.10.100", NetUtils.rewriteIp("192.168.8.100", "192.168.10.1")); + } + + @Test + public void testCost() { + this.init(); + long a = System.currentTimeMillis(); + for (int index = 0; index < 100000; index++) { + assertTrue(NetUtils.subnetIp("192.168.100.1", "192.168.100.100")); + assertFalse(NetUtils.subnetIp("192.168.1.1", "192.168.8.100")); + } + long z = System.currentTimeMillis(); + log.info("耗时:{}", z - a); + a = System.currentTimeMillis(); + for (int index = 0; index < 100000; index++) { + assertEquals("192.168.1.100", NetUtils.rewriteIp("192.168.8.100", "192.168.1.1")); + assertEquals("192.168.10.100", NetUtils.rewriteIp("192.168.8.100", "192.168.10.1")); + } + z = System.currentTimeMillis(); + log.info("耗时:{}", z - a); + } + +} diff --git a/taoyao-signal-server/taoyao-server/pom.xml b/taoyao-signal-server/taoyao-server/pom.xml index d4851c6..689d972 100644 --- a/taoyao-signal-server/taoyao-server/pom.xml +++ b/taoyao-signal-server/taoyao-server/pom.xml @@ -26,6 +26,10 @@ com.acgist taoyao-signal + + org.hibernate.validator + hibernate-validator + diff --git a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/configuration/TaoyaoAutoConfiguration.java b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/configuration/TaoyaoAutoConfiguration.java index 7e56e07..701287d 100644 --- a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/configuration/TaoyaoAutoConfiguration.java +++ b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/configuration/TaoyaoAutoConfiguration.java @@ -1,13 +1,17 @@ package com.acgist.taoyao.configuration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import com.acgist.taoyao.boot.config.SecurityProperties; +import com.acgist.taoyao.boot.config.TaoyaoProperties; import com.acgist.taoyao.interceptor.SecurityInterceptor; import com.acgist.taoyao.interceptor.SlowInterceptor; import com.acgist.taoyao.signal.service.SecurityService; +import com.acgist.taoyao.signal.service.UsernamePasswordService; import com.acgist.taoyao.signal.service.impl.SecurityServiceImpl; /** @@ -15,26 +19,29 @@ import com.acgist.taoyao.signal.service.impl.SecurityServiceImpl; * * @author acgist */ -@Configuration +@AutoConfiguration public class TaoyaoAutoConfiguration { @Bean @ConditionalOnMissingBean - public SlowInterceptor slowInterceptor() { - return new SlowInterceptor(); + public SlowInterceptor slowInterceptor(TaoyaoProperties taoyaoProperties) { + return new SlowInterceptor(taoyaoProperties); } @Bean @ConditionalOnMissingBean - public SecurityService securityService() { - return new SecurityServiceImpl(); + public SecurityService securityService( + SecurityProperties securityProperties, + @Autowired(required = false) UsernamePasswordService usernamePasswordService + ) { + return new SecurityServiceImpl(securityProperties, usernamePasswordService); } @Bean @ConditionalOnProperty(prefix = "taoyao.security", name = "enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnMissingBean - public SecurityInterceptor securityInterceptor() { - return new SecurityInterceptor(); + public SecurityInterceptor securityInterceptor(SecurityService securityService, SecurityProperties securityProperties) { + return new SecurityInterceptor(securityService, securityProperties); } } diff --git a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/controller/ConfigController.java b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/controller/ConfigController.java index 0aeea3c..f3dcd95 100644 --- a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/controller/ConfigController.java +++ b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/controller/ConfigController.java @@ -1,6 +1,5 @@ package com.acgist.taoyao.controller; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -24,13 +23,16 @@ import io.swagger.v3.oas.annotations.tags.Tag; @RestController @RequestMapping("/config") public class ConfigController { - - @Autowired - private MediaProperties mediaProperties; - @Autowired - private WebrtcProperties webrtcProperties; + + private final MediaProperties mediaProperties; + private final WebrtcProperties webrtcProperties; - @Operation(summary = "媒体配置", description = "媒体配置") + public ConfigController(MediaProperties mediaProperties, WebrtcProperties webrtcProperties) { + this.mediaProperties = mediaProperties; + this.webrtcProperties = webrtcProperties; + } + + @Operation(summary = "媒体配置", description = "媒体配置") @GetMapping("/media") @ApiResponse(content = @Content(schema = @Schema(implementation = MediaProperties.class))) public Message media() { diff --git a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/controller/ControlController.java b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/controller/ControlController.java index e147564..52dbda9 100644 --- a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/controller/ControlController.java +++ b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/controller/ControlController.java @@ -1,14 +1,16 @@ package com.acgist.taoyao.controller; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.protocol.media.MediaRebootProtocol; -import com.acgist.taoyao.signal.protocol.media.MediaShutdownProtocol; +import com.acgist.taoyao.signal.protocol.control.ControlBellProtocol; +import com.acgist.taoyao.signal.protocol.control.ControlPhotographProtocol; +import com.acgist.taoyao.signal.protocol.control.ControlPtzProtocol; +import com.acgist.taoyao.signal.protocol.control.ControlRecordProtocol; import com.acgist.taoyao.signal.protocol.platform.PlatformRebootProtocol; import com.acgist.taoyao.signal.protocol.platform.PlatformShutdownProtocol; import com.acgist.taoyao.signal.protocol.system.SystemRebootProtocol; @@ -16,6 +18,7 @@ import com.acgist.taoyao.signal.protocol.system.SystemShutdownProtocol; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; /** * 控制 @@ -23,37 +26,68 @@ import io.swagger.v3.oas.annotations.tags.Tag; * @author acgist */ @Tag(name = "控制", description = "控制管理") +@Validated @RestController @RequestMapping("/control") public class ControlController { - @Autowired - private MediaRebootProtocol mediaRebootProtocol; - @Autowired - private MediaShutdownProtocol mediaShutdownProtocol; - @Autowired - private SystemRebootProtocol systemRebootProtocol; - @Autowired - private SystemShutdownProtocol systemShutdownProtocol; - @Autowired - private PlatformRebootProtocol platformRebootProtocol; - @Autowired - private PlatformShutdownProtocol platformShutdownProtocol; + private final ControlPtzProtocol controlPtzProtocol; + private final ControlBellProtocol controlBellProtocol; + private final SystemRebootProtocol systemRebootProtocol; + private final ControlRecordProtocol controlRecordProtocol; + private final SystemShutdownProtocol systemShutdownProtocol; + private final PlatformRebootProtocol platformRebootProtocol; + private final PlatformShutdownProtocol platformShutdownProtocol; + private final ControlPhotographProtocol controlPhotographProtocol; - @Operation(summary = "重启媒体", description = "重启媒体") - @GetMapping("/media/reboot/{mediaId}") - public Message mediaReboot(@PathVariable String mediaId) { - this.mediaRebootProtocol.execute(mediaId); - return Message.success(); - } - - @Operation(summary = "关闭媒体", description = "关闭媒体") - @GetMapping("/media/shutdown/{mediaId}") - public Message mediaShutdown(@PathVariable String mediaId) { - this.mediaShutdownProtocol.execute(mediaId); - return Message.success(); + public ControlController( + ControlPtzProtocol controlPtzProtocol, ControlBellProtocol controlBellProtocol, + SystemRebootProtocol systemRebootProtocol, ControlRecordProtocol controlRecordProtocol, + SystemShutdownProtocol systemShutdownProtocol, PlatformRebootProtocol platformRebootProtocol, + PlatformShutdownProtocol platformShutdownProtocol, ControlPhotographProtocol controlPhotographProtocol + ) { + this.controlPtzProtocol = controlPtzProtocol; + this.controlBellProtocol = controlBellProtocol; + this.systemRebootProtocol = systemRebootProtocol; + this.controlRecordProtocol = controlRecordProtocol; + this.systemShutdownProtocol = systemShutdownProtocol; + this.platformRebootProtocol = platformRebootProtocol; + this.platformShutdownProtocol = platformShutdownProtocol; + this.controlPhotographProtocol = controlPhotographProtocol; } + @Operation(summary = "PTZ", description = "PTZ") + @GetMapping("/ptz/{clientId}") + public Message ptz( + @NotNull(message = "PTZ类型不能为空") ControlPtzProtocol.Type type, + @NotNull(message = "PTZ参数不能为空") Double value, + @PathVariable String clientId + ) { + this.controlPtzProtocol.execute(type, value, clientId); + return Message.success(); + } + + @Operation(summary = "响铃", description = "响铃") + @GetMapping("/bell/{clientId}") + public Message bell(@PathVariable String clientId) { + this.controlBellProtocol.execute(clientId); + return Message.success(); + } + + @Operation(summary = "录像", description = "录像") + @GetMapping("/record/{clientId}") + public Message record(@PathVariable String clientId) { + this.controlRecordProtocol.execute(clientId); + return Message.success(); + } + + @Operation(summary = "拍照", description = "拍照") + @GetMapping("/photograph/{clientId}") + public Message photograph(@PathVariable String clientId) { + this.controlPhotographProtocol.execute(clientId); + return Message.success(); + } + @Operation(summary = "重启系统", description = "重启系统") @GetMapping("/system/reboot") public Message systemReboot() { diff --git a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SecurityInterceptor.java b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SecurityInterceptor.java index 2f764b4..c13201f 100644 --- a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SecurityInterceptor.java +++ b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SecurityInterceptor.java @@ -4,7 +4,6 @@ import java.util.Base64; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.util.AntPathMatcher; @@ -25,12 +24,16 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class SecurityInterceptor extends InterceptorAdapter { - @Autowired - private SecurityService securityService; - @Autowired - private SecurityProperties securityProperties; + private final SecurityService securityService; + private final SecurityProperties securityProperties; + + public SecurityInterceptor(SecurityService securityService, SecurityProperties securityProperties) { + this.securityService = securityService; + this.securityProperties = securityProperties; + log.info("安全拦截密码:{}", securityProperties.getPassword()); + } - /** + /** * 地址匹配 */ private final AntPathMatcher matcher = new AntPathMatcher(); diff --git a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SlowInterceptor.java b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SlowInterceptor.java index adcf34b..24832f7 100644 --- a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SlowInterceptor.java +++ b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/interceptor/SlowInterceptor.java @@ -1,7 +1,5 @@ package com.acgist.taoyao.interceptor; -import org.springframework.beans.factory.annotation.Autowired; - import com.acgist.taoyao.boot.config.TaoyaoProperties; import com.acgist.taoyao.boot.interceptor.InterceptorAdapter; @@ -17,10 +15,13 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class SlowInterceptor extends InterceptorAdapter { - @Autowired - private TaoyaoProperties taoyaoProperties; + private final TaoyaoProperties taoyaoProperties; - /** + public SlowInterceptor(TaoyaoProperties taoyaoProperties) { + this.taoyaoProperties = taoyaoProperties; + } + + /** * 请求开始时间 */ private final ThreadLocal local = new ThreadLocal<>(); diff --git a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/HTTPClientListener.java b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/QnjrcmzListener.java similarity index 59% rename from taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/HTTPClientListener.java rename to taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/QnjrcmzListener.java index 2f0f1bf..56d796f 100644 --- a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/HTTPClientListener.java +++ b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/QnjrcmzListener.java @@ -3,18 +3,24 @@ package com.acgist.taoyao.listener; import org.springframework.boot.context.event.ApplicationStartingEvent; import org.springframework.context.ApplicationListener; +import lombok.extern.slf4j.Slf4j; + /** - * 配置JDK HTTPClient域名校验问题 + * 去年今日此门中 + * 人面桃花相映红 * * 注意:SpringApplicationRunListeners里面同步执行 * * @author acgist */ -public class HTTPClientListener implements ApplicationListener { +@Slf4j +public class QnjrcmzListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationStartingEvent event) { - synchronized (HTTPClientListener.class) { + synchronized (QnjrcmzListener.class) { + log.debug("配置忽略证书域名校验"); + // 配置JDK HTTPClient域名校验问题 System.getProperties().setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString()); } } diff --git a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/ThyjxcfListener.java b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/ThyjxcfListener.java new file mode 100644 index 0000000..334571a --- /dev/null +++ b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/listener/ThyjxcfListener.java @@ -0,0 +1,29 @@ +package com.acgist.taoyao.listener; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; + +import com.acgist.taoyao.boot.annotation.Listener; +import com.acgist.taoyao.boot.config.TaoyaoProperties; + +import lombok.extern.slf4j.Slf4j; + +/** + * 人面不知何处去 + * 桃花依旧笑春风 + * + * @author acgist + */ +@Slf4j +@Listener +public class ThyjxcfListener implements ApplicationListener { + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + synchronized (ThyjxcfListener.class) { + final TaoyaoProperties taoyaoProperties = event.getApplicationContext().getBean(TaoyaoProperties.class); + log.info("项目启动成功:{}", taoyaoProperties.getName()); + } + } + +} diff --git a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/main/TaoyaoApplication.java b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/main/TaoyaoApplication.java index 80f28f7..f10f26a 100644 --- a/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/main/TaoyaoApplication.java +++ b/taoyao-signal-server/taoyao-server/src/main/java/com/acgist/taoyao/main/TaoyaoApplication.java @@ -10,6 +10,7 @@ public class TaoyaoApplication { public static void main(String[] args) { SpringApplication.run(TaoyaoApplication.class, args); +// System.exit(SpringApplication.exit(SpringApplication.run(TaoyaoApplication.class, args))); } } diff --git a/taoyao-signal-server/taoyao-server/src/main/resources/META-INF/spring.factories b/taoyao-signal-server/taoyao-server/src/main/resources/META-INF/spring.factories index f4bc38e..5460a92 100644 --- a/taoyao-signal-server/taoyao-server/src/main/resources/META-INF/spring.factories +++ b/taoyao-signal-server/taoyao-server/src/main/resources/META-INF/spring.factories @@ -1 +1 @@ -org.springframework.context.ApplicationListener=com.acgist.taoyao.listener.HTTPClientListener \ No newline at end of file +org.springframework.context.ApplicationListener=com.acgist.taoyao.listener.QnjrcmzListener diff --git a/taoyao-signal-server/taoyao-server/src/main/resources/application-dev.yml b/taoyao-signal-server/taoyao-server/src/main/resources/application-dev.yml index e7e611f..af7073d 100644 --- a/taoyao-signal-server/taoyao-server/src/main/resources/application-dev.yml +++ b/taoyao-signal-server/taoyao-server/src/main/resources/application-dev.yml @@ -1,14 +1,10 @@ taoyao: - media: - media-server-list: - - media-id: media-local-a - enabled: false - rewrite-ip: true -# host: 192.168.1.110 - host: 192.168.8.110 - port: 9443 - schema: wss - username: taoyao - password: taoyao security: - permit: /v3/api-docs/**,/swagger-ui/**,/swagger-ui.html,/,/error,/index.html,/favicon.ico \ No newline at end of file + permit: + - / + - /error + - /index.html + - /favicon.ico + - /v3/api-docs/** + - /swagger-ui/** + - /swagger-ui.html \ No newline at end of file diff --git a/taoyao-signal-server/taoyao-server/src/main/resources/application-prd.yml b/taoyao-signal-server/taoyao-server/src/main/resources/application-prd.yml index c0d2200..9e5c416 100644 --- a/taoyao-signal-server/taoyao-server/src/main/resources/application-prd.yml +++ b/taoyao-signal-server/taoyao-server/src/main/resources/application-prd.yml @@ -1,4 +1,6 @@ taoyao: + security: + password: ${random.uuid} script: enabled: false \ No newline at end of file diff --git a/taoyao-signal-server/taoyao-server/src/main/resources/application.yml b/taoyao-signal-server/taoyao-server/src/main/resources/application.yml index 842f409..61a07f3 100644 --- a/taoyao-signal-server/taoyao-server/src/main/resources/application.yml +++ b/taoyao-signal-server/taoyao-server/src/main/resources/application.yml @@ -19,6 +19,8 @@ server: # servlet: # context-path: /taoyao spring: +# main: +# lazy-initialization: true profiles: active: dev application: @@ -51,7 +53,6 @@ taoyao: name: 桃夭信令服务 version: 1.0.0 description: 桃夭WebRTC信令服务 - ip-mask: 24 timeout: 5000 id: index: 0 @@ -99,24 +100,6 @@ taoyao: bitrate: 800 frame-rate: 16 resolution: 720*480 - # 媒体服务配置 - media-server-list: - - media-id: media-local-a - enabled: true - rewrite-ip: true - host: 127.0.0.1 - port: 9443 - schema: wss - username: taoyao - password: taoyao - - media-id: media-local-z - enabled: true - rewrite-ip: true - host: 127.0.0.1 - port: 9443 - schema: wss - username: taoyao - password: taoyao # Socket信令 socket: enabled: true @@ -151,18 +134,31 @@ taoyao: security: enabled: true realm: taoyao - permit: /,/error,/index.html,/favicon.ico + permit: + - / + - /error + - /index.html + - /favicon.ico username: taoyao password: taoyao # 定时任务 scheduled: media: 0 * * * * ? client: 0 * * * * ? + # 地址重写 + ip-rewrite: + enabled: true + prefix: 24 + rule: + - prefix: 24 + network: 192.168.1.0 + target-host: + - prefix: 24 + network: 192.168.8.0 + target-host: # 脚本配置 script: enabled: true - media-reboot: pm2 restart taoyao-client-media - media-shutdown: pm2 stop taoyao-client-media system-reboot: reboot system-shutdown: shutdown now platform-reboot: systemctl restart taoyao-signal-server diff --git a/taoyao-signal-server/taoyao-server/src/test/java/com/acgist/taoyao/service/IdServiceTest.java b/taoyao-signal-server/taoyao-server/src/test/java/com/acgist/taoyao/service/IdServiceTest.java index 789eba0..493c4f0 100644 --- a/taoyao-signal-server/taoyao-server/src/test/java/com/acgist/taoyao/service/IdServiceTest.java +++ b/taoyao-signal-server/taoyao-server/src/test/java/com/acgist/taoyao/service/IdServiceTest.java @@ -25,6 +25,7 @@ class IdServiceTest { void testId() { final long id = this.idService.buildId(); log.info("生成ID:{}", id); + log.info("生成ID:{}", String.valueOf(id).length()); } @Test diff --git a/taoyao-signal-server/taoyao-server/src/test/java/com/acgist/taoyao/service/IpServiceTest.java b/taoyao-signal-server/taoyao-server/src/test/java/com/acgist/taoyao/service/IpServiceTest.java deleted file mode 100644 index 88f1614..0000000 --- a/taoyao-signal-server/taoyao-server/src/test/java/com/acgist/taoyao/service/IpServiceTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.acgist.taoyao.service; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.net.InetAddress; -import java.net.UnknownHostException; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import com.acgist.taoyao.annotation.TaoyaoTest; -import com.acgist.taoyao.boot.service.IpService; -import com.acgist.taoyao.main.TaoyaoApplication; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@TaoyaoTest(classes = TaoyaoApplication.class) -public class IpServiceTest { - - @Autowired - private IpService ipService; - - @Test - public void testDomain() throws UnknownHostException { - InetAddress byName = InetAddress.getByName("www.acgist.com"); - System.out.println(byName); - System.out.println(byName.getHostAddress()); - System.out.println(byName.getHostName()); - System.out.println(byName.isAnyLocalAddress()); - System.out.println(byName.isLoopbackAddress()); - System.out.println(byName.isLinkLocalAddress()); - System.out.println(byName.isMulticastAddress()); - System.out.println(byName.isSiteLocalAddress()); - } - - @Test - public void testSubnetIp() { - assertTrue(this.ipService.subnetIp("192.168.8.1", "192.168.8.100")); - assertTrue(this.ipService.subnetIp("192.168.100.1", "192.168.100.100")); - assertFalse(this.ipService.subnetIp("192.168.1.1", "192.168.8.100")); - assertFalse(this.ipService.subnetIp("192.168.80.1", "192.168.8.100")); - assertTrue(this.ipService.subnetIp("fe80::9ff9:2da9:9759:17e9", "fe80::9ff9:2da9:9759:17e9")); - assertTrue(this.ipService.subnetIp("fe80::9ff9:2da9:9759:17ee", "fe80::9ff9:2da9:9759:17e9")); - assertFalse(this.ipService.subnetIp("fe81::9ff9:2da9:9759:17e9", "fe80::9ff9:2da9:9759:17e9")); - assertFalse(this.ipService.subnetIp("fe81::9ff9:2da9:9759:17ee", "fe80::9ff9:2da9:9759:17e9")); - } - - @Test - public void testRewriteIp() { - assertEquals("192.168.1.0", this.ipService.rewriteIp("0.0.0.0", "192.168.1.1")); - assertEquals("192.168.1.10", this.ipService.rewriteIp("0.0.0.0", "192.168.1.1", "192.168.8.10")); - assertEquals("192.168.1.100", this.ipService.rewriteIp("192.168.8.100", "192.168.1.1")); - assertEquals("192.168.10.100", this.ipService.rewriteIp("192.168.8.100", "192.168.10.1")); - } - - @Test - public void testCost() { - long a = System.currentTimeMillis(); - for (int index = 0; index < 100000; index++) { - assertTrue(this.ipService.subnetIp("192.168.100.1", "192.168.100.100")); - assertFalse(this.ipService.subnetIp("192.168.1.1", "192.168.8.100")); - } - long z = System.currentTimeMillis(); - log.info("耗时:{}", z - a); - a = System.currentTimeMillis(); - for (int index = 0; index < 100000; index++) { - assertEquals("192.168.1.100", this.ipService.rewriteIp("192.168.8.100", "192.168.1.1")); - assertEquals("192.168.10.100", this.ipService.rewriteIp("192.168.8.100", "192.168.10.1")); - } - z = System.currentTimeMillis(); - log.info("耗时:{}", z - a); - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/MapBodyGetter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/MapBodyGetter.java deleted file mode 100644 index db44b21..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/MapBodyGetter.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.acgist.taoyao.signal; - -import java.util.Map; - -/** - * Map参数Getter - * - * @author acgist - */ -public interface MapBodyGetter { - - /** - * @param 参数泛型 - * - * @param body 消息主体 - * @param key 参数名称 - * - * @return 参数值 - */ - @SuppressWarnings("unchecked") - default T get(Map body, String key) { - if(body == null) { - return null; - } - return (T) body.get(key); - } - - /** - * @param 参数泛型 - * - * @param body 消息主体 - * @param key 参数名称 - * @param defaultValue 参数默认值 - * - * @return 参数值 - */ - @SuppressWarnings("unchecked") - default T get(Map body, String key, T defaultValue) { - if(body == null) { - return defaultValue; - } - final T t = (T) body.get(key); - return t == null ? defaultValue : t; - } - - /** - * @param body 消息主体 - * @param key 参数名称 - * - * @return 参数值 - */ - default Long getLong(Map body, String key) { - if(body == null) { - return null; - } - final Object object = body.get(key); - if(object == null) { - return null; - } else if(object instanceof Long value) { - return value; - } - return Long.valueOf(object.toString()); - } - - /** - * @param body 消息主体 - * @param key 参数名称 - * - * @return 参数值 - */ - default Integer getInteger(Map body, String key) { - if(body == null) { - return null; - } - final Object object = body.get(key); - if(object == null) { - return null; - } else if(object instanceof Integer value) { - return value; - } - return Integer.valueOf(object.toString()); - } - - /** - * @param body 消息主体 - * @param key 参数名称 - * - * @return 参数值 - */ - default Boolean getBoolean(Map body, String key) { - if(body == null) { - return null; - } - final Object object = body.get(key); - if(object == null) { - return null; - } else if(object instanceof Boolean value) { - return value; - } - return Boolean.valueOf(object.toString()); - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/Client.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/Client.java index c9bc080..44ee177 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/Client.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/Client.java @@ -1,8 +1,6 @@ package com.acgist.taoyao.signal.client; import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.media.MediaClient; -import com.acgist.taoyao.signal.media.Peer; /** * 终端 @@ -22,9 +20,9 @@ public interface Client extends AutoCloseable { String clientId(); /** - * @return Peer + * @return 终端类型 */ - Peer peer(); + ClientType clientType(); /** * @return 终端状态 @@ -38,6 +36,25 @@ public interface Client extends AutoCloseable { */ void push(Message message); + /** + * 请求消息 + * + * @param request 消息 + * + * @return 响应 + */ + Message request(Message request); + + /** + * 响应消息 + * + * @param id 消息标识 + * @param message 消息 + * + * @return 是否响应消息 + */ + boolean response(Long id, Message message); + /** * @param timeout 超时时间 * @@ -62,14 +79,4 @@ public interface Client extends AutoCloseable { */ boolean authorized(); - /** - * @return 媒体服务终端 - */ - MediaClient mediaClient(); - - /** - * @param mediaClient 媒体服务终端 - */ - void mediaClient(MediaClient mediaClient); - } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientAdapter.java index 6aeb6fa..53052a7 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientAdapter.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientAdapter.java @@ -1,7 +1,14 @@ package com.acgist.taoyao.signal.client; -import com.acgist.taoyao.signal.media.MediaClient; -import com.acgist.taoyao.signal.media.Peer; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.acgist.taoyao.boot.model.Header; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.model.MessageCode; +import com.acgist.taoyao.boot.model.MessageCodeException; + +import lombok.extern.slf4j.Slf4j; /** * 终端适配器 @@ -10,6 +17,7 @@ import com.acgist.taoyao.signal.media.Peer; * * @param 实例泛型 */ +@Slf4j public abstract class ClientAdapter implements Client { /** @@ -21,6 +29,10 @@ public abstract class ClientAdapter implements Client { */ protected final long time; /** + * 超时时间 + */ + protected final long timeout; + /** * 终端标识 */ protected String clientId; @@ -32,25 +44,22 @@ public abstract class ClientAdapter implements Client { * 是否授权 */ protected boolean authorized; - /** - * Peer - */ - protected Peer peer; /** * 终端状态 */ - protected ClientStatus status; + protected final ClientStatus status; /** - * 媒体服务终端 - */ - protected MediaClient mediaClient; + * 同步消息 + */ + protected final Map requestMessage; - protected ClientAdapter(T instance) { + protected ClientAdapter(long timeout, T instance) { this.time = System.currentTimeMillis(); + this.timeout = timeout; this.instance = instance; this.authorized = false; - this.peer = new Peer(this); this.status = new ClientStatus(); + this.requestMessage = new ConcurrentHashMap<>(); } @Override @@ -64,8 +73,8 @@ public abstract class ClientAdapter implements Client { } @Override - public Peer peer() { - return this.peer; + public ClientType clientType() { + return this.status.getClientType(); } @Override @@ -73,6 +82,43 @@ public abstract class ClientAdapter implements Client { return this.status; } + @Override + public Message request(Message request) { + final Header header = request.getHeader(); + final Long id = header.getId(); + this.requestMessage.put(id, request); + synchronized (request) { + this.push(request); + try { + request.wait(this.timeout); + } catch (InterruptedException e) { + log.error("媒体服务等待响应异常:{}", request, e); + } + } + final Message response = this.requestMessage.remove(id); + if (response == null || request.equals(response)) { + log.warn("媒体服务没有响应:{}", request); + throw MessageCodeException.of(MessageCode.CODE_2001, "媒体服务没有响应"); + } + return response; + } + + @Override + public boolean response(Long id, Message message) { + final Message request = this.requestMessage.get(id); + if (request != null) { + // 同步处理:重新设置响应消息 + this.requestMessage.put(id, message); + // 唤醒等待线程 + synchronized (request) { + request.notifyAll(); + } + return true; + } else { + return false; + } + } + @Override public boolean timeout(long timeout) { return System.currentTimeMillis() - this.time > timeout; @@ -94,17 +140,6 @@ public abstract class ClientAdapter implements Client { return this.authorized; } - @Override - public MediaClient mediaClient() { - return this.mediaClient; - } - - @Override - public void mediaClient(MediaClient mediaClient) { - this.mediaClient = mediaClient; - this.status.setMediaId(mediaClient.mediaId()); - } - @Override public void close() throws Exception { this.instance.close(); diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientManager.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientManager.java index 18f0740..634e61a 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientManager.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientManager.java @@ -4,14 +4,14 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; -import org.springframework.beans.factory.annotation.Autowired; +import org.apache.commons.lang3.ArrayUtils; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.annotation.Scheduled; import com.acgist.taoyao.boot.annotation.Manager; import com.acgist.taoyao.boot.config.TaoyaoProperties; import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.event.client.ClientCloseEvent; +import com.acgist.taoyao.signal.event.ClientCloseEvent; import lombok.extern.slf4j.Slf4j; @@ -24,19 +24,23 @@ import lombok.extern.slf4j.Slf4j; @Manager public class ClientManager { - @Autowired - private TaoyaoProperties taoyaoProperties; - @Autowired - private ApplicationContext applicationContext; + private final TaoyaoProperties taoyaoProperties; + private final ApplicationContext applicationContext; /** * 终端列表 */ - private List clients = new CopyOnWriteArrayList<>(); - + private final List clients; + + public ClientManager(TaoyaoProperties taoyaoProperties, ApplicationContext applicationContext) { + this.taoyaoProperties = taoyaoProperties; + this.applicationContext = applicationContext; + this.clients = new CopyOnWriteArrayList<>(); + } + @Scheduled(cron = "${taoyao.scheduled.client:0 * * * * ?}") public void scheduled() { - this.closeTimeout(); + this.closeTimeout(); } /** @@ -75,12 +79,11 @@ public class ClientManager { /** * 授权终端广播消息 * - * TODO:类型 - * * @param message 消息 + * @param clientTypes 终端类型 */ - public void broadcast(Message message) { - this.clients().forEach(v -> v.push(message)); + public void broadcast(Message message, ClientType ... clientTypes) { + this.clients(clientTypes).forEach(v -> v.push(message)); } /** @@ -88,9 +91,10 @@ public class ClientManager { * * @param from 发送终端 * @param message 消息 + * @param clientTypes 终端类型 */ - public void broadcast(String from, Message message) { - this.clients().stream() + public void broadcast(String from, Message message, ClientType ... clientTypes) { + this.clients(clientTypes).stream() .filter(v -> !Objects.equals(from, v.clientId())) .forEach(v -> v.push(message)); } @@ -100,9 +104,10 @@ public class ClientManager { * * @param from 发送终端 * @param message 消息 + * @param clientTypes 终端类型 */ - public void broadcast(Client from, Message message) { - this.clients().stream() + public void broadcast(Client from, Message message, ClientType ... clientTypes) { + this.clients(clientTypes).stream() .filter(v -> v.instance() != from) .forEach(v -> v.push(message)); } @@ -112,7 +117,7 @@ public class ClientManager { * * @return 终端 */ - public Client client(AutoCloseable instance) { + public Client clients(AutoCloseable instance) { return this.clients.stream() .filter(v -> v.instance() == instance) .findFirst() @@ -122,20 +127,24 @@ public class ClientManager { /** * @param clientId 终端标识 * - * @return 授权终端列表 + * @return 授权终端 */ - public List clients(String clientId) { + public Client clients(String clientId) { return this.clients().stream() .filter(v -> Objects.equals(clientId, v.clientId())) - .toList(); + .findFirst() + .orElse(null); } /** + * @param clientTypes 终端类型 + * * @return 所有授权终端列表 */ - public List clients() { + public List clients(ClientType ... clientTypes) { return this.clients.stream() .filter(Client::authorized) + .filter(client -> ArrayUtils.isEmpty(clientTypes) || ArrayUtils.contains(clientTypes, client.clientType())) .toList(); } @@ -145,26 +154,27 @@ public class ClientManager { * @return 终端状态 */ public ClientStatus status(AutoCloseable instance) { - final Client client = this.client(instance); + final Client client = this.clients(instance); return client == null ? null : client.status(); } /** * @param clientId 终端标识 * - * @return 授权终端状态列表 + * @return 授权终端状态 */ - public List status(String clientId) { - return this.clients(clientId).stream() - .map(Client::status) - .toList(); + public ClientStatus status(String clientId) { + final Client client = this.clients(clientId); + return client == null ? null : client.status(); } /** + * @param clientTypes 终端类型 + * * @return 所有授权终端状态列表 */ - public List status() { - return this.clients().stream() + public List status(ClientType ... clientTypes) { + return this.clients(clientTypes).stream() .map(Client::status) .toList(); } @@ -176,7 +186,7 @@ public class ClientManager { * @param message 消息 */ public void push(AutoCloseable instance, Message message) { - final Client client = this.client(instance); + final Client client = this.clients(instance); if(client == null) { log.warn("推送消息终端无效:{}-{}", instance, message); return; @@ -190,7 +200,7 @@ public class ClientManager { * @param instance 终端实例 */ public void close(AutoCloseable instance) { - final Client client = this.client(instance); + final Client client = this.clients(instance); try { if(client != null) { client.close(); diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientStatus.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientStatus.java index 5e0a5be..2db4d16 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientStatus.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientStatus.java @@ -1,6 +1,11 @@ package com.acgist.taoyao.signal.client; import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +import com.acgist.taoyao.boot.config.Constant; +import com.acgist.taoyao.boot.utils.MapUtils; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @@ -18,23 +23,79 @@ public class ClientStatus { @Schema(title = "终端IP", description = "终端IP") private String ip; - @Schema(title = "媒体服务标识", description = "媒体服务标识") - private String mediaId; + @Schema(title = "终端名称", description = "终端名称") + private String name; @Schema(title = "终端标识", description = "终端标识") private String clientId; + @Schema(title = "终端类型", description = "终端类型") + private ClientType clientType; + @Schema(title = "纬度", description = "纬度") + private Double latitude; + @Schema(title = "经度", description = "经度") + private Double longitude; + @Schema(title = "湿度", description = "湿度") + private Double humidity; + @Schema(title = "温度", description = "温度") + private Double temperature; @Schema(title = "信号强度(0~100)", description = "信号强度(0~100)") - private Integer signal = 0; + private Integer signal; @Schema(title = "电池电量(0~100)", description = "电池电量(0~100)") - private Integer battery = 0; + private Integer battery; + @Schema(title = "是否正在运行", description = "是否正在运行") + private Boolean running; @Schema(title = "是否正在充电", description = "是否正在充电") - private Boolean charging = false; + private Boolean charging; @Schema(title = "是否正在录像", description = "是否正在录像") - private Boolean recording = false; - @Schema(title = "生产者数量", description = "生产者数量") - private Integer producerSize; - @Schema(title = "消费者数量", description = "消费者数量") - private Integer customerSize; + private Boolean recording; @Schema(title = "最后心跳时间", description = "最后心跳时间") private LocalDateTime lastHeartbeat; + @Schema(title = "终端状态", description = "其他扩展终端状态") + private Map status = new HashMap<>(); + @Schema(title = "终端配置", description = "其他扩展终端配置") + private Map config = new HashMap<>(); + + /** + * 拷贝属性 + * + * @param body 消息主体 + */ + public void copy(Map body) { + this.setLatitude(MapUtils.get(body, Constant.LATITUDE)); + this.setLongitude(MapUtils.get(body, Constant.LONGITUDE)); + this.setHumidity(MapUtils.get(body, Constant.HUMIDITY)); + this.setTemperature(MapUtils.get(body, Constant.TEMPERATURE)); + this.setSignal(MapUtils.get(body, Constant.SIGNAL)); + this.setBattery(MapUtils.get(body, Constant.BATTERY)); + this.setRunning(MapUtils.get(body, Constant.RUNNING)); + this.setCharging(MapUtils.get(body, Constant.CHARGING)); + this.setRecording(MapUtils.get(body, Constant.RECORDING)); + this.setLastHeartbeat(LocalDateTime.now()); + this.status(MapUtils.get(body, Constant.STATUS)); + this.config(MapUtils.get(body, Constant.CONFIG)); + } + + /** + * 拷贝状态 + * + * @param map 状态 + */ + public void status(Map map) { + if(map == null) { + return; + } + map.forEach(this.status::put); + } + + /** + * 拷贝配置 + * + * @param map 配置 + */ + public void config(Map map) { + if(map == null) { + return; + } + map.forEach(this.config::put); + } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientType.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientType.java index 683030c..98fe0ad 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientType.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/ClientType.java @@ -1,45 +1,43 @@ package com.acgist.taoyao.signal.client; +import com.acgist.taoyao.boot.model.MessageCodeException; + +import lombok.Getter; + /** * 终端类型 * * @author acgist */ +@Getter public enum ClientType { - /** - * 前端页面 - */ - WEB, - /** - * 媒体服务 - */ - MEDIA, - /** - * 洗衣机 - */ - WASHER, - /** - * 摄像头 - */ - CAMERA, - /** - * 冰箱 - */ - REFRIGERATOR, - /** - * 电视机 - */ - TELEVISION, - /** - * 空调 - */ - AIR_CONDITIONER, - /** - * 热水器 - */ - WATER_HEATER; + WEB("Web"), + MEDIA("媒体服务"), + CAMERA("摄像头"), + OTHER("其他终端"); + /** + * 名称 + */ private final String name; + + private ClientType(String name) { + this.name = name; + } + + /** + * @param value 类型 + * + * @return 类型 + */ + public static final ClientType of(String value) { + for (ClientType type : ClientType.values()) { + if(type.name().equalsIgnoreCase(value)) { + return type; + } + } + throw MessageCodeException.of("未知终端类型:" + value); + } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/socket/SocketClient.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/socket/SocketClient.java index 611fea8..49c1fa9 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/socket/SocketClient.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/socket/SocketClient.java @@ -9,6 +9,7 @@ import java.util.concurrent.TimeUnit; import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.model.MessageCodeException; import com.acgist.taoyao.signal.client.ClientAdapter; import lombok.Getter; @@ -25,10 +26,6 @@ import lombok.extern.slf4j.Slf4j; @Setter public class SocketClient extends ClientAdapter { - /** - * 超时时间 - */ - private final long timeout; /** * 换行符号 */ @@ -39,15 +36,10 @@ public class SocketClient extends ClientAdapter { private final int lineLength; public SocketClient(Long timeout, AsynchronousSocketChannel instance) { - super(instance); - this.timeout = timeout; + super(timeout, instance); + this.ip = this.clientIp(instance); this.line = Constant.LINE.getBytes(); this.lineLength = this.line.length; - try { - this.ip = ((InetSocketAddress) instance.getRemoteAddress()).getHostString(); - } catch (IOException e) { - log.error("Socket终端获取远程IP异常", e); - } } @Override @@ -71,4 +63,17 @@ public class SocketClient extends ClientAdapter { } } + /** + * @param instance 终端实例 + * + * @return 终端IP + */ + private String clientIp(AsynchronousSocketChannel instance) { + try { + return ((InetSocketAddress) instance.getRemoteAddress()).getHostString(); + } catch (IOException e) { + throw MessageCodeException.of(e, "无效终端(IP):" + instance); + } + } + } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/socket/SocketSignalMessageHandler.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/socket/SocketSignalMessageHandler.java index 232c477..572bfee 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/socket/SocketSignalMessageHandler.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/socket/SocketSignalMessageHandler.java @@ -71,6 +71,7 @@ public final class SocketSignalMessageHandler implements CompletionHandler { - public WebSocketClient(Session instance) { - super(instance); - final Map userProperties = instance.getUserProperties(); - this.ip = (String) userProperties.get(Constant.IP); + public WebSocketClient(long timeout, Session instance) { + super(timeout, instance); + this.ip = (String) instance.getUserProperties().get(Constant.IP); } @Override diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/websocket/WebSocketSignal.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/websocket/WebSocketSignal.java index f037752..7d69d0f 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/websocket/WebSocketSignal.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/websocket/WebSocketSignal.java @@ -2,6 +2,7 @@ package com.acgist.taoyao.signal.client.websocket; import org.springframework.beans.factory.annotation.Autowired; +import com.acgist.taoyao.boot.config.TaoyaoProperties; import com.acgist.taoyao.signal.client.ClientManager; import com.acgist.taoyao.signal.protocol.ProtocolManager; import com.acgist.taoyao.signal.protocol.platform.PlatformErrorProtocol; @@ -25,12 +26,13 @@ public class WebSocketSignal { private static ClientManager clientManager; private static ProtocolManager protocolManager; + private static TaoyaoProperties taoyaoProperties; private static PlatformErrorProtocol platformErrorProtocol; @OnOpen public void open(Session session) { log.debug("WebSocket信令终端连接成功:{}", session); - WebSocketSignal.clientManager.open(new WebSocketClient(session)); + WebSocketSignal.clientManager.open(new WebSocketClient(WebSocketSignal.taoyaoProperties.getTimeout(), session)); } @OnMessage @@ -39,7 +41,7 @@ public class WebSocketSignal { try { WebSocketSignal.protocolManager.execute(message.strip(), session); } catch (Exception e) { - log.error("处理WebSocket信令消息异常:{}", message, e); + log.error("处理WebSocket信令消息异常:{}-{}", WebSocketSignal.clientManager.clients(session), message, e); WebSocketSignal.clientManager.push(session, WebSocketSignal.platformErrorProtocol.build(e)); } } @@ -66,6 +68,11 @@ public class WebSocketSignal { WebSocketSignal.protocolManager = protocolManager; } + @Autowired + public void setTaoyaoProperties(TaoyaoProperties taoyaoProperties) { + WebSocketSignal.taoyaoProperties = taoyaoProperties; + } + @Autowired public void setPlatformErrorProtocol(PlatformErrorProtocol platformErrorProtocol) { WebSocketSignal.platformErrorProtocol = platformErrorProtocol; diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/websocket/WebSocketSignalConfigurator.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/websocket/WebSocketSignalConfigurator.java index 65fab33..1da692c 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/websocket/WebSocketSignalConfigurator.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/client/websocket/WebSocketSignalConfigurator.java @@ -1,23 +1,21 @@ package com.acgist.taoyao.signal.client.websocket; import java.lang.reflect.Field; -import java.util.Map; import org.apache.catalina.connector.RequestFacade; import com.acgist.taoyao.boot.config.Constant; +import com.acgist.taoyao.boot.model.MessageCodeException; import jakarta.websocket.HandshakeResponse; import jakarta.websocket.server.HandshakeRequest; import jakarta.websocket.server.ServerEndpointConfig; -import lombok.extern.slf4j.Slf4j; /** * WebSocket信令配置 * * @author acgist */ -@Slf4j public class WebSocketSignalConfigurator extends ServerEndpointConfig.Configurator { @Override @@ -25,11 +23,9 @@ public class WebSocketSignalConfigurator extends ServerEndpointConfig.Configurat try { final Field field = request.getClass().getDeclaredField(Constant.REQUEST); field.setAccessible(true); - final RequestFacade requestFacade = (RequestFacade) field.get(request); - final Map userProperties = config.getUserProperties(); - userProperties.put(Constant.IP, requestFacade.getRemoteAddr()); + config.getUserProperties().put(Constant.IP, ((RequestFacade) field.get(request)).getRemoteAddr()); } catch (SecurityException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { - log.error("WebSocket终端获取远程IP异常", e); + throw MessageCodeException.of(e, "无效终端(IP):" + request); } super.modifyHandshake(config, request, response); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/MediaClientAutoConfiguration.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/MediaClientAutoConfiguration.java deleted file mode 100644 index 21c2d75..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/MediaClientAutoConfiguration.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.acgist.taoyao.signal.configuration; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import com.acgist.taoyao.boot.runner.OrderedCommandLineRunner; -import com.acgist.taoyao.signal.media.MediaClientManager; - -/** - * 媒体服务自动配置 - * - * @author acgist - */ -@Configuration -public class MediaClientAutoConfiguration { - - @Bean - @Autowired - public CommandLineRunner mediaCommandLineRunner(MediaClientManager mediaClientManager) { - return new OrderedCommandLineRunner() { - @Override - public void run(String ... args) throws Exception { - mediaClientManager.init(); - } - }; - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/ScriptAutoConfiguration.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/ScriptAutoConfiguration.java index 297d9ae..ea14ab3 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/ScriptAutoConfiguration.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/ScriptAutoConfiguration.java @@ -1,12 +1,11 @@ package com.acgist.taoyao.signal.configuration; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import com.acgist.taoyao.signal.protocol.media.MediaRebootProtocol; -import com.acgist.taoyao.signal.protocol.media.MediaShutdownProtocol; +import com.acgist.taoyao.boot.config.ScriptProperties; import com.acgist.taoyao.signal.protocol.platform.PlatformRebootProtocol; import com.acgist.taoyao.signal.protocol.platform.PlatformScriptProtocol; import com.acgist.taoyao.signal.protocol.platform.PlatformShutdownProtocol; @@ -18,50 +17,38 @@ import com.acgist.taoyao.signal.protocol.system.SystemShutdownProtocol; * * @author acgist */ -@Configuration +@AutoConfiguration @ConditionalOnProperty(prefix = "taoyao.script", name = "enabled", havingValue = "true", matchIfMissing = true) public class ScriptAutoConfiguration { @Bean @ConditionalOnMissingBean - public MediaRebootProtocol mediaRebootProtocol() { - return new MediaRebootProtocol(); + public SystemRebootProtocol systemRebootProtocol(ScriptProperties scriptProperties) { + return new SystemRebootProtocol(scriptProperties); } @Bean @ConditionalOnMissingBean - public MediaShutdownProtocol mediaShutdownProtocol() { - return new MediaShutdownProtocol(); + public SystemShutdownProtocol systemShutdownProtocol(ScriptProperties scriptProperties) { + return new SystemShutdownProtocol(scriptProperties); } @Bean @ConditionalOnMissingBean - public SystemRebootProtocol systemRebootProtocol() { - return new SystemRebootProtocol(); + public PlatformRebootProtocol platformRebootProtocol(ScriptProperties scriptProperties) { + return new PlatformRebootProtocol(scriptProperties); } @Bean @ConditionalOnMissingBean - public SystemShutdownProtocol systemShutdownProtocol() { - return new SystemShutdownProtocol(); + public PlatformShutdownProtocol platformShutdownProtocol(ScriptProperties scriptProperties) { + return new PlatformShutdownProtocol(scriptProperties); } @Bean @ConditionalOnMissingBean public PlatformScriptProtocol platformScriptProtocol() { - return new PlatformScriptProtocol(); - } - - @Bean - @ConditionalOnMissingBean - public PlatformRebootProtocol platformRebootProtocol() { - return new PlatformRebootProtocol(); - } - - @Bean - @ConditionalOnMissingBean - public PlatformShutdownProtocol platformShutdownProtocol() { - return new PlatformShutdownProtocol(); + return new PlatformScriptProtocol(); } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/SignalAutoConfiguration.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/SignalAutoConfiguration.java index 6321134..459f6f1 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/SignalAutoConfiguration.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/SignalAutoConfiguration.java @@ -1,9 +1,8 @@ package com.acgist.taoyao.signal.configuration; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import com.acgist.taoyao.boot.runner.OrderedCommandLineRunner; import com.acgist.taoyao.signal.protocol.ProtocolManager; @@ -13,11 +12,10 @@ import com.acgist.taoyao.signal.protocol.ProtocolManager; * * @author acgist */ -@Configuration +@AutoConfiguration public class SignalAutoConfiguration { @Bean - @Autowired public CommandLineRunner signalCommandLineRunner(ProtocolManager protocolManager) { return new OrderedCommandLineRunner() { @Override diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/SocketSignalAutoConfiguration.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/SocketSignalAutoConfiguration.java index b1eb9cf..eb4afeb 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/SocketSignalAutoConfiguration.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/SocketSignalAutoConfiguration.java @@ -1,12 +1,11 @@ package com.acgist.taoyao.signal.configuration; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import com.acgist.taoyao.boot.config.SocketProperties; import com.acgist.taoyao.boot.runner.OrderedCommandLineRunner; @@ -20,12 +19,11 @@ import com.acgist.taoyao.signal.protocol.platform.PlatformErrorProtocol; * * @author acgist */ -@Configuration +@AutoConfiguration @ConditionalOnProperty(prefix = "taoyao.socket", name = "enabled", havingValue = "true", matchIfMissing = true) public class SocketSignalAutoConfiguration { @Bean - @Autowired @ConditionalOnMissingBean public SocketSignal socketSignal( ClientManager clientManager, @@ -37,7 +35,6 @@ public class SocketSignalAutoConfiguration { } @Bean - @Autowired @ConditionalOnBean(SocketSignal.class) public CommandLineRunner socketSignalCommandLineRunner(SocketSignal socketSignal) { return new OrderedCommandLineRunner() { diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/WebSocketSignalAutoConfiguration.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/WebSocketSignalAutoConfiguration.java index eb8b86c..4a0b60b 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/WebSocketSignalAutoConfiguration.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/configuration/WebSocketSignalAutoConfiguration.java @@ -1,8 +1,8 @@ package com.acgist.taoyao.signal.configuration; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @@ -13,8 +13,8 @@ import com.acgist.taoyao.signal.client.websocket.WebSocketSignal; * * @author acgist */ -@Configuration @EnableWebSocket +@AutoConfiguration public class WebSocketSignalAutoConfiguration { @Bean diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ClientController.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ClientController.java index 14e2f87..f545987 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ClientController.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ClientController.java @@ -1,6 +1,5 @@ package com.acgist.taoyao.signal.controller; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -11,6 +10,7 @@ import com.acgist.taoyao.signal.client.ClientManager; import com.acgist.taoyao.signal.client.ClientStatus; import com.acgist.taoyao.signal.protocol.client.ClientRebootProtocol; import com.acgist.taoyao.signal.protocol.client.ClientShutdownProtocol; +import com.acgist.taoyao.signal.protocol.client.ClientWakeupProtocol; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -28,14 +28,22 @@ import io.swagger.v3.oas.annotations.tags.Tag; @RequestMapping("/client") public class ClientController { - @Autowired - private ClientManager clientManager; - @Autowired - private ClientRebootProtocol clientRebootProtocol; - @Autowired - private ClientShutdownProtocol clientShutdownProtocol; + private final ClientManager clientManager; + private final ClientWakeupProtocol clientWakeupProtocol; + private final ClientRebootProtocol clientRebootProtocol; + private final ClientShutdownProtocol clientShutdownProtocol; - @Operation(summary = "终端列表", description = "终端列表") + public ClientController( + ClientManager clientManager, ClientWakeupProtocol clientWakeupProtocol, + ClientRebootProtocol clientRebootProtocol, ClientShutdownProtocol clientShutdownProtocol + ) { + this.clientManager = clientManager; + this.clientWakeupProtocol = clientWakeupProtocol; + this.clientRebootProtocol = clientRebootProtocol; + this.clientShutdownProtocol = clientShutdownProtocol; + } + + @Operation(summary = "终端列表", description = "终端列表") @GetMapping("/list") @ApiResponse(content = @Content(schema = @Schema(implementation = ClientStatus.class))) public Message list() { @@ -49,6 +57,13 @@ public class ClientController { return Message.success(this.clientManager.status(clientId)); } + @Operation(summary = "唤醒终端", description = "唤醒终端") + @GetMapping("/wakeup/{clientId}") + public Message wakeup(@PathVariable String clientId) { + this.clientWakeupProtocol.execute(clientId); + return Message.success(); + } + @Operation(summary = "重启终端", description = "重启终端") @GetMapping("/reboot/{clientId}") public Message reboot(@PathVariable String clientId) { diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ProtocolController.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ProtocolController.java index 56648cb..97fa742 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ProtocolController.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/ProtocolController.java @@ -3,7 +3,6 @@ package com.acgist.taoyao.signal.controller; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -28,9 +27,12 @@ import lombok.extern.slf4j.Slf4j; @RequestMapping("/protocol") public class ProtocolController { - @Autowired - private ApplicationContext applicationContext; + private final ApplicationContext applicationContext; + public ProtocolController(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + @Operation(summary = "信令列表", description = "信令列表Markdown") @GetMapping("/list") public String list() { @@ -52,18 +54,11 @@ public class ProtocolController { } ``` - ### 数据流向 + ### 符号解释 ``` - 请求:终端->信令服务 || 信令服务->媒体服务 - 响应:信令服务->终端 || 服务媒体->信令服务 - 广播:信令服务-)终端 || 信令服务+)终端 - ``` - - ### 流向解释 - - ``` - -[消息类型]> 请求(单播):定向请求(单播)信令 + -[消息类型]> 异步请求 | 定向单播 + =[消息类型]> 同步请求 -[消息类型]) 全员广播:对所有的终端广播信令(排除自己) +[消息类型]) 全员广播:对所有的终端广播信令(包含自己) ``` diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/RoomController.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/RoomController.java index 6733038..b961a83 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/RoomController.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/controller/RoomController.java @@ -2,7 +2,6 @@ package com.acgist.taoyao.signal.controller; import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -10,9 +9,9 @@ import org.springframework.web.bind.annotation.RestController; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.signal.client.ClientStatus; -import com.acgist.taoyao.signal.media.Room; -import com.acgist.taoyao.signal.media.RoomManager; -import com.acgist.taoyao.signal.media.RoomStatus; +import com.acgist.taoyao.signal.terminal.media.Room; +import com.acgist.taoyao.signal.terminal.media.RoomManager; +import com.acgist.taoyao.signal.terminal.media.RoomStatus; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -30,10 +29,13 @@ import io.swagger.v3.oas.annotations.tags.Tag; @RequestMapping("/room") public class RoomController { - @Autowired - private RoomManager roomManager; + private final RoomManager roomManager; - @Operation(summary = "房间列表", description = "房间列表") + public RoomController(RoomManager roomManager) { + this.roomManager = roomManager; + } + + @Operation(summary = "房间列表", description = "房间列表") @GetMapping("/list") @ApiResponse(content = @Content(schema = @Schema(implementation = RoomStatus.class))) public Message list() { diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ApplicationEventAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ApplicationEventAdapter.java index e25ea72..1f62064 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ApplicationEventAdapter.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ApplicationEventAdapter.java @@ -5,7 +5,7 @@ import java.util.Map; import org.springframework.context.ApplicationEvent; import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.MapBodyGetter; +import com.acgist.taoyao.boot.utils.MapUtils; import lombok.Getter; @@ -15,58 +15,58 @@ import lombok.Getter; * @author acgist */ @Getter -public class ApplicationEventAdapter extends ApplicationEvent implements MapBodyGetter { +public class ApplicationEventAdapter extends ApplicationEvent { private static final long serialVersionUID = 1L; - /** - * 主体 - */ - private final Map body; /** * 消息 */ private final Message message; + /** + * 主体 + */ + private final Map body; - protected ApplicationEventAdapter(Map body, Message message, Object source) { + protected ApplicationEventAdapter(Object source, Message message, Map body) { super(source); this.body = body; this.message = message; } /** - * @see #get(Map, String) + * @see MapUtils#get(Map, String) */ public T get(String key) { - return this.get(this.body, key); + return MapUtils.get(this.body, key); } /** - * @see #get(Map, String, Object) + * @see MapUtils#get(Map, String, Object) */ public T get(String key, T defaultValue) { - return this.get(this.body, key, defaultValue); + return MapUtils.get(this.body, key, defaultValue); } /** - * @see #getLong(Map, String) + * @see MapUtils#getLong(Map, String) */ public Long getLong(String key) { - return this.getLong(this.body, key); + return MapUtils.getLong(this.body, key); } /** - * @see #getInteger(Map, String) + * @see MapUtils#getInteger(Map, String) */ public Integer getInteger(String key) { - return this.getInteger(this.body, key); + return MapUtils.getInteger(this.body, key); } /** - * @see #getBoolean(Map, String) + * @see MapUtils#getBoolean(Map, String) */ public Boolean getBoolean(String key) { - return this.getBoolean(this.body, key); + return MapUtils.getBoolean(this.body, key); } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/client/ClientCloseEvent.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientCloseEvent.java similarity index 67% rename from taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/client/ClientCloseEvent.java rename to taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientCloseEvent.java index 810898f..e50cb6a 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/client/ClientCloseEvent.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientCloseEvent.java @@ -1,7 +1,6 @@ -package com.acgist.taoyao.signal.event.client; +package com.acgist.taoyao.signal.event; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.event.ClientEventAdapter; /** * 终端关闭事件 @@ -13,7 +12,7 @@ public class ClientCloseEvent extends ClientEventAdapter { private static final long serialVersionUID = 1L; public ClientCloseEvent(Client client) { - super(null, client); + super(client); } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientEventAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientEventAdapter.java index ea343c9..369fe74 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientEventAdapter.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/ClientEventAdapter.java @@ -26,12 +26,16 @@ public abstract class ClientEventAdapter extends ApplicationEventAdapter { */ private final String clientId; - public ClientEventAdapter(Message message, Client client) { - this(Map.of(), message, client); + public ClientEventAdapter(Client client) { + this(client, null, null); } - public ClientEventAdapter(Map body, Message message, Client client) { - super(body, message, client); + public ClientEventAdapter(Client client, Message message) { + this(client, message, null); + } + + public ClientEventAdapter(Client client, Message message, Map body) { + super(client, message, body); this.client = client; this.clientId = client.clientId(); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaClientRegisterEvent.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaClientRegisterEvent.java new file mode 100644 index 0000000..e85bdef --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaClientRegisterEvent.java @@ -0,0 +1,18 @@ +package com.acgist.taoyao.signal.event; + +import com.acgist.taoyao.signal.client.Client; + +/** + * 媒体服务终端注册事件 + * + * @author acgist + */ +public class MediaClientRegisterEvent extends ClientEventAdapter { + + private static final long serialVersionUID = 1L; + + public MediaClientRegisterEvent(Client client) { + super(client); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaEventAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaEventAdapter.java deleted file mode 100644 index 02e61d2..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaEventAdapter.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.acgist.taoyao.signal.event; - -import java.util.Map; - -import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.media.MediaClient; - -import lombok.Getter; - -/** - * 媒体事件适配器 - * - * @author acgist - */ -@Getter -public class MediaEventAdapter extends ApplicationEventAdapter { - - private static final long serialVersionUID = 1L; - - /** - * 媒体服务终端 - */ - private final MediaClient mediaClient; - - public MediaEventAdapter(Message message, MediaClient mediaClient) { - this(Map.of(), message, mediaClient); - } - - public MediaEventAdapter(Map body, Message message, MediaClient mediaClient) { - super(body, message, mediaClient); - this.mediaClient = mediaClient; - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaProduceEvent.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaProduceEvent.java new file mode 100644 index 0000000..8ed19e1 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/MediaProduceEvent.java @@ -0,0 +1,30 @@ +package com.acgist.taoyao.signal.event; + +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.terminal.media.Producer; +import com.acgist.taoyao.signal.terminal.media.Room; + +import lombok.Getter; +import lombok.Setter; + +/** + * 媒体生产事件 + * + * @author acgist + */ +@Getter +@Setter +public class MediaProduceEvent extends RoomEventAdapter { + + private static final long serialVersionUID = 1L; + + private final Client client; + private final Producer producer; + + public MediaProduceEvent(Room room, Client client, Producer producer) { + super(room); + this.client = client; + this.producer = producer; + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/RoomEventAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/RoomEventAdapter.java index 0417d1b..0386c7f 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/RoomEventAdapter.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/RoomEventAdapter.java @@ -3,7 +3,7 @@ package com.acgist.taoyao.signal.event; import java.util.Map; import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.media.Room; +import com.acgist.taoyao.signal.terminal.media.Room; import lombok.Getter; @@ -22,12 +22,16 @@ public class RoomEventAdapter extends ApplicationEventAdapter { */ private final Room room; - public RoomEventAdapter(Message message, Room room) { - this(Map.of(), message, room); + public RoomEventAdapter(Room room) { + this(room, null, null); } - public RoomEventAdapter(Map body, Message message, Room room) { - super(body, message, room); + public RoomEventAdapter(Room room, Message message) { + this(room, message, null); + } + + public RoomEventAdapter(Room room, Message message, Map body) { + super(room, message, body); this.room = room; } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/MediaClient.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/MediaClient.java deleted file mode 100644 index acfa0b4..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/MediaClient.java +++ /dev/null @@ -1,309 +0,0 @@ -package com.acgist.taoyao.signal.media; - -import java.net.URI; -import java.net.http.WebSocket; -import java.net.http.WebSocket.Listener; -import java.nio.ByteBuffer; -import java.time.Duration; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.TaskScheduler; - -import com.acgist.taoyao.boot.annotation.Prototype; -import com.acgist.taoyao.boot.config.MediaServerProperties; -import com.acgist.taoyao.boot.config.TaoyaoProperties; -import com.acgist.taoyao.boot.model.Header; -import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.boot.model.MessageCode; -import com.acgist.taoyao.boot.model.MessageCodeException; -import com.acgist.taoyao.boot.utils.HTTPUtils; -import com.acgist.taoyao.boot.utils.JSONUtils; -import com.acgist.taoyao.signal.protocol.Protocol; -import com.acgist.taoyao.signal.protocol.ProtocolManager; -import com.acgist.taoyao.signal.protocol.media.MediaRegisterProtocol; - -import lombok.extern.slf4j.Slf4j; - -/** - * 媒体服务终端 - * - * @author acgist - */ -@Slf4j -@Prototype -public class MediaClient { - - @Autowired - private TaskScheduler taskScheduler; - @Autowired - private ProtocolManager protocolManager; - @Autowired - private TaoyaoProperties taoyaoProperties; - @Autowired - private MediaRegisterProtocol mediaRegisterProtocol; - - /** - * 最长重试周期 - */ - private static final long MAX_DURATION = 60L * 1000; - - /** - * 标识 - */ - private String mediaId; - /** - * 重试周期 - */ - private long duration; - /** - * 服务通道 - */ - private WebSocket webSocket; - /** - * 服务配置 - */ - private MediaServerProperties mediaServerProperties; - /** - * 同步消息 - */ - private final Map requestMessage = new ConcurrentHashMap<>(); - - /** - * 加载终端 - * - * @param mediaServerProperties 媒体服务配置 - */ - public void init(MediaServerProperties mediaServerProperties) { - this.mediaId = mediaServerProperties.getMediaId(); - this.duration = this.taoyaoProperties.getTimeout(); - this.mediaServerProperties = mediaServerProperties; - this.connectServer(); - } - - /** - * @return 标识 - */ - public String mediaId() { - return this.mediaId; - } - - /** - * @return 媒体服务配置 - */ - public MediaServerProperties mediaServerProperties() { - return this.mediaServerProperties; - } - - /** - * 心跳 - */ - public void heartbeat() { - final CompletableFuture future = this.webSocket.sendPing(ByteBuffer.allocate(0)); - try { - log.debug("媒体服务心跳:{}", this.mediaId); - future.get(this.taoyaoProperties.getTimeout(), TimeUnit.MILLISECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - log.error("媒体服务心跳异常:{}", this.mediaId, e); - } - } - - /** - * 发送消息 - * - * @param message 消息 - */ - public void send(Message message) { - while(this.webSocket == null) { - Thread.yield(); - } - this.webSocket.sendText(JSONUtils.toJSON(message), true); - } - - /** - * 请求消息 - * - * @param request 消息 - * - * @return 响应 - */ - public Message request(Message request) { - final Header header = request.getHeader(); - final String id = header.getId(); - this.requestMessage.put(id, request); - synchronized (request) { - this.send(request); - try { - request.wait(this.taoyaoProperties.getTimeout()); - } catch (InterruptedException e) { - log.error("媒体服务等待响应异常:{}", request, e); - } - } - final Message response = this.requestMessage.remove(id); - if(response == null || request.equals(response)) { - log.warn("媒体服务没有响应:{}", request); - throw MessageCodeException.of(MessageCode.CODE_2001, "媒体服务没有响应"); - } - return response; - } - - /** - * @return 重试周期 - */ - private long retryDuration() { - return this.duration = Math.min(this.duration + this.taoyaoProperties.getTimeout(), MAX_DURATION); - } - - /** - * 连接服务通道 - */ - private void connectServer() { - final URI uri = URI.create(this.mediaServerProperties.getAddress()); - log.debug("开始连接媒体服务:{}", uri); - try { - final WebSocket webSocket = HTTPUtils.newClient() - .newWebSocketBuilder() - .connectTimeout(Duration.ofMillis(this.taoyaoProperties.getTimeout())) - .buildAsync(uri, new MessageListener()) - .get(); - log.info("连接媒体服务成功:{}", webSocket); - // 关闭旧的通道 - if(this.webSocket != null && !(this.webSocket.isInputClosed() && this.webSocket.isOutputClosed())) { - this.webSocket.abort(); - } - // 设置新的通道 - this.webSocket = webSocket; - // 重置重试周期 - this.duration = this.taoyaoProperties.getTimeout(); - // 发送授权消息 - this.send(this.mediaRegisterProtocol.build(this.mediaServerProperties)); - } catch (Exception e) { - log.error("连接媒体服务异常:{}", uri, e); - this.taskScheduler.schedule( - this::connectServer, - Instant.now().plusMillis(this.retryDuration()) - ); - } - } - - /** - * 处理信令消息 - * - * @param content 信令消息 - */ - private void execute(String content) { - if(StringUtils.isEmpty(content)) { - log.warn("媒体服务信令消息格式错误:{}", content); - return; - } - final Message message = JSONUtils.toJava(content, Message.class); - final Header header = message.getHeader(); - if(header == null) { - log.warn("媒体服务信令消息格式错误(没有头部):{}", content); - return; - } - final String v = header.getV(); - final String id = header.getId(); - final String signal = header.getSignal(); - if(v == null || id == null || signal == null) { - log.warn("媒体服务信令消息格式错误(缺失头部关键参数):{}", content); - return; - } - final Message request = this.requestMessage.get(id); - if(request != null) { - // 同步处理:重新设置响应消息 - this.requestMessage.put(id, message); - // 唤醒等待线程 - synchronized (request) { - request.notifyAll(); - } - // 同步处理不要执行回调 - } else { - final Protocol protocol = this.protocolManager.protocol(signal); - if(protocol == null) { - log.warn("不支持的媒体信令协议:{}", content); - } else { - protocol.execute(this, message); - } - } - } - - /** - * 信令消息监听 - * - * @author acgist - */ - public class MessageListener implements Listener { - - @Override - public void onOpen(WebSocket webSocket) { - log.info("媒体服务通道打开:{}", webSocket); - Listener.super.onOpen(webSocket); - } - - @Override - public CompletionStage onBinary(WebSocket webSocket, ByteBuffer buffer, boolean last) { - log.debug("媒体服务收到信令消息(binary):{}", webSocket); - return Listener.super.onBinary(webSocket, buffer, last); - } - - @Override - public CompletionStage onText(WebSocket webSocket, CharSequence content, boolean last) { - log.debug("媒体服务收到信令消息(text):{}-{}", webSocket, content); - try { - MediaClient.this.execute(content.toString()); - } catch (Exception e) { - log.error("处理媒体服务信令消息异常:{}", content, e); - } - return Listener.super.onText(webSocket, content, last); - } - - @Override - public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { - log.warn("媒体服务通道关闭:{}-{}-{}", webSocket, statusCode, reason); - try { - return Listener.super.onClose(webSocket, statusCode, reason); - } finally { - MediaClient.this.taskScheduler.schedule( - MediaClient.this::connectServer, - Instant.now().plusMillis(MediaClient.this.retryDuration()) - ); - } - } - - @Override - public void onError(WebSocket webSocket, Throwable error) { - log.error("媒体服务通道异常:{}", webSocket, error); - try { - Listener.super.onError(webSocket, error); - } finally { - MediaClient.this.taskScheduler.schedule( - MediaClient.this::connectServer, - Instant.now().plusMillis(MediaClient.this.retryDuration()) - ); - } - } - - @Override - public CompletionStage onPing(WebSocket webSocket, ByteBuffer buffer) { - log.debug("媒体服务收到信令消息(ping):{}", webSocket); - return Listener.super.onPing(webSocket, buffer); - } - - @Override - public CompletionStage onPong(WebSocket webSocket, ByteBuffer buffer) { - log.debug("媒体服务收到信令消息(pong):{}", webSocket); - return Listener.super.onPong(webSocket, buffer); - } - - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/MediaClientManager.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/MediaClientManager.java deleted file mode 100644 index e1054a9..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/MediaClientManager.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.acgist.taoyao.signal.media; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.scheduling.annotation.Scheduled; - -import com.acgist.taoyao.boot.annotation.Manager; -import com.acgist.taoyao.boot.config.MediaProperties; - -import lombok.extern.slf4j.Slf4j; - -/** - * 媒体服务终端管理 - * - * @author acgist - */ -@Slf4j -@Manager -public class MediaClientManager { - - @Autowired - private MediaProperties mediaProperties; - @Autowired - private ApplicationContext applicationContext; - - /** - * 媒体服务终端列表 - */ - private Map clientMap = new ConcurrentHashMap<>(); - - @Scheduled(cron = "${taoyao.scheduled.media:0 * * * * ?}") - public void scheduled() { - this.heartbeat(); - } - - /** - * 加载媒体服务终端 - */ - public void init() { - this.mediaProperties.getMediaServerList().stream() - .filter(v -> Boolean.TRUE.equals(v.getEnabled())) - .forEach(v -> { - final MediaClient client = this.applicationContext.getBean(MediaClient.class); - client.init(v); - this.clientMap.put(client.mediaId(), client); - log.info("注册媒体服务终端:{}-{}", v.getMediaId(), v.getAddress()); - }); - } - - /** - * @param name 媒体服务终端名称 - * - * @return 媒体服务终端 - */ - public MediaClient mediaClient(String name) { - return this.clientMap.get(name); - } - - /** - * 心跳 - */ - private void heartbeat() { - this.clientMap.forEach((k, v) -> { - v.heartbeat(); - }); - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Peer.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Peer.java deleted file mode 100644 index a98564d..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Peer.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.acgist.taoyao.signal.media; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import com.acgist.taoyao.signal.client.Client; - -import lombok.Getter; -import lombok.Setter; - -/** - * Peer - * - * @author acgist - */ -@Getter -@Setter -public class Peer { - - /** - * 终端 - */ - private final Client client; - private String device; - private String produce; - private String consume; - private Object rtpCapabilities; - private Object sctpCapabilities; - private Map transports; - private Map producers; - private Map consumers; - private Map dataProducers; - private Map dataConsumers; - - public Peer(Client client) { - this.client = client; - this.transports = new ConcurrentHashMap<>(); - this.producers = new ConcurrentHashMap<>(); - this.consumers = new ConcurrentHashMap<>(); - this.dataProducers = new ConcurrentHashMap<>(); - this.dataConsumers = new ConcurrentHashMap<>(); - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Room.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Room.java deleted file mode 100644 index 5868c23..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Room.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.acgist.taoyao.signal.media; - -import java.io.Closeable; -import java.util.List; -import java.util.Objects; - -import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.client.ClientStatus; - -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; - -/** - * 房间 - * - * @author acgist - */ -@Slf4j -@Getter -@Setter -public class Room implements Closeable { - - /** - * 房间标识 - */ - private String roomId; - /** - * 密码 - * 设置密码之后进入房间需要验证密码 - */ - private String password; - /** - * 状态 - */ - private RoomStatus status; - /** - * 媒体服务 - */ - private MediaClient mediaClient; - /** - * 终端列表 - */ - private List clients; - /** - * 传输通道列表 - * TODO:是否需要 - */ - private List transports; - - /** - * @return 终端状态列表 - */ - public List clientStatus() { - return this.clients.stream() - .map(Client::status) - .toList(); - } - - /** - * 终端进入 - * - * @param client 终端 - */ - public void enter(Client client) { - synchronized (this.clients) { - if(this.clients.contains(client)) { - return; - } - if(this.clients.add(client)) { - this.status.setClientSize(this.status.getClientSize() + 1); - } - } - } - - /** - * 终端离开 - * - * @param client 终端 - */ - public void leave(Client client) { - synchronized (this.clients) { - if(this.clients.remove(client)) { - this.status.setClientSize(this.status.getClientSize() - 1); - } - } - } - - /** - * 广播消息 - * - * @param message 消息 - */ - public void broadcast(Message message) { - this.clients.forEach(v -> v.push(message)); - } - - /** - * 广播消息 - * - * @param from 发送终端 - * @param message 消息 - */ - public void broadcast(String from, Message message) { - this.clients.stream() - .filter(v -> !Objects.equals(from, v.clientId())) - .forEach(v -> v.push(message)); - } - - /** - * 广播消息 - * - * @param from 发送终端 - * @param message 消息 - */ - public void broadcast(Client from, Message message) { - this.clients.stream() - .filter(v -> v != from) - .forEach(v -> v.push(message)); - } - - /** - * @see MediaClient#send(Message) - */ - public void send(Message message) { - this.mediaClient.send(message); - } - - /** - * @see MediaClient#request(Message) - */ - public Message request(Message message) { - return this.mediaClient.request(message); - } - - @Override - public void close() { - log.info("关闭房间:{}", this.roomId); - this.mediaClient.send(null); - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Transport.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Transport.java deleted file mode 100644 index 6291e23..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/Transport.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.acgist.taoyao.signal.media; - -import java.io.Closeable; -import java.util.Map; - -import com.acgist.taoyao.boot.config.Constant; -import com.acgist.taoyao.signal.MapBodyGetter; -import com.acgist.taoyao.signal.client.Client; - -import lombok.Getter; -import lombok.Setter; - -/** - * 传输通道 - * - * @author acgist - */ -@Getter -@Setter -public class Transport implements Closeable, MapBodyGetter { - - /** - * 终端 - */ - private final Client client; - private String transportId; - private Object iceCandidates; - private Object iceParameters; - private Object dtlsParameters; - private Object sctpParameters; - - public Transport(Client client) { - this.client = client; - } - - /** - * 拷贝属性 - * - * @param body 消息主体 - */ - public void copy(Map body) { - this.transportId = this.get(body, Constant.TRANSPORT_ID); - this.iceCandidates = this.get(body, Constant.ICE_CANDIDATES); - this.iceParameters = this.get(body, Constant.ICE_PARAMETERS); - this.dtlsParameters = this.get(body, Constant.DTLS_PARAMETERS); - this.sctpParameters = this.get(body, Constant.SCTP_PARAMETERS); - } - - @Override - public void close() { - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java index 85f3eee..c9d8fe8 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java @@ -4,7 +4,6 @@ import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.model.MessageCode; import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.event.ApplicationEventAdapter; -import com.acgist.taoyao.signal.media.MediaClient; /** * 信令 @@ -13,6 +12,7 @@ import com.acgist.taoyao.signal.media.MediaClient; * media:: 媒体信令 * client:: 终端信令 * system:: 系统信令 + * control:: 控制信令 * platform:: 平台信令 * * @author acgist @@ -40,14 +40,6 @@ public interface Protocol { return true; } - /** - * 处理媒体服务信令 - * - * @param mediaClient 媒体服务终端 - * @param message 信令消息 - */ - void execute(MediaClient mediaClient, Message message); - /** * 处理终端信令 * @@ -110,6 +102,6 @@ public interface Protocol { * * @return 信令消息 */ - Message build(String id, MessageCode code, String message, Object body); + Message build(Long id, MessageCode code, String message, Object body); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java index e55afd9..238fac9 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java @@ -1,6 +1,5 @@ package com.acgist.taoyao.signal.protocol; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -9,18 +8,19 @@ import com.acgist.taoyao.boot.model.Header; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.model.MessageCode; import com.acgist.taoyao.boot.service.IdService; -import com.acgist.taoyao.signal.MapBodyGetter; import com.acgist.taoyao.signal.client.ClientManager; import com.acgist.taoyao.signal.event.ApplicationEventAdapter; -import com.acgist.taoyao.signal.media.MediaClientManager; -import com.acgist.taoyao.signal.media.RoomManager; +import com.acgist.taoyao.signal.terminal.media.RoomManager; + +import lombok.extern.slf4j.Slf4j; /** * 信令适配器 * * @author acgist */ -public abstract class ProtocolAdapter implements Protocol, MapBodyGetter { +@Slf4j +public abstract class ProtocolAdapter implements Protocol { @Autowired protected IdService idService; @@ -32,8 +32,6 @@ public abstract class ProtocolAdapter implements Protocol, MapBodyGetter { protected TaoyaoProperties taoyaoProperties; @Autowired protected ApplicationContext applicationContext; - @Autowired - protected MediaClientManager mediaClientManager; /** * 信令名称 @@ -90,10 +88,10 @@ public abstract class ProtocolAdapter implements Protocol, MapBodyGetter { } @Override - public Message build(String id, MessageCode code, String message, Object body) { + public Message build(Long id, MessageCode code, String message, Object body) { // 消息标识 - if(StringUtils.isEmpty(id)) { - id = this.idService.buildIdToString(); + if(id == null) { + id = this.idService.buildId(); } // 消息头部 final Header header = Header.builder() @@ -111,4 +109,17 @@ public abstract class ProtocolAdapter implements Protocol, MapBodyGetter { return build; } + /** + * @param args 参数 + */ + protected void logNoAdapter(Object ... args) { + final StringBuilder builder = new StringBuilder(this.name); + builder.append("没有适配信令消息:"); + for (final Object object : args) { + builder.append(object).append(" "); + } + builder.setLength(builder.length() - 1); + log.debug(builder.toString()); + } + } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolClientAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolClientAdapter.java index 5549daa..1ce223e 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolClientAdapter.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolClientAdapter.java @@ -3,9 +3,8 @@ package com.acgist.taoyao.signal.protocol; import java.util.Map; import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.boot.model.MessageCodeException; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.MediaClient; +import com.acgist.taoyao.signal.client.ClientType; /** * 终端信令适配器 @@ -17,32 +16,22 @@ public abstract class ProtocolClientAdapter extends ProtocolAdapter { protected ProtocolClientAdapter(String name, String signal) { super(name, signal); } - - @Override - public void execute(MediaClient mediaClient, Message message) { - } @Override public void execute(Client client, Message message) { - final Object body = message.getBody(); - if(body instanceof Map map) { - this.execute(client.clientId(), map, client, message); - } else if(body == null) { - this.execute(client.clientId(), Map.of(), client, message); - } else { - throw MessageCodeException.of("信令主体类型错误:" + message); - } + this.execute(client.clientId(), client.clientType(), client, message, message.mapBody()); } /** * 处理终端信令 * * @param clientId 终端标识 - * @param body 消息主体 + * @param clientType 终端类型 * @param client 终端 - * @param message 信令消息 + * @param message 消息 + * @param body 消息主体 */ - public void execute(String clientId, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolControlAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolControlAdapter.java new file mode 100644 index 0000000..9795367 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolControlAdapter.java @@ -0,0 +1,47 @@ +package com.acgist.taoyao.signal.protocol; + +import java.util.Map; + +import com.acgist.taoyao.boot.config.Constant; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.model.MessageCodeException; +import com.acgist.taoyao.boot.utils.MapUtils; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; + +/** + * 控制信令适配器 + * + * @author acgist + */ +public class ProtocolControlAdapter extends ProtocolClientAdapter { + + protected ProtocolControlAdapter(String name, String signal) { + super(name, signal); + } + + @Override + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { + final String to = MapUtils.get(body, Constant.TO); + final Client targetClient = this.clientManager.clients(to); + if(targetClient == null) { + throw MessageCodeException.of("目标终端无效:" + to); + } + this.execute(clientId, clientType, client, targetClient, message, body); + } + + /** + * 处理终端控制信令 + * + * @param clientId 终端标识 + * @param clientType 终端类型 + * @param room 房间 + * @param client 终端 + * @param targetClient 目标 + * @param message 消息 + * @param body 消息主体 + */ + public void execute(String clientId, ClientType clientType, Client client, Client targetClient, Message message, Map body) { + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java index 180b321..2c76690 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java @@ -3,7 +3,6 @@ package com.acgist.taoyao.signal.protocol; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import com.acgist.taoyao.boot.annotation.Manager; @@ -28,20 +27,24 @@ import lombok.extern.slf4j.Slf4j; @Slf4j @Manager public class ProtocolManager { + + private final ClientManager clientManager; + private final SecurityService securityService; + private final ApplicationContext applicationContext; + private final PlatformErrorProtocol platformErrorProtocol; + + /** + * 信令映射 + */ + private final Map protocolMapping; - /** - * 信令映射 - */ - private Map protocolMapping = new ConcurrentHashMap<>(); - - @Autowired - private ClientManager clientManager; - @Autowired - private SecurityService securityService; - @Autowired - private ApplicationContext applicationContext; - @Autowired - private PlatformErrorProtocol platformErrorProtocol; + public ProtocolManager(ClientManager clientManager, SecurityService securityService, ApplicationContext applicationContext, PlatformErrorProtocol platformErrorProtocol) { + this.clientManager = clientManager; + this.securityService = securityService; + this.applicationContext = applicationContext; + this.platformErrorProtocol = platformErrorProtocol; + this.protocolMapping = new ConcurrentHashMap<>(); + } /** * 加载信令映射 @@ -79,7 +82,7 @@ public class ProtocolManager { */ public void execute(String content, AutoCloseable instance) { log.debug("执行信令消息:{}", content); - final Client client = this.clientManager.client(instance); + final Client client = this.clientManager.clients(instance); if(client == null) { log.warn("信令终端无效:{}-{}", instance, content); return; @@ -98,7 +101,7 @@ public class ProtocolManager { return; } final String v = header.getV(); - final String id = header.getId(); + final Long id = header.getId(); final String signal = header.getSignal(); // 设置缓存ID this.platformErrorProtocol.set(id); @@ -117,7 +120,11 @@ public class ProtocolManager { if(protocol instanceof ClientRegisterProtocol) { protocol.execute(client, message); } else if(this.securityService.authenticate(client, message, protocol)) { - protocol.execute(client, message); + if(client.response(id, message)) { + // 响应消息不做其他处理 + } else { + protocol.execute(client, message); + } } else { log.warn("终端没有授权:{}", content); client.push(this.platformErrorProtocol.build(MessageCode.CODE_3401, "终端没有授权")); diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolMediaAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolMediaAdapter.java deleted file mode 100644 index a2e4765..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolMediaAdapter.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.acgist.taoyao.signal.protocol; - -import java.util.Map; - -import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.boot.model.MessageCodeException; -import com.acgist.taoyao.signal.media.MediaClient; - -/** - * 媒体服务信令适配器 - * - * @author acgist - */ -public abstract class ProtocolMediaAdapter extends ProtocolClientAdapter { - - protected ProtocolMediaAdapter(String name, String signal) { - super(name, signal); - } - - /** - * 处理媒体服务信令 - * - * @param mediaClient 媒体服务终端 - * @param message 信令消息 - */ - public void execute(MediaClient mediaClient, Message message) { - final Object body = message.getBody(); - if(body instanceof Map map) { - this.execute(map, mediaClient, message); - } else if(body == null) { - this.execute(Map.of(), mediaClient, message); - } else { - throw MessageCodeException.of("信令主体类型错误:" + message); - } - } - - /** - * 处理媒体服务信令 - * - * @param body 消息主体 - * @param mediaClient 媒体服务终端 - * @param message 信令消息 - */ - public void execute(Map body, MediaClient mediaClient, Message message) { - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolRoomAdapter.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolRoomAdapter.java index dc3514d..6698a90 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolRoomAdapter.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolRoomAdapter.java @@ -5,68 +5,45 @@ import java.util.Map; import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.model.MessageCodeException; +import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.MediaClient; -import com.acgist.taoyao.signal.media.Room; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.terminal.media.Room; /** * 房间信令适配器 * - * TODO:校验是否是房间内的用户权限 - * * @author acgist */ -public abstract class ProtocolRoomAdapter extends ProtocolMediaAdapter { +public abstract class ProtocolRoomAdapter extends ProtocolClientAdapter { protected ProtocolRoomAdapter(String name, String signal) { super(name, signal); } @Override - public void execute(Map body, MediaClient mediaClient, Message message) { - this.execute(this.room(body), body, mediaClient, message); - } - - @Override - public void execute(String clientId, Map body, Client client, Message message) { - this.execute(clientId, this.room(body), body, client, message); - } - - /** - * @param body 消息主体 - * - * @return 房间 - */ - protected Room room(Map body) { - final String roomId = this.get(body, Constant.ROOM_ID); - final Room room = this.roomManager.room(roomId); - if(room == null) { - throw MessageCodeException.of("无效房间:" + roomId); - } - return room; - } - - /** - * 处理媒体服务房间信令 - * - * @param room 房间 - * @param body 消息主体 - * @param mediaClient 媒体服务终端 - * @param message 信令消息 - */ - public void execute(Room room, Map body, MediaClient mediaClient, Message message) { + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { + final String roomId = MapUtils.get(body, Constant.ROOM_ID); + final Room room = this.roomManager.room(roomId); + if(room == null) { + throw MessageCodeException.of("无效房间:" + roomId); + } + // TODO:验证用户是否在房间里面 + this.execute(clientId, clientType, room, client, room.getMediaClient(), message, body); } /** * 处理终端房间信令 * * @param clientId 终端标识 + * @param clientType 终端类型 * @param room 房间 - * @param body 消息主体 * @param client 终端 - * @param message 信令消息 + * @param mediaClient 媒体服务终端 + * @param message 消息 + * @param body 消息主体 */ - public void execute(String clientId, Room room, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientAlarmProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientAlarmProtocol.java new file mode 100644 index 0000000..ab526ee --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientAlarmProtocol.java @@ -0,0 +1,54 @@ +package com.acgist.taoyao.signal.protocol.client; + +import java.util.Map; + +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.config.Constant; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.utils.MapUtils; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; + +import lombok.extern.slf4j.Slf4j; + +/** + * 终端告警信令 + * + * @author acgist + */ +@Slf4j +@Protocol +@Description( + body = """ + { + "message": "告警描述", + "datetime": "告警时间(yyyyMMddHHmmss)" + } + """, + flow = "终端->信令服务" +) +public class ClientAlarmProtocol extends ProtocolClientAdapter { + + public static final String SIGNAL = "client::alarm"; + + public ClientAlarmProtocol() { + super("终端告警信令", SIGNAL); + } + + @Override + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { + log.warn( + """ + 终端发生告警:{} + {} + {} + """, + clientId, + MapUtils.get(body, Constant.MESSAGE), + MapUtils.get(body, Constant.DATETIME) + ); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientBroadcastProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientBroadcastProtocol.java index 343be5b..1dfee75 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientBroadcastProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientBroadcastProtocol.java @@ -2,10 +2,15 @@ package com.acgist.taoyao.signal.protocol.client; import java.util.Map; +import org.apache.commons.lang3.StringUtils; + import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -14,7 +19,16 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; * @author acgist */ @Protocol -@Description(flow = "终端->信令服务-)终端") +@Description( + body = { + """ + { + "clientType": "终端类型(可选)" + } + """ + }, + flow = "终端->信令服务-)终端" +) public class ClientBroadcastProtocol extends ProtocolClientAdapter { public static final String SIGNAL = "client::broadcast"; @@ -24,8 +38,13 @@ public class ClientBroadcastProtocol extends ProtocolClientAdapter { } @Override - public void execute(String clientId, Map body, Client client, Message message) { - this.clientManager.broadcast(client, message); + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { + final String queryClientType = MapUtils.get(body, Constant.CLIENT_TYPE); + if(StringUtils.isEmpty(queryClientType)) { + this.clientManager.broadcast(client, message); + } else { + this.clientManager.broadcast(client, message, ClientType.of(queryClientType)); + } } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientCloseProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientCloseProtocol.java index e78b46f..098d092 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientCloseProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientCloseProtocol.java @@ -2,15 +2,16 @@ package com.acgist.taoyao.signal.protocol.client; import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; +import org.springframework.scheduling.annotation.Async; import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.event.client.ClientCloseEvent; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.event.ClientCloseEvent; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; import lombok.extern.slf4j.Slf4j; @@ -23,29 +24,33 @@ import lombok.extern.slf4j.Slf4j; @Slf4j @Protocol @Description( - flow = { "终端->信令服务->终端", "终端->信令服务-[终端下线])终端" } + flow = { + "终端->信令服务->终端", + "终端->信令服务-[终端下线])终端" + } ) public class ClientCloseProtocol extends ProtocolClientAdapter implements ApplicationListener { public static final String SIGNAL = "client::close"; - @Autowired - private ClientOfflineProtocol offlineProtocol; + private final ClientOfflineProtocol clientOfflineProtocol; - public ClientCloseProtocol() { + public ClientCloseProtocol(ClientOfflineProtocol clientOfflineProtocol) { super("终端关闭信令", SIGNAL); + this.clientOfflineProtocol = clientOfflineProtocol; } + @Async @Override public void onApplicationEvent(ClientCloseEvent event) { this.close(event.getClient()); } @Override - public void execute(String clientId, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { // 响应消息 client.push(message.cloneWithoutBody()); - // 不用发布事件:关闭连接后会发布事件 + // 关闭连接后会发布事件 try { client.close(); } catch (Exception e) { @@ -68,7 +73,7 @@ public class ClientCloseProtocol extends ProtocolClientAdapter implements Applic // 房间释放 this.roomManager.leave(client); // 广播下线事件 - final Message message = this.offlineProtocol.build( + final Message message = this.clientOfflineProtocol.build( Map.of(Constant.CLIENT_ID, clientId) ); this.clientManager.broadcast(clientId, message); diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientConfigProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientConfigProtocol.java index 0c5db42..e42a169 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientConfigProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientConfigProtocol.java @@ -1,22 +1,20 @@ package com.acgist.taoyao.signal.protocol.client; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; - import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.config.MediaProperties; import com.acgist.taoyao.boot.config.WebrtcProperties; import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.boot.service.IpService; import com.acgist.taoyao.boot.utils.DateUtils; import com.acgist.taoyao.boot.utils.DateUtils.DateTimeStyle; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; -import com.acgist.taoyao.signal.wrapper.WebrtcPropertiesWrapper; /** * 终端配置信令 @@ -27,8 +25,8 @@ import com.acgist.taoyao.signal.wrapper.WebrtcPropertiesWrapper; @Description( body = """ { - "media": "媒体配置", - "webrtc": "WebRTC配置", + "media": "媒体配置(可选)", + "webrtc": "WebRTC配置(可选)", "datetime": "日期时间(yyyyMMddHHmmss)" } """, @@ -38,36 +36,38 @@ public class ClientConfigProtocol extends ProtocolClientAdapter { public static final String SIGNAL = "client::config"; - @Autowired - private IpService ipService; - @Autowired - private MediaProperties mediaProperties; - @Autowired - private WebrtcProperties webrtcProperties; + private final MediaProperties mediaProperties; + private final WebrtcProperties webrtcProperties; - public ClientConfigProtocol() { + public ClientConfigProtocol(MediaProperties mediaProperties, WebrtcProperties webrtcProperties) { super("终端配置信令", SIGNAL); + this.mediaProperties = mediaProperties; + this.webrtcProperties = webrtcProperties; } @Override - public void execute(String clientId, Map body, Client client, Message message) { - client.push(this.build(client)); + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { + client.push(this.build(clientType)); } /** - * @param client 终端 + * @param clientType 终端类型 * - * @return 信令消息 + * @return 消息 */ - public Message build(Client client) { - final String clientIp = client.ip(); - final Message message = this.build(); - final WebrtcPropertiesWrapper webrtcPropertiesWrapper = new WebrtcPropertiesWrapper(clientIp, this.ipService, this.webrtcProperties); - message.setBody(Map.of( - Constant.MEDIA, this.mediaProperties, - Constant.WEBRTC, webrtcPropertiesWrapper, - Constant.DATETIME, DateUtils.format(LocalDateTime.now(), DateTimeStyle.YYYYMMDDHH24MMSS) - )); + public Message build(ClientType clientType) { + final Message message = super.build(); + final Map config = new HashMap<>(); + // 日期时间 + config.put(Constant.DATETIME, DateUtils.format(LocalDateTime.now(), DateTimeStyle.YYYYMMDDHH24MMSS)); + // Web、摄像头:媒体配置 + if(clientType == ClientType.WEB || clientType == ClientType.CAMERA) { + config.put(Constant.MEDIA, this.mediaProperties); + config.put(Constant.WEBRTC, this.webrtcProperties); + } else { + this.logNoAdapter(clientType); + } + message.setBody(config); return message; } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java index 9f5bd29..c5623be 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientHeartbeatProtocol.java @@ -1,14 +1,13 @@ package com.acgist.taoyao.signal.protocol.client; -import java.time.LocalDateTime; import java.util.Map; import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; -import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.client.ClientStatus; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -20,9 +19,17 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; @Description( body = """ { + "latitude": 纬度, + "longitude": 经度, + "humidity": 湿度, + "temperature": 温度, "signal": 信号强度(0~100), "battery": 电池电量(0~100), - "charging": 是否正在充电(true|false) + "running": 是否正在运行(true|false), + "charging": 是否正在充电(true|false), + "recording": 是否正在录像(true|false), + "status": {更多状态}, + "config": {更多配置} } """, flow = "终端->信令服务->终端" @@ -36,15 +43,12 @@ public class ClientHeartbeatProtocol extends ProtocolClientAdapter { } @Override - public void execute(String clientId, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { // 响应心跳 client.push(message.cloneWithoutBody()); // 设置状态 final ClientStatus status = client.status(); - status.setSignal(this.get(body, Constant.SIGNAL)); - status.setBattery(this.get(body, Constant.BATTERY)); - status.setCharging(this.get(body, Constant.CHARGING)); - status.setLastHeartbeat(LocalDateTime.now()); + status.copy(body); } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientListProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientListProtocol.java index a34dac2..f2c89b8 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientListProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientListProtocol.java @@ -2,10 +2,15 @@ package com.acgist.taoyao.signal.protocol.client; import java.util.Map; +import org.apache.commons.lang3.StringUtils; + import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -16,15 +21,26 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; @Protocol @Description( body = """ + { + "clientType": "终端类型(可选)" + } [ { - "clientId": "终端标识", "ip": "终端IP", + "name": "终端名称", + "clientId": "终端标识", + "clientType": "终端类型", + "latitude": 纬度, + "longitude": 经度, + "humidity": 湿度, + "temperature": 温度, "signal": 信号强度(0~100), "battery": 电池电量(0~100), + "running": 是否正在运行(true|false), "charging": 是否正在充电(true|false), - "mediaId": "媒体服务标识", - "lastHeartbeat": "最后心跳时间" + "recording": 是否正在录像(true|false), + "status": {更多状态}, + "config": {更多配置} }, ... ] @@ -40,8 +56,13 @@ public class ClientListProtocol extends ProtocolClientAdapter { } @Override - public void execute(String clientId, Map body, Client client, Message message) { - message.setBody(this.clientManager.status()); + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { + final String queryClientType = MapUtils.get(body, Constant.CLIENT_TYPE); + if(StringUtils.isEmpty(queryClientType)) { + message.setBody(this.clientManager.status()); + } else { + message.setBody(this.clientManager.status(ClientType.of(queryClientType))); + } client.push(message); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOfflineProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOfflineProtocol.java index dac05c6..6dd6901 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOfflineProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOfflineProtocol.java @@ -1,11 +1,7 @@ package com.acgist.taoyao.signal.protocol.client; -import java.util.Map; - import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; -import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -30,9 +26,4 @@ public class ClientOfflineProtocol extends ProtocolClientAdapter { super("终端下线信令", SIGNAL); } - @Override - public void execute(String clientId, Map body, Client client, Message message) { - // 忽略 - } - } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOnlineProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOnlineProtocol.java index 122da92..33b3d82 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOnlineProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientOnlineProtocol.java @@ -1,11 +1,7 @@ package com.acgist.taoyao.signal.protocol.client; -import java.util.Map; - import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; -import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -18,12 +14,20 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; body = """ { "ip": "终端IP", - "mediaId": "媒体服务标识", + "name": "终端名称", "clientId": "终端标识", + "clientType": "终端类型", + "latitude": 纬度, + "longitude": 经度, + "humidity": 湿度, + "temperature": 温度, "signal": 信号强度(0~100), "battery": 电池电量(0~100), + "running": 是否正在运行(true|false), "charging": 是否正在充电(true|false), - "lastHeartbeat": "最后心跳时间" + "recording": 是否正在录像(true|false), + "status": {更多状态}, + "config": {更多配置} } """, flow = "终端-[终端注册]>信令服务-)终端" @@ -36,9 +40,4 @@ public class ClientOnlineProtocol extends ProtocolClientAdapter { super("终端上线信令", SIGNAL); } - @Override - public void execute(String clientId, Map body, Client client, Message message) { - // 忽略 - } - } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientRegisterProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientRegisterProtocol.java index 1a3ef06..ea5764c 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientRegisterProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientRegisterProtocol.java @@ -1,19 +1,18 @@ package com.acgist.taoyao.signal.protocol.client; -import java.time.LocalDateTime; import java.util.Map; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; - import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.model.MessageCode; import com.acgist.taoyao.boot.model.MessageCodeException; +import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.client.ClientStatus; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.event.MediaClientRegisterEvent; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; import com.acgist.taoyao.signal.service.SecurityService; @@ -30,62 +29,96 @@ import lombok.extern.slf4j.Slf4j; @Description( body = """ { - "clientId": "终端标识", "username": "信令用户", "password": "信令密码", - "ip": "终端IP(选填)", + "name": "终端名称", + "clientId": "终端标识", + "clientType": "终端类型", + "latitude": 纬度, + "longitude": 经度, + "humidity": 湿度, + "temperature": 温度, "signal": 信号强度(0~100), "battery": 电池电量(0~100), - "charging": 是否正在充电(true|false) + "running": 是否正在运行(true|false), + "charging": 是否正在充电(true|false), + "recording": 是否正在录像(true|false), + "status": {更多状态}, + "config": {更多配置} } """, - flow = { "终端->信令服务->终端", "终端->信令服务-[终端上线])终端" } + flow = { + "终端->信令服务->终端", + "终端->信令服务-[终端上线])终端" + } ) public class ClientRegisterProtocol extends ProtocolClientAdapter { public static final String SIGNAL = "client::register"; - @Autowired - private SecurityService securityService; - @Autowired - private ClientConfigProtocol configProtocol; - @Autowired - private ClientOnlineProtocol onlineProtocol; + private final SecurityService securityService; + private final ClientConfigProtocol clientConfigProtocol; + private final ClientOnlineProtocol clientOnlineProtocol; - public ClientRegisterProtocol() { + public ClientRegisterProtocol(SecurityService securityService, ClientConfigProtocol clientConfigProtocol, ClientOnlineProtocol clientOnlineProtocol) { super("终端注册信令", SIGNAL); + this.securityService = securityService; + this.clientConfigProtocol = clientConfigProtocol; + this.clientOnlineProtocol = clientOnlineProtocol; } - @Override - public void execute(String nullClientId, Map body, Client client, Message message) { - final String clientId = this.get(body, Constant.CLIENT_ID); - final String username = this.get(body, Constant.USERNAME); - final String password = this.get(body, Constant.PASSWORD); + @Override + public void execute(String nullClientId, ClientType nullClientType, Client client, Message message, Map body) { + final String clientId = MapUtils.get(body, Constant.CLIENT_ID); + final String username = MapUtils.get(body, Constant.USERNAME); + final String password = MapUtils.get(body, Constant.PASSWORD); // 如果需要终端鉴权在此实现 if(this.securityService.authenticate(username, password)) { + final Client oldClient = this.clientManager.clients(clientId); + if(oldClient != null) { + log.debug("终端已经存在(注销旧的终端):{}", clientId); + oldClient.clientId(); + } log.info("终端注册:{}", clientId); client.authorize(clientId); message.setCode(MessageCode.CODE_0000); } else { throw MessageCodeException.of(MessageCode.CODE_3401, "注册失败"); } + final ClientType clientType = ClientType.of(MapUtils.get(body, Constant.CLIENT_TYPE)); // 推送消息 client.push(message.cloneWithoutBody()); // 下发配置 - client.push(this.configProtocol.build(client)); + client.push(this.clientConfigProtocol.build(clientType)); // 终端状态 - final ClientStatus status = client.status(); - status.setClientId(clientId); - status.setIp(StringUtils.defaultString(client.ip(), this.get(body, Constant.IP))); - status.setSignal(this.get(body, Constant.SIGNAL)); - status.setBattery(this.get(body, Constant.BATTERY)); - status.setCharging(this.get(body, Constant.CHARGING)); - status.setLastHeartbeat(LocalDateTime.now()); + final ClientStatus status = this.buildStatus(clientId, clientType, client, body); // 上线事件 this.clientManager.broadcast( clientId, - this.onlineProtocol.build(status) + this.clientOnlineProtocol.build(status) ); + // 媒体服务终端注册 + if(clientType == ClientType.MEDIA) { + this.publishEvent(new MediaClientRegisterEvent(client)); + } + } + + /** + * @param clientId 终端标识 + * @param clientType 终端类型 + * @param client 终端 + * @param body 消息主体 + * + * @return 终端状态 + */ + private ClientStatus buildStatus(String clientId, ClientType clientType, Client client, Map body) { + final ClientStatus status = client.status(); + status.setIp(client.ip()); + status.setName(MapUtils.get(body, Constant.NAME)); + status.setClientId(clientId); + status.setClientType(clientType); + status.copy(body); + return status; } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientStatusProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientStatusProtocol.java index 6c5f250..337163d 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientStatusProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientStatusProtocol.java @@ -6,7 +6,9 @@ import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -19,7 +21,7 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; body = { """ { - "clientId": "终端标识" + "clientId": "终端标识(可选)" } """, """ @@ -45,9 +47,9 @@ public class ClientStatusProtocol extends ProtocolClientAdapter { } @Override - public void execute(String clientId, Map body, Client client, Message message) { - // 如果没有指定终端标识默认查询自己 - message.setBody(this.clientManager.status(this.get(body, Constant.CLIENT_ID, clientId))); + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { + final String queryClientId = MapUtils.get(body, Constant.CLIENT_ID, clientId); + message.setBody(this.clientManager.status(queryClientId)); client.push(message); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientUnicastProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientUnicastProtocol.java index 0fb4bf7..87aebc0 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientUnicastProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientUnicastProtocol.java @@ -9,6 +9,7 @@ import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; import lombok.extern.slf4j.Slf4j; @@ -24,7 +25,7 @@ import lombok.extern.slf4j.Slf4j; body = """ { "to": "接收终端标识", - // 主体信息 + ...自定义的主体 } """, flow = "终端->信令服务->终端" @@ -38,7 +39,7 @@ public class ClientUnicastProtocol extends ProtocolClientAdapter { } @Override - public void execute(String clientId, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { final String to = (String) body.remove(Constant.TO); if(StringUtils.isNotEmpty(to)) { this.clientManager.unicast(to, message); diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientWakeupProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientWakeupProtocol.java index a70413c..76fa782 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientWakeupProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/client/ClientWakeupProtocol.java @@ -1,10 +1,31 @@ package com.acgist.taoyao.signal.protocol.client; +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; + /** * 终端唤醒信令 * * @author acgist */ -public class ClientWakeupProtocol { +@Protocol +@Description( + flow = "信令服务->终端" +) +public class ClientWakeupProtocol extends ProtocolClientAdapter { + + private static final String SIGNAL = "client::wakeup"; + + public ClientWakeupProtocol() { + super("终端唤醒信令", SIGNAL); + } + + /** + * @param clientId 终端标识 + */ + public void execute(String clientId) { + this.clientManager.unicast(clientId, this.build()); + } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlBellProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlBellProtocol.java new file mode 100644 index 0000000..00131d5 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlBellProtocol.java @@ -0,0 +1,44 @@ +package com.acgist.taoyao.signal.protocol.control; + +import java.util.Map; + +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.protocol.ProtocolControlAdapter; + +/** + * 响铃信令 + * + * @author acgist + */ +@Protocol +@Description( + flow = { + "信令服务->终端", + "终端->信令服务->终端" + } +) +public class ControlBellProtocol extends ProtocolControlAdapter { + + private static final String SIGNAL = "control::bell"; + + public ControlBellProtocol() { + super("响铃信令", SIGNAL); + } + + @Override + public void execute(String clientId, ClientType clientType, Client client, Client targetClient, Message message, Map body) { + targetClient.push(message); + } + + /** + * @param clientId 终端标识 + */ + public void execute(String clientId) { + this.clientManager.unicast(clientId, this.build()); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlPhotographProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlPhotographProtocol.java new file mode 100644 index 0000000..e6517e8 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlPhotographProtocol.java @@ -0,0 +1,44 @@ +package com.acgist.taoyao.signal.protocol.control; + +import java.util.Map; + +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.protocol.ProtocolControlAdapter; + +/** + * 拍照信令 + * + * @author acgist + */ +@Protocol +@Description( + flow = { + "信令服务->终端", + "终端->信令服务->终端" + } +) +public class ControlPhotographProtocol extends ProtocolControlAdapter { + + public static final String SIGNAL = "control::photograph"; + + public ControlPhotographProtocol() { + super("拍照信令", SIGNAL); + } + + @Override + public void execute(String clientId, ClientType clientType, Client client, Client targetClient, Message message, Map body) { + targetClient.push(message); + } + + /** + * @param clientId 终端标识 + */ + public void execute(String clientId) { + this.clientManager.unicast(clientId, this.build()); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlPtzProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlPtzProtocol.java new file mode 100644 index 0000000..57a918d --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlPtzProtocol.java @@ -0,0 +1,68 @@ +package com.acgist.taoyao.signal.protocol.control; + +import java.util.Map; + +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.protocol.ProtocolControlAdapter; + +/** + * PTZ信令 + * + * @author acgist + */ +@Protocol +@Description( + body = """ + { + "type": "PTZ类型(PAN|TILT|ZOOM)", + "value": PTZ参数 + } + """, + flow = { + "信令服务->终端", + "终端->信令服务->终端" + } +) +public class ControlPtzProtocol extends ProtocolControlAdapter { + + public static final String SIGNAL = "control::ptz"; + + public ControlPtzProtocol() { + super("PTZ信令", SIGNAL); + } + + /** + * PTZ类型 + * + * @author acgist + */ + public enum Type { + + // 水平 + PAN, + // 垂直 + TILT, + // 变焦 + ZOOM; + + } + + @Override + public void execute(String clientId, ClientType clientType, Client client, Client targetClient, Message message, Map body) { + targetClient.push(message); + } + + /** + * @param type PTZ类型 + * @param value PTZ参数 + * @param clientId 终端标识 + */ + public void execute(Type type, Double value, String clientId) { + this.clientManager.unicast(clientId, this.build(Map.of(type, value))); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlRecordProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlRecordProtocol.java new file mode 100644 index 0000000..248b75c --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/control/ControlRecordProtocol.java @@ -0,0 +1,45 @@ +package com.acgist.taoyao.signal.protocol.control; + +import java.util.Map; + +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.protocol.ProtocolControlAdapter; + +/** + * 录像信令 + * + * @author acgist + */ +@Protocol +@Description( + memo = "状态通过心跳回传", + flow = { + "信令服务->终端", + "终端->信令服务->终端" + } +) +public class ControlRecordProtocol extends ProtocolControlAdapter { + + public static final String SIGNAL = "control::record"; + + public ControlRecordProtocol() { + super("录像信令", SIGNAL); + } + + @Override + public void execute(String clientId, ClientType clientType, Client client, Client targetClient, Message message, Map body) { + targetClient.push(message); + } + + /** + * @param clientId 终端标识 + */ + public void execute(String clientId) { + this.clientManager.unicast(clientId, this.build()); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaAudioActiveSpeakerProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaAudioActiveSpeakerProtocol.java index c97ea62..0e1e2a2 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaAudioActiveSpeakerProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaAudioActiveSpeakerProtocol.java @@ -5,9 +5,9 @@ import java.util.Map; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.MediaClient; -import com.acgist.taoyao.signal.media.Room; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; +import com.acgist.taoyao.signal.terminal.media.Room; /** * 当前讲话终端信令 @@ -17,20 +17,19 @@ import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; @Protocol public class MediaAudioActiveSpeakerProtocol extends ProtocolRoomAdapter { - public static final String SIGNAL = "audio::active::speaker"; + public static final String SIGNAL = "media::audio::active::speaker"; public MediaAudioActiveSpeakerProtocol() { super("当前讲话终端信令", SIGNAL); } @Override - public void execute(Room room, Map body, MediaClient mediaClient, Message message) { - room.broadcast(message); - } - - @Override - public void execute(String clientId, Room room, Map body, Client client, Message message) { - // 忽略 + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { + if(clientType == ClientType.MEDIA) { + room.broadcast(message); + } else { + // 忽略其他情况 + } } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaConsumeProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaConsumeProtocol.java new file mode 100644 index 0000000..7186eaa --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaConsumeProtocol.java @@ -0,0 +1,83 @@ +package com.acgist.taoyao.signal.protocol.media; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.context.ApplicationListener; +import org.springframework.scheduling.annotation.Async; + +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.config.Constant; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.utils.MapUtils; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.event.MediaProduceEvent; +import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; +import com.acgist.taoyao.signal.terminal.media.ClientWrapper; +import com.acgist.taoyao.signal.terminal.media.Producer; +import com.acgist.taoyao.signal.terminal.media.Room; +import com.acgist.taoyao.signal.terminal.media.Transport; + +/** + * 消费媒体信令 + * + * @author acgist + */ +@Protocol +@Description( + flow = { + "信令服务->媒体服务=>信令服务=>终端->信令服务->信令服务", + "终端->信令服务->媒体服务=>信令服务=>终端->信令服务->信令服务" + } +) +public class MediaConsumeProtocol extends ProtocolRoomAdapter implements ApplicationListener { + + public static final String SIGNAL = "media::consume"; + + protected MediaConsumeProtocol() { + super("消费媒体信令", SIGNAL); + } + + @Async + @Override + public void onApplicationEvent(MediaProduceEvent event) { + // TODO:根据类型进行消费 + final Room room = event.getRoom(); + final Client client = event.getClient(); + final Producer producer = event.getProducer(); + room.getClients().keySet().stream() + .filter(v -> v != client) + .forEach(v -> this.consume(room, v, producer)); + } + + @Override + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { + final String kind = MapUtils.get(body, Constant.KIND); + final String producerId = MapUtils.get(body, Constant.PRODUCER_ID); + final Producer producer = room.producer(producerId); + final String streamId = producer.getStreamId() + "->" + clientId; + if(clientType == ClientType.WEB || clientType == ClientType.CAMERA) { + // 请求消费 + } else if(clientType == ClientType.MEDIA) { + // 等待消费者准备完成 + + } else { + } + } + + private void consume(Room room, Client client, Producer producer) { + final Client mediaClient = room.getMediaClient(); + final ClientWrapper clientWrapper = room.client(client); + final Transport recvTransport = clientWrapper.getRecvTransport(); + final Map body = new HashMap<>(); + body.put(Constant.ROOM_ID, room.getRoomId()); + body.put(Constant.PRODUCER_ID, producer.getProducerId()); + body.put(Constant.TRANSPORT_ID, recvTransport.getTransportId()); + body.put(Constant.RTP_CAPABILITIES, clientWrapper.getRtpCapabilities()); + body.put(Constant.SCTP_CAPABILITIES, clientWrapper.getSctpCapabilities()); + mediaClient.push(this.build(body)); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaIceRestartProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaIceRestartProtocol.java index 7826d78..d44ad85 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaIceRestartProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaIceRestartProtocol.java @@ -1,5 +1,34 @@ package com.acgist.taoyao.signal.protocol.media; +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; + +/** + * 媒体重启ICE信令 + * + * @author acgist + */ +@Protocol +@Description( + body = { + """ + { + "roomId": "房间标识", + "transportId": "通道标识" + } + """, + """ + { + "roomId": "房间标识", + "transportId": "通道标识", + "iceParameters": "iceParameters" + } + """ + }, + flow = "终端->信令服务->媒体服务->信令服务->终端" +) public class MediaIceRestartProtocol { + public static final String SIGNAL = "media::ice::restart"; + } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaListProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaListProtocol.java deleted file mode 100644 index 5454910..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaListProtocol.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.acgist.taoyao.signal.protocol.media; - -import java.util.Map; - -import org.springframework.beans.factory.annotation.Autowired; - -import com.acgist.taoyao.boot.annotation.Description; -import com.acgist.taoyao.boot.annotation.Protocol; -import com.acgist.taoyao.boot.config.MediaProperties; -import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; - -/** - * 媒体服务列表信令 - * - * @author acgist - */ -@Protocol -@Description( - body = """ - [ - { - "name": "名称", - "enabled": "是否启用", - "host": "主机", - "port": "端口", - "schema": "协议", - "address": "完整地址" - } - ] - """, - flow = "终端->信令服务->终端" -) -public class MediaListProtocol extends ProtocolClientAdapter { - - public static final String SIGNAL = "media::list"; - - @Autowired - private MediaProperties mediaProperties; - - public MediaListProtocol() { - super("媒体服务列表信令", SIGNAL); - } - - @Override - public void execute(String clientId, Map body, Client client, Message message) { - message.setBody(this.mediaProperties.getMediaServerList()); - client.push(message); - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaProduceProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaProduceProtocol.java index 8221870..abf2d71 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaProduceProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaProduceProtocol.java @@ -1,5 +1,77 @@ package com.acgist.taoyao.signal.protocol.media; -public class MediaProduceProtocol { +import java.util.Map; +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.config.Constant; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.utils.MapUtils; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.event.MediaProduceEvent; +import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; +import com.acgist.taoyao.signal.terminal.media.ClientWrapper; +import com.acgist.taoyao.signal.terminal.media.Producer; +import com.acgist.taoyao.signal.terminal.media.Room; + +import lombok.extern.slf4j.Slf4j; + +/** + * 生产媒体信令 + * + * @author acgist + */ +@Slf4j +@Protocol +@Description( + body = { + """ + { + "kind": "媒体类型", + "roomId": "房间标识", + "transportId": "通道标识", + "rtpParameters": "rtpParameters" + } + """ + }, + flow = "终端->信令服务->媒体服务->信令服务->终端" +) +public class MediaProduceProtocol extends ProtocolRoomAdapter { + + public static final String SIGNAL = "media::produce"; + + public MediaProduceProtocol() { + super("生产媒体信令", SIGNAL); + } + + @Override + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { + // TODO;类型判断? + final String kind = MapUtils.get(body, Constant.KIND); + final String streamId = kind + "::" + clientId; + body.put(Constant.CLIENT_ID, clientId); + body.put(Constant.STREAM_ID, streamId); + final Message response = room.request(message); + final Map responseBody = response.mapBody(); + final String producerId = MapUtils.get(responseBody, Constant.PRODUCER_ID); + final ClientWrapper clientWrapper = room.client(client); + final Map producers = clientWrapper.getProducers(); + final Producer producer = producers.computeIfAbsent(producerId, key -> new Producer(client, kind, streamId, producerId)); + final Message responseMessage = response.cloneWithoutBody(); + responseMessage.setBody(Map.of( + Constant.KIND, kind, + Constant.STREAM_ID, streamId, + Constant.PRODUCER_ID, producerId + )); + // 根据不同类型进行推送: + // 自动推送:不用广播 + // 音频全收:广播视频 + // 视频全收:广播音频 + // 全部不收:全部广播 + room.broadcast(responseMessage); + log.info("{}生产媒体:{} - {} - {}", clientId, kind, streamId, producerId); + this.publishEvent(new MediaProduceEvent(room, client, producer)); + } + } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaProducerScoreProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaProducerScoreProtocol.java index 43889a0..3de0ab2 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaProducerScoreProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaProducerScoreProtocol.java @@ -1,5 +1,35 @@ package com.acgist.taoyao.signal.protocol.media; -public class MediaProducerScoreProtocol { +import java.util.Map; +import com.acgist.taoyao.boot.annotation.Description; +import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; +import com.acgist.taoyao.signal.terminal.media.Room; + +/** + * 媒体生产者评分信令 + * + * @author acgist + */ +@Protocol +@Description( + flow = "媒体服务->信令服务->终端" +) +public class MediaProducerScoreProtocol extends ProtocolRoomAdapter { + + public static final String SIGNAL = "media::producer::score"; + + public MediaProducerScoreProtocol() { + super("媒体生产者评分信令", SIGNAL); + } + + @Override + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { + room.broadcast(message); + } + } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRebootProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRebootProtocol.java deleted file mode 100644 index 64aa485..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRebootProtocol.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.acgist.taoyao.signal.protocol.media; - -import java.util.Map; - -import org.springframework.beans.factory.annotation.Autowired; - -import com.acgist.taoyao.boot.annotation.Description; -import com.acgist.taoyao.boot.config.ScriptProperties; -import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.boot.utils.ScriptUtils; -import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.protocol.ControlProtocol; -import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; - -import lombok.extern.slf4j.Slf4j; - -/** - * 媒体服务重启信令 - * - * TODO:指定媒体服务名称 - * - * @author acgist - */ -@Slf4j -@Description(flow = "终端->信令服务+)终端") -public class MediaRebootProtocol extends ProtocolClientAdapter implements ControlProtocol { - - public static final String SIGNAL = "media::reboot"; - - @Autowired - private ScriptProperties scriptProperties; - - public MediaRebootProtocol() { - super("重启媒体服务信令", SIGNAL); - } - - /** - * @param mediaId 媒体服务标识 - */ - public void execute(String mediaId) { - log.info("重启媒体服务"); - this.clientManager.broadcast(this.build()); - ScriptUtils.execute(this.scriptProperties.getMediaReboot()); - } - - @Override - public void execute(String clientId, Map body, Client client, Message message) { - log.info("重启媒体服务:{}", clientId); - this.clientManager.broadcast(message); - ScriptUtils.execute(this.scriptProperties.getMediaReboot()); - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRecordProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRecordProtocol.java deleted file mode 100644 index 3dda6c9..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRecordProtocol.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.acgist.taoyao.signal.protocol.media; - -public class MediaRecordProtocol { - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRegisterProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRegisterProtocol.java deleted file mode 100644 index bbbf191..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRegisterProtocol.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.acgist.taoyao.signal.protocol.media; - -import java.util.Map; - -import org.springframework.beans.factory.annotation.Autowired; - -import com.acgist.taoyao.boot.annotation.Description; -import com.acgist.taoyao.boot.annotation.Protocol; -import com.acgist.taoyao.boot.config.Constant; -import com.acgist.taoyao.boot.config.MediaServerProperties; -import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.MediaClient; -import com.acgist.taoyao.signal.protocol.ProtocolMediaAdapter; -import com.acgist.taoyao.signal.protocol.room.RoomCreateProtocol; - -import lombok.extern.slf4j.Slf4j; - -/** - * 媒体服务注册信令 - * - * @author acgist - */ -@Slf4j -@Protocol -@Description( - body = """ - { - "username": "媒体用户", - "password": "媒体密码" - } - """, - flow = "信令服务->媒体服务->信令服务" -) -public class MediaRegisterProtocol extends ProtocolMediaAdapter { - - public static final String SIGNAL = "media::register"; - - @Autowired - private RoomCreateProtocol roomCreateProtocol; - - public MediaRegisterProtocol() { - super("媒体服务注册信令", SIGNAL); - } - - /** - * 创建信令消息 - * - * @param mediaServerProperties 媒体服务配置 - * - * @return 信令消息 - */ - public Message build(MediaServerProperties mediaServerProperties) { - return super.build(Map.of( - Constant.USERNAME, mediaServerProperties.getUsername(), - Constant.PASSWORD, mediaServerProperties.getPassword() - )); - } - - @Override - public void execute(Map body, MediaClient mediaClient, Message message) { - log.info("媒体终端注册结果:{}", message); - this.roomManager.recreate(mediaClient, this.roomCreateProtocol.build()); - } - - @Override - public void execute(String clientId, Map body, Client client, Message message) { - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRouterRtpCapabilitiesProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRouterRtpCapabilitiesProtocol.java index f00cf46..6ad668e 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRouterRtpCapabilitiesProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaRouterRtpCapabilitiesProtocol.java @@ -6,9 +6,9 @@ import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.MediaClient; -import com.acgist.taoyao.signal.media.Room; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; +import com.acgist.taoyao.signal.terminal.media.Room; /** * 路由RTP能力信令 @@ -41,13 +41,12 @@ public class MediaRouterRtpCapabilitiesProtocol extends ProtocolRoomAdapter { } @Override - public void execute(Room room, Map body, MediaClient mediaClient, Message message) { - // 忽略 - } - - @Override - public void execute(String clientId, Room room, Map body, Client client, Message message) { - client.push(room.request(message)); + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { + if(clientType == ClientType.WEB || clientType == ClientType.CAMERA) { + client.push(room.request(message)); + } else { + // 忽略其他情况 + } } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaShutdownProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaShutdownProtocol.java deleted file mode 100644 index 7412b39..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaShutdownProtocol.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.acgist.taoyao.signal.protocol.media; - -import java.util.Map; - -import org.springframework.beans.factory.annotation.Autowired; - -import com.acgist.taoyao.boot.config.ScriptProperties; -import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.boot.utils.ScriptUtils; -import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.protocol.ControlProtocol; -import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; - -import lombok.extern.slf4j.Slf4j; - -/** - * 关闭媒体服务信令 - * - * TODO:指定媒体服务名称 - * - * @author acgist - */ -@Slf4j -public class MediaShutdownProtocol extends ProtocolClientAdapter implements ControlProtocol { - - public static final String SIGNAL = "media::shutdown"; - - @Autowired - private ScriptProperties scriptProperties; - - public MediaShutdownProtocol() { - super("关闭媒体服务信令", SIGNAL); - } - - /** - * @param mediaId 媒体服务标识 - */ - public void execute(String mediaId) { - log.info("关闭媒体服务"); - this.clientManager.broadcast(this.build()); - ScriptUtils.execute(this.scriptProperties.getMediaShutdown()); - } - - @Override - public void execute(String clientId, Map body, Client client, Message message) { - log.info("关闭媒体服务:{}", clientId); - this.clientManager.broadcast(message); - ScriptUtils.execute(this.scriptProperties.getMediaShutdown()); - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaTransportWebRtcConnectProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaTransportWebRtcConnectProtocol.java index 74a2e57..4a90704 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaTransportWebRtcConnectProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaTransportWebRtcConnectProtocol.java @@ -4,17 +4,22 @@ import java.util.Map; import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; +import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.MediaClient; -import com.acgist.taoyao.signal.media.Room; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; +import com.acgist.taoyao.signal.terminal.media.Room; + +import lombok.extern.slf4j.Slf4j; /** * 连接WebRTC通道信令 * * @author acgist */ +@Slf4j @Protocol @Description( body = { @@ -32,13 +37,16 @@ public class MediaTransportWebRtcConnectProtocol extends ProtocolRoomAdapter { } @Override - public void execute(Room room, Map body, MediaClient mediaClient, Message message) { - } - - @Override - public void execute(String clientId, Room room, Map body, Client client, Message message) { - final Message response = room.request(message); - client.push(response); + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { + if(clientType == ClientType.WEB || clientType == ClientType.CAMERA) { + final Message response = room.request(message); + final Map responseBody = response.mapBody(); + client.push(response); + final String transportId = MapUtils.get(responseBody, Constant.TRANSPORT_ID); + log.info("{} 连接WebRTC信令通道:{}", clientId, transportId); + } else { + // 忽略其他情况 + } } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaTransportWebRtcCreateProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaTransportWebRtcCreateProtocol.java index 9b76954..e7b8190 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaTransportWebRtcCreateProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/media/MediaTransportWebRtcCreateProtocol.java @@ -4,21 +4,20 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.config.Constant; -import com.acgist.taoyao.boot.config.MediaServerProperties; import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.boot.service.IpService; +import com.acgist.taoyao.boot.utils.MapUtils; +import com.acgist.taoyao.boot.utils.NetUtils; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.MediaClient; -import com.acgist.taoyao.signal.media.Peer; -import com.acgist.taoyao.signal.media.Room; -import com.acgist.taoyao.signal.media.Transport; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; +import com.acgist.taoyao.signal.terminal.media.ClientWrapper; +import com.acgist.taoyao.signal.terminal.media.Room; +import com.acgist.taoyao.signal.terminal.media.Transport; import lombok.extern.slf4j.Slf4j; @@ -51,33 +50,44 @@ public class MediaTransportWebRtcCreateProtocol extends ProtocolRoomAdapter { public static final String SIGNAL = "media::transport::webrtc::create"; - @Autowired - private IpService ipService; - protected MediaTransportWebRtcCreateProtocol() { super("创建WebRTC通道信令", SIGNAL); } @Override - public void execute(Room room, Map body, MediaClient mediaClient, Message message) { - } - - @Override - public void execute(String clientId, Room room, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { + body.put(Constant.CLIENT_ID, clientId); final Message response = room.request(message); - final Peer peer = client.peer(); - final Map responseBody = (Map) response.getBody(); - final MediaServerProperties mediaServerProperties = room.getMediaClient().mediaServerProperties(); - if(Boolean.TRUE.equals(mediaServerProperties.getRewriteIp())) { - // 重写IP地址 - this.rewriteIp(client.ip(), responseBody, mediaServerProperties); + final Map responseBody = response.mapBody(); + final String transportId = MapUtils.get(responseBody, Constant.TRANSPORT_ID); + // 重写地址 + this.rewriteIp(client.ip(), responseBody); + // 处理逻辑 + final ClientWrapper clientWrapper = room.client(client); + // 消费者 + final Boolean consuming = MapUtils.getBoolean(body, Constant.CONSUMING); + if(Boolean.TRUE.equals(consuming)) { + Transport recvTransport = clientWrapper.getRecvTransport(); + if(recvTransport == null) { + recvTransport = new Transport(transportId, room, client); + clientWrapper.setRecvTransport(recvTransport); + } + // 拷贝属性 + recvTransport.copy(responseBody); + } + // 生产者 + final Boolean producing = MapUtils.getBoolean(body, Constant.PRODUCING); + if(Boolean.TRUE.equals(producing)) { + Transport sendTransport = clientWrapper.getSendTransport(); + if(sendTransport == null) { + sendTransport = new Transport(transportId, room, client); + clientWrapper.setSendTransport(sendTransport); + } + // 拷贝属性 + sendTransport.copy(responseBody); } - final Map transports = peer.getTransports(); - final String transportId = this.get(responseBody, Constant.TRANSPORT_ID); - final Transport transport = transports.computeIfAbsent(transportId, key -> new Transport(client)); - // 拷贝属性 - transport.copy(responseBody); client.push(response); + log.info("{} 创建WebRTC信令通道:{}", clientId, transportId); } /** @@ -85,22 +95,21 @@ public class MediaTransportWebRtcCreateProtocol extends ProtocolRoomAdapter { * * @param clientIp 终端IP * @param body 消息主体 - * @param mediaServerProperties 媒体服务配置 */ - private void rewriteIp(String clientIp, Map body, MediaServerProperties mediaServerProperties) { - final List> iceCandidates = this.get(body, Constant.ICE_CANDIDATES); + private void rewriteIp(String clientIp, Map body) { + final List> iceCandidates = MapUtils.get(body, Constant.ICE_CANDIDATES); if(CollectionUtils.isEmpty(iceCandidates)) { return; } - final String defaultMediaIp = mediaServerProperties.getHost(); iceCandidates.forEach(map -> { + // 媒体服务返回IP final String mediaIp = (String) map.get(Constant.IP); if(StringUtils.isNotEmpty(mediaIp)) { - final String rewriteIp = this.ipService.rewriteIp(mediaIp, clientIp, defaultMediaIp); - log.debug("重写IP地址:{} | {} + {} -> {}", mediaIp, defaultMediaIp, clientIp, rewriteIp); + final String rewriteIp = NetUtils.rewriteIp(mediaIp, clientIp); + log.debug("重写地址:{} + {} -> {}", mediaIp, clientIp, rewriteIp); map.put(Constant.IP, rewriteIp); } }); } - + } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformErrorProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformErrorProtocol.java index 898a465..900d798 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformErrorProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformErrorProtocol.java @@ -1,13 +1,10 @@ package com.acgist.taoyao.signal.protocol.platform; -import java.util.Map; - import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.model.MessageCode; import com.acgist.taoyao.boot.model.MessageCodeException; -import com.acgist.taoyao.signal.client.Client; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -16,7 +13,9 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; * @author acgist */ @Protocol -@Description(flow = "终端->信令服务->终端") +@Description( + flow = "终端->信令服务->终端" +) public class PlatformErrorProtocol extends ProtocolClientAdapter { public static final String SIGNAL = "platform::error"; @@ -24,42 +23,17 @@ public class PlatformErrorProtocol extends ProtocolClientAdapter { /** * 请求ID缓存 */ - private ThreadLocal idLocal = new InheritableThreadLocal<>(); + private ThreadLocal idLocal = new InheritableThreadLocal<>(); public PlatformErrorProtocol() { super("平台异常信令", SIGNAL); } - /** - * @param id 请求ID - */ - public void set(String id) { - this.idLocal.set(id); - } - @Override - public void execute(String clientId, Map body, Client client, Message message) { - } - - /** - * @param e 异常 - * - * @return 异常消息 - */ - public Message build(Exception e) { - final Message message = super.build(); - if(e instanceof MessageCodeException code) { - message.setCode(code.getCode(), code.getMessage()); - } - message.setBody(e.getMessage()); - return message; - } - - @Override - public Message build(String id, MessageCode code, String message, Object body) { - final String oldId = this.idLocal.get(); + public Message build(Long id, MessageCode code, String message, Object body) { + final Long oldId = this.idLocal.get(); if(oldId == null) { - id = this.idService.buildIdToString(); + id = this.idService.buildId(); } else { id = oldId; this.idLocal.remove(); @@ -68,4 +42,25 @@ public class PlatformErrorProtocol extends ProtocolClientAdapter { return super.build(id, code == null ? MessageCode.CODE_9999 : code, message, body); } + /** + * @param id 请求ID + */ + public void set(Long id) { + this.idLocal.set(id); + } + + /** + * @param e 异常 + * + * @return 异常消息 + */ + public Message build(Exception e) { + final Message message = super.build(); + if(e instanceof MessageCodeException code) { + message.setCode(code.getCode(), code.getMessage()); + } + message.setBody(e.getMessage()); + return message; + } + } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformRebootProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformRebootProtocol.java index a37b7f1..dcc0f21 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformRebootProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformRebootProtocol.java @@ -2,13 +2,12 @@ package com.acgist.taoyao.signal.protocol.platform; import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; - import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.config.ScriptProperties; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.utils.ScriptUtils; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ControlProtocol; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; @@ -20,16 +19,18 @@ import lombok.extern.slf4j.Slf4j; * @author acgist */ @Slf4j -@Description(flow = "终端->信令服务+)终端") +@Description( + flow = "终端->信令服务+)终端" +) public class PlatformRebootProtocol extends ProtocolClientAdapter implements ControlProtocol { public static final String SIGNAL = "platform::reboot"; - @Autowired - private ScriptProperties scriptProperties; + private final ScriptProperties scriptProperties; - public PlatformRebootProtocol() { + public PlatformRebootProtocol(ScriptProperties scriptProperties) { super("重启平台信令", SIGNAL); + this.scriptProperties = scriptProperties; } /** @@ -42,7 +43,7 @@ public class PlatformRebootProtocol extends ProtocolClientAdapter implements Con } @Override - public void execute(String clientId, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { log.info("重启平台:{}", clientId); this.clientManager.broadcast(message); ScriptUtils.execute(this.scriptProperties.getPlatformReboot()); diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformScriptProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformScriptProtocol.java index 75eedbc..dd1b0df 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformScriptProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformScriptProtocol.java @@ -5,8 +5,10 @@ import java.util.Map; import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.boot.utils.ScriptUtils; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; import lombok.extern.slf4j.Slf4j; @@ -41,9 +43,9 @@ public class PlatformScriptProtocol extends ProtocolClientAdapter { } @Override - public void execute(String clientId, Map body, Client client, Message message) { - final String script = this.get(body, Constant.SCRIPT); - final String result = ScriptUtils.execute(this.get(body, Constant.SCRIPT)); + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { + final String script = MapUtils.get(body, Constant.SCRIPT); + final String result = ScriptUtils.execute(script); log.info(""" 执行终端:{} 执行命令:{} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformShutdownProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformShutdownProtocol.java index fbf6a79..ef48432 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformShutdownProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/PlatformShutdownProtocol.java @@ -2,7 +2,6 @@ package com.acgist.taoyao.signal.protocol.platform; import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; import com.acgist.taoyao.boot.annotation.Description; @@ -10,6 +9,7 @@ import com.acgist.taoyao.boot.config.ScriptProperties; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.utils.ScriptUtils; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ControlProtocol; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; @@ -21,18 +21,27 @@ import lombok.extern.slf4j.Slf4j; * @author acgist */ @Slf4j -@Description(flow = "终端->信令服务+)终端") +@Description( + flow = "终端->信令服务+)终端" +) public class PlatformShutdownProtocol extends ProtocolClientAdapter implements ControlProtocol { public static final String SIGNAL = "platform::shutdown"; - @Autowired - private ScriptProperties scriptProperties; + private final ScriptProperties scriptProperties; - public PlatformShutdownProtocol() { + public PlatformShutdownProtocol(ScriptProperties scriptProperties) { super("关闭平台信令", SIGNAL); + this.scriptProperties = scriptProperties; } + @Override + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { + log.info("关闭平台:{}", clientId); + this.clientManager.broadcast(message); + this.shutdown(); + } + /** * 执行命令信令 */ @@ -41,13 +50,6 @@ public class PlatformShutdownProtocol extends ProtocolClientAdapter implements C this.clientManager.broadcast(this.build()); this.shutdown(); } - - @Override - public void execute(String clientId, Map body, Client client, Message message) { - log.info("关闭平台:{}", clientId); - this.clientManager.broadcast(message); - this.shutdown(); - } /** * 关闭平台 diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCloseProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCloseProtocol.java index 6a12edc..1a129c6 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCloseProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCloseProtocol.java @@ -2,12 +2,13 @@ package com.acgist.taoyao.signal.protocol.room; import java.util.Map; +import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.MediaClient; -import com.acgist.taoyao.signal.media.Room; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; +import com.acgist.taoyao.signal.terminal.media.Room; /** * 关闭房间信令 @@ -15,6 +16,9 @@ import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; * @author acgist */ @Protocol +@Description( + flow = "终端->信令服务->媒体服务->信令服务+)终端" +) public class RoomCloseProtocol extends ProtocolRoomAdapter { private static final String SIGNAL = "room::close"; @@ -24,13 +28,9 @@ public class RoomCloseProtocol extends ProtocolRoomAdapter { } @Override - public void execute(Room room, Map body, MediaClient mediaClient, Message message) { - - } - - @Override - public void execute(String clientId, Room room, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { room.close(); + this.clientManager.broadcast(message); } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCreateProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCreateProtocol.java index 651022f..3e89158 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCreateProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomCreateProtocol.java @@ -2,20 +2,28 @@ package com.acgist.taoyao.signal.protocol.room; import java.util.Map; +import org.springframework.context.ApplicationListener; +import org.springframework.scheduling.annotation.Async; + import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; -import com.acgist.taoyao.boot.model.MessageCodeException; +import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.Room; +import com.acgist.taoyao.signal.client.ClientType; +import com.acgist.taoyao.signal.event.MediaClientRegisterEvent; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; +import com.acgist.taoyao.signal.terminal.media.Room; + +import lombok.extern.slf4j.Slf4j; /** * 创建房间信令 * * @author acgist */ +@Slf4j @Protocol @Description( body = """ @@ -28,7 +36,7 @@ import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; """, flow = "终端->服务端+)终端" ) -public class RoomCreateProtocol extends ProtocolClientAdapter { +public class RoomCreateProtocol extends ProtocolClientAdapter implements ApplicationListener { public static final String SIGNAL = "room::create"; @@ -36,22 +44,33 @@ public class RoomCreateProtocol extends ProtocolClientAdapter { super("创建房间信令", SIGNAL); } + @Async @Override - public void execute(String clientId, Map body, Client client, Message message) { - final String roomId = this.get(body, Constant.ROOM_ID); - if (roomId != null && this.roomManager.room(roomId) != null) { - throw MessageCodeException.of("房间已经存在"); - } - // 创建房间 - final Room room = this.roomManager.create( - this.get(body, Constant.NAME), - this.get(body, Constant.PASSWORD), - this.get(body, Constant.MEDIA_ID), - message.cloneWithoutBody() - ); - // 广播消息 - message.setBody(room.getStatus()); - this.clientManager.broadcast(message); + public void onApplicationEvent(MediaClientRegisterEvent event) { + this.roomManager.recreate(event.getClient(), this.build()); + } + + @Override + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { + if(clientType == ClientType.WEB) { + final String roomId = MapUtils.get(body, Constant.ROOM_ID); + if (roomId != null && this.roomManager.room(roomId) != null) { + log.info("房间已经存在:{}", roomId); + return; + } + // 创建房间 + final Room room = this.roomManager.create( + MapUtils.get(body, Constant.NAME), + MapUtils.get(body, Constant.PASSWORD), + MapUtils.get(body, Constant.MEDIA_ID), + message.cloneWithoutBody() + ); + // 广播消息 + message.setBody(room.getRoomStatus()); + this.clientManager.broadcast(message); + } else { + this.logNoAdapter(clientType); + } } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomEnterProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomEnterProtocol.java index dc6b83f..b2eb6cc 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomEnterProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomEnterProtocol.java @@ -8,16 +8,21 @@ import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.model.MessageCode; import com.acgist.taoyao.boot.model.MessageCodeException; +import com.acgist.taoyao.boot.utils.MapUtils; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.MediaClient; -import com.acgist.taoyao.signal.media.Room; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter; +import com.acgist.taoyao.signal.terminal.media.ClientWrapper; +import com.acgist.taoyao.signal.terminal.media.Room; + +import lombok.extern.slf4j.Slf4j; /** * 进入房间信令 * * @author acgist */ +@Slf4j @Protocol @Description( body = { @@ -44,31 +49,25 @@ public class RoomEnterProtocol extends ProtocolRoomAdapter { } @Override - public void execute(Room room, Map body, MediaClient mediaClient, Message message) { - } - - @Override - public void execute(String clientId, Room room, Map body, Client client, Message message) { - final String password = this.get(body, Constant.PASSWORD); + public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map body) { + final String password = MapUtils.get(body, Constant.PASSWORD); + final Object rtpCapabilities = MapUtils.get(body, Constant.RTP_CAPABILITIES); + final Object sctpCapabilities = MapUtils.get(body, Constant.SCTP_CAPABILITIES); final String roomPassowrd = room.getPassword(); if(roomPassowrd != null && !roomPassowrd.equals(password)) { throw MessageCodeException.of(MessageCode.CODE_3401, "密码错误"); } - final MediaClient mediaClient = room.getMediaClient(); - if(client.mediaClient() == null) { - client.mediaClient(mediaClient); - } else if(client.mediaClient() == mediaClient) { - } else { - throw MessageCodeException.of("不在相同媒体服务:" + mediaClient.mediaId()); - } // 进入房间 - room.enter(client); + final ClientWrapper clientWrapper = room.enter(client); + clientWrapper.setRtpCapabilities(rtpCapabilities); + clientWrapper.setSctpCapabilities(sctpCapabilities); // 发送通知 message.setBody(Map.of( Constant.ROOM_ID, room.getRoomId(), Constant.CLIENT_ID, clientId )); room.broadcast(message); + log.info("进入房间:{} - {}", clientId, room.getRoomId()); } } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomExpelProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomExpelProtocol.java index c706742..5f012ca 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomExpelProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomExpelProtocol.java @@ -1,5 +1,10 @@ package com.acgist.taoyao.signal.protocol.room; +/** + * 踢出房间信令 + * + * @author acgist + */ public class RoomExpelProtocol { } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomInviteProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomInviteProtocol.java index 0e3db77..51de3f9 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomInviteProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomInviteProtocol.java @@ -1,5 +1,10 @@ package com.acgist.taoyao.signal.protocol.room; +/** + * 邀请房间信令 + * + * @author acgist + */ public class RoomInviteProtocol { } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java index 0d46b8b..cb5f84e 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomLeaveProtocol.java @@ -1,5 +1,10 @@ package com.acgist.taoyao.signal.protocol.room; +/** + * 离开房间信令 + * + * @author acgist + */ public class RoomLeaveProtocol { } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomListProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomListProtocol.java index ddc0af3..312d3fc 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomListProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomListProtocol.java @@ -2,13 +2,11 @@ package com.acgist.taoyao.signal.protocol.room; import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; - import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.annotation.Protocol; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.signal.client.Client; -import com.acgist.taoyao.signal.media.RoomManager; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; /** @@ -34,15 +32,12 @@ public class RoomListProtocol extends ProtocolClientAdapter { public static final String SIGNAL = "room::list"; - @Autowired - private RoomManager roomManager; - public RoomListProtocol() { super("房间列表信令", SIGNAL); } @Override - public void execute(String clientId, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { message.setBody(this.roomManager.status()); client.push(message); } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomStatusProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomStatusProtocol.java index a4ef99b..79adafd 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomStatusProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomStatusProtocol.java @@ -1,5 +1,10 @@ package com.acgist.taoyao.signal.protocol.room; +/** + * 房间状态信令 + * + * @author acgist + */ public class RoomStatusProtocol { } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomSubscribeProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomSubscribeProtocol.java new file mode 100644 index 0000000..5ee53a4 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomSubscribeProtocol.java @@ -0,0 +1,10 @@ +package com.acgist.taoyao.signal.protocol.room; + +/** + * 房间订阅信令 + * + * @author acgist + */ +public class RoomSubscribeProtocol { + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomUnsubscribeProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomUnsubscribeProtocol.java new file mode 100644 index 0000000..12ea814 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/room/RoomUnsubscribeProtocol.java @@ -0,0 +1,10 @@ +package com.acgist.taoyao.signal.protocol.room; + +/** + * 房间取消订阅信令 + * + * @author acgist + */ +public class RoomUnsubscribeProtocol { + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemDiskspaceProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemDiskspaceProtocol.java new file mode 100644 index 0000000..f9d6bca --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemDiskspaceProtocol.java @@ -0,0 +1,10 @@ +package com.acgist.taoyao.signal.protocol.system; + +/** + * 平台磁盘空间信令 + * + * @author acgist + */ +public class SystemDiskspaceProtocol { + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemRebootProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemRebootProtocol.java index 2740b06..371eeed 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemRebootProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemRebootProtocol.java @@ -2,13 +2,12 @@ package com.acgist.taoyao.signal.protocol.system; import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; - import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.config.ScriptProperties; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.utils.ScriptUtils; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ControlProtocol; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; @@ -20,16 +19,18 @@ import lombok.extern.slf4j.Slf4j; * @author acgist */ @Slf4j -@Description(flow = "终端->信令服务+)终端") +@Description( + flow = "终端->信令服务+)终端" +) public class SystemRebootProtocol extends ProtocolClientAdapter implements ControlProtocol { public static final String SIGNAL = "system::reboot"; - @Autowired - private ScriptProperties scriptProperties; + private final ScriptProperties scriptProperties; - public SystemRebootProtocol() { + public SystemRebootProtocol(ScriptProperties scriptProperties) { super("重启系统信令", SIGNAL); + this.scriptProperties = scriptProperties; } /** @@ -42,7 +43,7 @@ public class SystemRebootProtocol extends ProtocolClientAdapter implements Contr } @Override - public void execute(String clientId, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { log.info("重启系统:{}", clientId); this.clientManager.broadcast(message); ScriptUtils.execute(this.scriptProperties.getSystemReboot()); diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemShutdownProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemShutdownProtocol.java index 95f86a9..8f94a00 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemShutdownProtocol.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/system/SystemShutdownProtocol.java @@ -2,13 +2,12 @@ package com.acgist.taoyao.signal.protocol.system; import java.util.Map; -import org.springframework.beans.factory.annotation.Autowired; - import com.acgist.taoyao.boot.annotation.Description; import com.acgist.taoyao.boot.config.ScriptProperties; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.utils.ScriptUtils; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientType; import com.acgist.taoyao.signal.protocol.ControlProtocol; import com.acgist.taoyao.signal.protocol.ProtocolClientAdapter; @@ -20,16 +19,18 @@ import lombok.extern.slf4j.Slf4j; * @author acgist */ @Slf4j -@Description(flow = "终端->信令服务+)终端") +@Description( + flow = "终端->信令服务+)终端" +) public class SystemShutdownProtocol extends ProtocolClientAdapter implements ControlProtocol { public static final String SIGNAL = "system::shutdown"; - @Autowired - private ScriptProperties scriptProperties; + private final ScriptProperties scriptProperties; - public SystemShutdownProtocol() { + public SystemShutdownProtocol(ScriptProperties scriptProperties) { super("关闭系统信令", SIGNAL); + this.scriptProperties = scriptProperties; } /** @@ -42,7 +43,7 @@ public class SystemShutdownProtocol extends ProtocolClientAdapter implements Con } @Override - public void execute(String clientId, Map body, Client client, Message message) { + public void execute(String clientId, ClientType clientType, Client client, Message message, Map body) { log.info("关闭系统:{}", clientId); this.clientManager.broadcast(message); ScriptUtils.execute(this.scriptProperties.getSystemShutdown()); diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/service/impl/SecurityServiceImpl.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/service/impl/SecurityServiceImpl.java index 3b1bf9f..5d3218b 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/service/impl/SecurityServiceImpl.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/service/impl/SecurityServiceImpl.java @@ -1,7 +1,6 @@ package com.acgist.taoyao.signal.service.impl; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; import com.acgist.taoyao.boot.config.SecurityProperties; import com.acgist.taoyao.boot.model.Message; @@ -12,12 +11,15 @@ import com.acgist.taoyao.signal.service.UsernamePasswordService; public class SecurityServiceImpl implements SecurityService { - @Autowired - private SecurityProperties securityProperties; - @Autowired(required = false) - private UsernamePasswordService usernamePasswordService; + private final SecurityProperties securityProperties; + private final UsernamePasswordService usernamePasswordService; - @Override + public SecurityServiceImpl(SecurityProperties securityProperties, UsernamePasswordService usernamePasswordService) { + this.securityProperties = securityProperties; + this.usernamePasswordService = usernamePasswordService; + } + + @Override public boolean authenticate(String username, String password) { if(Boolean.FALSE.equals(this.securityProperties.getEnabled())) { return true; diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/ClientWrapper.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/ClientWrapper.java new file mode 100644 index 0000000..1316802 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/ClientWrapper.java @@ -0,0 +1,101 @@ +package com.acgist.taoyao.signal.terminal.media; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import com.acgist.taoyao.signal.client.Client; + +import lombok.Getter; +import lombok.Setter; + +/** + * Peer + * + * @author acgist + */ +@Getter +@Setter +public class ClientWrapper { + + /** + * 订阅类型 + * 如果需要订阅指定终端需要调用媒体消费信令 + * + * @author acgist + */ + public enum SubscribeType { + + // 订阅所有媒体 + ALL, + // 订阅所有音频媒体 + ALL_AUDIO, + // 订阅所有视频媒体 + ALL_VIDEO, + // 没有订阅任何媒体 + NONE; + + } + + /** + * 房间 + */ + private final Room room; + /** + * 终端 + */ + private final Client client; + /** + * 房间标识 + */ + private final String roomId; + /** + * 终端标识 + */ + private final String clientId; + private Object rtpCapabilities; + private Object sctpCapabilities; + /** + * 发送通道 + */ + private Transport sendTransport; + /** + * 接收通道 + */ + private Transport recvTransport; + /** + * 生产者 + */ + private final Map producers; + /** + * 数据通道生产者 + */ + private final Map dataProducers; + + public ClientWrapper(Room room, Client client) { + this.room = room; + this.client = client; + this.roomId = room.getRoomId(); + this.clientId = client.clientId(); + this.producers = new ConcurrentHashMap<>(); + this.dataProducers = new ConcurrentHashMap<>(); + } + + /** + * @return 生产者数量 + */ + public Integer producerSize() { + return this.producers.size(); + } + + /** + * @return 消费者数量 + */ + public Integer consumerSize() { + return this.producers.values().stream() + .map(producer -> producer.getConsumers().size()) + .collect(Collectors.counting()) + .intValue(); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Consumer.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Consumer.java new file mode 100644 index 0000000..a09b677 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Consumer.java @@ -0,0 +1,20 @@ +package com.acgist.taoyao.signal.terminal.media; + +import com.acgist.taoyao.signal.client.Client; + +/** + * 消费者 + * + * @author acgist + */ +public class Consumer { + + private Client client; + private Room room; + /** + * 终端ID + * 来源终端ID-> + */ + private String clientId; + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/DataConsumer.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/DataConsumer.java new file mode 100644 index 0000000..3bd4d08 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/DataConsumer.java @@ -0,0 +1,5 @@ +package com.acgist.taoyao.signal.terminal.media; + +public class DataConsumer { + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/DataProducer.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/DataProducer.java new file mode 100644 index 0000000..a760f27 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/DataProducer.java @@ -0,0 +1,9 @@ +package com.acgist.taoyao.signal.terminal.media; + +import java.util.Map; + +public class DataProducer { + + private Map dataConsumers; + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Kind.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Kind.java new file mode 100644 index 0000000..503f62d --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Kind.java @@ -0,0 +1,35 @@ +package com.acgist.taoyao.signal.terminal.media; + +import com.acgist.taoyao.boot.model.MessageCodeException; + +/** + * 媒体类型 + * + * @author acgist + */ +public enum Kind { + + /** + * 音频 + */ + AUDIO, + /** + * 视频 + */ + VIDEO; + + /** + * @param value 类型 + * + * @return 类型 + */ + public static final Kind of(String value) { + for (Kind kind : Kind.values()) { + if(kind.name().equalsIgnoreCase(value)) { + return kind; + } + } + throw MessageCodeException.of("未知媒体类型:" + value); + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Producer.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Producer.java new file mode 100644 index 0000000..c6bbb62 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Producer.java @@ -0,0 +1,47 @@ +package com.acgist.taoyao.signal.terminal.media; + +import java.util.Map; + +import com.acgist.taoyao.signal.client.Client; + +import lombok.Getter; +import lombok.Setter; + +/** + * 生产者 + * + * @author acgist + */ +@Getter +@Setter +public class Producer { + + /** + * 终端 + */ + private final Client client; + /** + * 媒体类型 + */ + private final Kind kind; + /** + * 媒体流ID + */ + private final String streamId; + /** + * 消费者标识 + */ + private final String producerId; + /** + * 消费者 + */ + private Map consumers; + + public Producer(Client client, String kind, String streamId, String producerId) { + this.client = client; + this.kind = Kind.of(kind); + this.streamId = streamId; + this.producerId = producerId; + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Room.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Room.java new file mode 100644 index 0000000..bb2a36c --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Room.java @@ -0,0 +1,182 @@ +package com.acgist.taoyao.signal.terminal.media; + +import java.io.Closeable; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import com.acgist.taoyao.boot.model.Message; +import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientStatus; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** + * 房间 + * + * @author acgist + */ +@Slf4j +@Getter +@Setter +public class Room implements Closeable { + + /** + * 房间标识 + */ + private String roomId; + /** + * 密码 + * 设置密码之后进入房间需要验证密码 + */ + private String password; + /** + * 状态 + */ + private RoomStatus roomStatus; + /** + * 媒体服务 + */ + private Client mediaClient; + /** + * 终端 + */ + private final Map clients; + + /** + * @param mediaClient 媒体服务 + */ + public Room(Client mediaClient) { + this.roomStatus = new RoomStatus(); + this.mediaClient = mediaClient; + this.clients = new ConcurrentHashMap<>(); + } + + /** + * @return 终端状态列表 + */ + public List clientStatus() { + return this.clients.keySet().stream() + .map(Client::status) + .toList(); + } + + /** + * 终端进入 + * + * @param client 终端 + * + * @return 终端封装器 + */ + public ClientWrapper enter(Client client) { + synchronized (this.clients) { + ClientWrapper clientWrapper = this.clients.get(client); + if(clientWrapper != null) { + return clientWrapper; + } + clientWrapper = new ClientWrapper(this, client); + this.clients.put(client, clientWrapper); + this.roomStatus.setClientSize(this.roomStatus.getClientSize() + 1); + return clientWrapper; + } + } + + /** + * 终端离开 + * + * @param client 终端 + */ + public void leave(Client client) { + synchronized (this.clients) { + if(this.clients.remove(client) != null) { + this.roomStatus.setClientSize(this.roomStatus.getClientSize() - 1); + // TODO:资源释放 + } + } + } + + /** + * 媒体服务推送消息 + * + * @param message 消息 + */ + public void push(Message message) { + this.mediaClient.push(message); + } + + /** + * 媒体服务请求消息 + * + * @param message 消息 + * + * @return 响应 + */ + public Message request(Message message) { + return this.mediaClient.request(message); + } + + /** + * 广播消息 + * + * @param message 消息 + */ + public void broadcast(Message message) { + this.clients.keySet().forEach(v -> v.push(message)); + } + + /** + * 广播消息 + * + * @param from 发送终端 + * @param message 消息 + */ + public void broadcast(String from, Message message) { + this.clients.keySet().stream() + .filter(v -> !Objects.equals(from, v.clientId())) + .forEach(v -> v.push(message)); + } + + /** + * 广播消息 + * + * @param from 发送终端 + * @param message 消息 + */ + public void broadcast(Client from, Message message) { + this.clients.keySet().stream() + .filter(v -> v != from) + .forEach(v -> v.push(message)); + } + + /** + * + * @param client + * @return + */ + public ClientWrapper client(Client client) { + return this.clients.get(client); + } + + /** + * + * @param producerId + * @return + */ + public Producer producer(String producerId) { + return this.clients.values().stream() + .map(wrapper -> wrapper.getProducers().get(producerId)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + @Override + public void close() { + log.info("关闭房间:{}", this.roomId); + // TODO + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/RoomManager.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/RoomManager.java similarity index 68% rename from taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/RoomManager.java rename to taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/RoomManager.java index 7b978c0..ec7af04 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/RoomManager.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/RoomManager.java @@ -1,18 +1,17 @@ -package com.acgist.taoyao.signal.media; +package com.acgist.taoyao.signal.terminal.media; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; -import org.springframework.beans.factory.annotation.Autowired; - import com.acgist.taoyao.boot.annotation.Manager; import com.acgist.taoyao.boot.config.Constant; import com.acgist.taoyao.boot.model.Message; import com.acgist.taoyao.boot.model.MessageCodeException; import com.acgist.taoyao.boot.service.IdService; import com.acgist.taoyao.signal.client.Client; +import com.acgist.taoyao.signal.client.ClientManager; import lombok.extern.slf4j.Slf4j; @@ -25,15 +24,20 @@ import lombok.extern.slf4j.Slf4j; @Manager public class RoomManager { - @Autowired - private IdService idService; - @Autowired - private MediaClientManager mediaClientManager; - + private final IdService idService; + private final ClientManager clientManager; + /** * 房间列表 */ - private List rooms = new CopyOnWriteArrayList<>(); + private final List rooms; + + public RoomManager(IdService idService, ClientManager clientManager) { + this.idService = idService; + this.clientManager = clientManager; + this.rooms = new CopyOnWriteArrayList<>(); + } + /** * @param roomId 房间标识 @@ -61,7 +65,7 @@ public class RoomManager { */ public RoomStatus status(String roomId) { final Room room = this.room(roomId); - return room == null ? null : room.getStatus(); + return room == null ? null : room.getRoomStatus(); } /** @@ -69,7 +73,7 @@ public class RoomManager { */ public List status() { return this.rooms().stream() - .map(Room::getStatus) + .map(Room::getRoomStatus) .toList(); } @@ -79,18 +83,20 @@ public class RoomManager { * @param mediaClient 媒体服务终端 * @param message 消息 */ - public void recreate(MediaClient mediaClient, Message message) { + public void recreate(Client mediaClient, Message message) { this.rooms.stream() - .filter(room -> room.getMediaClient() == mediaClient) + .filter(room -> mediaClient.clientId().equals(room.getMediaClient().clientId())) .forEach(room -> { log.info("重建房间:{}", room.getRoomId()); final Message clone = message.cloneWithoutBody(); - clone.getHeader().setId(this.idService.buildIdToString()); + clone.getHeader().setId(this.idService.buildId()); clone.setBody(Map.of(Constant.ROOM_ID, room.getRoomId())); // 异步发送防止线程卡死 - mediaClient.send(clone); + mediaClient.push(clone); // 同步需要添加异步注解 // mediaClient.request(clone); + // 更新媒体服务 + room.setMediaClient(mediaClient); }); } @@ -99,30 +105,27 @@ public class RoomManager { * * @param name 名称 * @param password 密码 - * @param mediaId 媒体服务标识 + * @param mediaClientId 媒体服务终端标识 * @param message 消息 * * @return 房间信息 */ - public Room create(String name, String password, String mediaId, Message message) { - final MediaClient mediaClient = this.mediaClientManager.mediaClient(mediaId); + public Room create(String name, String password, String mediaClientId, Message message) { + final Client mediaClient = this.clientManager.clients(mediaClientId); if(mediaClient == null) { - throw MessageCodeException.of("无效媒体服务:" + mediaId); + throw MessageCodeException.of("无效媒体服务:" + mediaClientId); } - final String roomId = this.idService.buildIdToString(); - // 状态 - final RoomStatus roomStatus = new RoomStatus(); - roomStatus.setRoomId(roomId); - roomStatus.setName(name); - roomStatus.setMediaId(mediaId); - roomStatus.setClientSize(0L); + final String roomId = this.idService.buildUuid(); // 房间 - final Room room = new Room(); + final Room room = new Room(mediaClient); room.setRoomId(roomId); room.setPassword(password); - room.setStatus(roomStatus); - room.setMediaClient(mediaClient); - room.setClients(new CopyOnWriteArrayList<>()); + // 状态 + final RoomStatus roomStatus = room.getRoomStatus(); + roomStatus.setRoomId(roomId); + roomStatus.setName(name); + roomStatus.setMediaClientId(mediaClientId); + roomStatus.setClientSize(0L); // 创建媒体服务房间 message.setBody(Map.of(Constant.ROOM_ID, roomId)); mediaClient.request(message); diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/RoomStatus.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/RoomStatus.java similarity index 88% rename from taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/RoomStatus.java rename to taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/RoomStatus.java index e44b568..fbe6484 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/media/RoomStatus.java +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/RoomStatus.java @@ -1,4 +1,4 @@ -package com.acgist.taoyao.signal.media; +package com.acgist.taoyao.signal.terminal.media; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @@ -18,9 +18,9 @@ public class RoomStatus { private String roomId; @Schema(title = "房间名称", description = "房间名称") private String name; - @Schema(title = "媒体服务标识", description = "媒体服务标识") - private String mediaId; @Schema(title = "终端数量", description = "终端数量") private Long clientSize; + @Schema(title = "媒体服务标识", description = "媒体服务标识") + private String mediaClientId; } diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Transport.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Transport.java new file mode 100644 index 0000000..cbcfba4 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/media/Transport.java @@ -0,0 +1,71 @@ +package com.acgist.taoyao.signal.terminal.media; + +import java.io.Closeable; +import java.util.Map; + +import com.acgist.taoyao.boot.config.Constant; +import com.acgist.taoyao.boot.utils.MapUtils; +import com.acgist.taoyao.signal.client.Client; + +import lombok.Getter; +import lombok.Setter; + +/** + * 传输通道 + * + * @author acgist + */ +@Getter +@Setter +public class Transport implements Closeable { + + /** + * 房间 + */ + private final Room room; + /** + * 终端 + */ + private final Client client; + /** + * 房间标识 + */ + private final String roomId; + /** + * 终端标识 + */ + private final String clientId; + /** + * 通道标识 + */ + private final String transportId; + private Object iceCandidates; + private Object iceParameters; + private Object dtlsParameters; + private Object sctpParameters; + + public Transport(String transportId, Room room, Client client) { + this.transportId = transportId; + this.room = room; + this.client = client; + this.roomId = room.getRoomId(); + this.clientId = client.clientId(); + } + + /** + * 拷贝属性 + * + * @param body 消息主体 + */ + public void copy(Map body) { + this.iceCandidates = MapUtils.get(body, Constant.ICE_CANDIDATES); + this.iceParameters = MapUtils.get(body, Constant.ICE_PARAMETERS); + this.dtlsParameters = MapUtils.get(body, Constant.DTLS_PARAMETERS); + this.sctpParameters = MapUtils.get(body, Constant.SCTP_PARAMETERS); + } + + @Override + public void close() { + } + +} diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/package-info.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/package-info.java new file mode 100644 index 0000000..1e67879 --- /dev/null +++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/terminal/package-info.java @@ -0,0 +1,4 @@ +/** + * 终端功能代码 + */ +package com.acgist.taoyao.signal.terminal; \ No newline at end of file diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/wrapper/WebrtcPropertiesWrapper.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/wrapper/WebrtcPropertiesWrapper.java deleted file mode 100644 index 58f23b9..0000000 --- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/wrapper/WebrtcPropertiesWrapper.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.acgist.taoyao.signal.wrapper; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.acgist.taoyao.boot.config.Constant; -import com.acgist.taoyao.boot.config.WebrtcProperties; -import com.acgist.taoyao.boot.config.WebrtcStunProperties; -import com.acgist.taoyao.boot.config.WebrtcTurnProperties; -import com.acgist.taoyao.boot.service.IpService; -import com.fasterxml.jackson.annotation.JsonIgnore; - -import lombok.AllArgsConstructor; - -/** - * WebRTC配置包装器 - * - * @author acgist - */ -@AllArgsConstructor -public class WebrtcPropertiesWrapper extends WebrtcProperties { - - /** - * 终端IP - */ - @JsonIgnore - private String clientIp; - /** - * IP服务 - */ - @JsonIgnore - private IpService ipService; - /** - * WebRTC配置 - */ - @JsonIgnore - private WebrtcProperties webrtcProperties; - - @Override - public WebrtcStunProperties[] getStun() { - return this.webrtcProperties.getStun(); - } - - @Override - public WebrtcTurnProperties[] getTurn() { - return this.webrtcProperties.getTurn(); - } - - @Override - public List> getIceServers() { - final List> list = new ArrayList<>(); - final WebrtcStunProperties[] stunList = this.webrtcProperties.getStun(); - if(stunList != null) { - for (WebrtcStunProperties stun : stunList) { - if(this.ipService.subnetIp(stun.getHost(), this.clientIp)) { - final Map map = new HashMap<>(); - map.put(Constant.URLS, stun.getAddress()); - list.add(map); - } - } - } - final WebrtcTurnProperties[] turnList = this.webrtcProperties.getTurn(); - if(turnList != null) { - for (WebrtcTurnProperties turn : turnList) { - if(this.ipService.subnetIp(turn.getHost(), this.clientIp)) { - final Map map = new HashMap<>(); - map.put(Constant.URLS, turn.getAddress()); - map.put(Constant.USERNAME, turn.getUsername()); - map.put(Constant.CREDENTIAL, turn.getPassword()); - list.add(map); - } - } - } - return list; - } - -} diff --git a/taoyao-signal-server/taoyao-signal/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/taoyao-signal-server/taoyao-signal/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 07a20fa..8c71f70 100644 --- a/taoyao-signal-server/taoyao-signal/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/taoyao-signal-server/taoyao-signal/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,5 +1,4 @@ com.acgist.taoyao.signal.configuration.ScriptAutoConfiguration com.acgist.taoyao.signal.configuration.SignalAutoConfiguration -com.acgist.taoyao.signal.configuration.MediaClientAutoConfiguration com.acgist.taoyao.signal.configuration.SocketSignalAutoConfiguration com.acgist.taoyao.signal.configuration.WebSocketSignalAutoConfiguration