├── README.md ├── doc ├── codeStructure.md ├── errorCode.md └── protocol.md ├── image ├── build_war.png ├── change_jdk_1.png ├── change_jdk_2.png ├── check_jdk.png ├── config_cvm.png ├── cvm_image.png ├── cvm_info.png ├── im_config.png ├── im_config_info.png ├── im_list.png ├── im_new.png ├── im_open.png ├── live_code.png ├── live_config.png ├── live_open.png ├── new_cvm.png ├── restart_tomcat.png ├── upload_war.png ├── upload_war_2.png └── war_decompression.png ├── pom.xml ├── roomservice.iml └── src └── main ├── java └── com │ ├── tencent │ └── qcloud │ │ └── roomservice │ │ ├── common │ │ ├── CORSFilter.java │ │ └── Config.java │ │ ├── controller │ │ ├── DoubleRoom.java │ │ ├── LiveRoom.java │ │ ├── MultiRoom.java │ │ └── Util.java │ │ ├── logic │ │ ├── IMMgr.java │ │ ├── LiveUtil.java │ │ └── RoomMgr.java │ │ ├── pojo │ │ ├── Audience.java │ │ ├── Pusher.java │ │ ├── Request │ │ │ ├── AddAudienceReq.java │ │ │ ├── AddPusherReq.java │ │ │ ├── CreateGroupReq.java │ │ │ ├── CreateRoomReq.java │ │ │ ├── DelAudienceReq.java │ │ │ ├── DeletePusherReq.java │ │ │ ├── DestroyGroupReq.java │ │ │ ├── DestroyRoomReq.java │ │ │ ├── GetAudiencesReq.java │ │ │ ├── GetCustomInfoReq.java │ │ │ ├── GetLoginInfoReq.java │ │ │ ├── GetPushUrlReq.java │ │ │ ├── GetPushersReq.java │ │ │ ├── GetRoomListReq.java │ │ │ ├── NotifyPusherChangeReq.java │ │ │ ├── PusherHeartbeatReq.java │ │ │ └── SetCustomInfoReq.java │ │ ├── Response │ │ │ ├── BaseRsp.java │ │ │ ├── CreateRoomRsp.java │ │ │ ├── GetAudiencesRsp.java │ │ │ ├── GetCustomInfoRsp.java │ │ │ ├── GetLoginInfoRsp.java │ │ │ ├── GetPushUrlRsp.java │ │ │ ├── GetRoomListRsp.java │ │ │ ├── GetStreamStatusOutPut.java │ │ │ ├── GetStreamStatusRsp.java │ │ │ ├── GetTestPushUrlRsp.java │ │ │ ├── GetTestRtmpAccUrlRsp.java │ │ │ ├── LoginRsp.java │ │ │ └── MergeStreamRsp.java │ │ ├── Room.java │ │ └── StreamIDS.java │ │ ├── service │ │ ├── RoomService.java │ │ ├── UtilService.java │ │ └── impl │ │ │ ├── RoomServiceImpl.java │ │ │ └── UtilServiceImpl.java │ │ └── utils │ │ ├── RestTemplateConfig.java │ │ └── Utils.java │ └── tls │ ├── base64_url │ └── base64_url.java │ └── tls_sigature │ └── tls_sigature.java ├── resources ├── applicationContext.xml └── logback.xml └── webapp ├── WEB-INF ├── dispatcher-servlet.xml └── web.xml └── index.jsp /README.md: -------------------------------------------------------------------------------- 1 | # 腾讯云音视频多人会话解决方案服务端 2 | 3 | ## 1.项目简介 4 | RTCRoom Server 是配合 live-room(用于直播连麦)和 rtc-room(用于视频通话)的后台组件,主要作用有: 5 | 6 | - 生成推流、播放地址 7 | - 生成IM签名,用于IM独立模式下的用户登录 8 | - 管理IM聊天室,聊天室的创建和销毁、成员进出通知 9 | - 双人/多人音视频管理视频位 10 | 11 | **特别说明:** 12 | 13 | - 房间管理采用 java对象直接在内存中进行管理。房间信息具有动态和实效性,因此没有采用数据库做持久存储,而是在内存中动态管理。 14 | 15 | ## 2.云服务开通 16 | 17 | ### 开通直播服务 18 | 19 | #### 申请开通视频直播服务 20 | 进入 [直播管理控制台](https://console.cloud.tencent.com/live),如果服务还没有开通,则会有如下提示: 21 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/live_open.png) 22 | 点击申请开通,之后会进入腾讯云人工审核阶段,审核通过后即可开通。 23 | 24 | #### 配置直播码 25 | 直播服务开通后,进入【直播控制台】>【直播码接入】>【[接入配置](https://console.cloud.tencent.com/live/livecodemanage)】 完成相关配置,即可开启直播码服务: 26 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/live_code.png) 27 | 点击【确定接入】按钮即可。 28 | 29 | #### 获取直播服务配置信息 30 | 从直播控制台获取`APP_ID`、`APP_BIZID`、`API_KEY`,后面配置服务器会用到: 31 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/live_config.png) 32 | 33 | ### 开通云通信服务 34 | #### 申请开通云通讯服务 35 | 进入[云通讯管理控制台](https://console.cloud.tencent.com/avc),如果还没有服务,直接点击**直接开通云通讯**按钮即可。新认证的腾讯云账号,云通讯的应用列表是空的,如下图: 36 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/im_open.png) 37 | 38 | 点击**创建应用接入**按钮创建一个新的应用接入,即您要接入腾讯云IM通讯服务的App的名字,我们的测试应用名称叫做“RTMPRoom”,如下图所示: 39 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/im_new.png) 40 | 41 | 点击确定按钮,之后就可以在应用列表中看到刚刚添加的项目了,如下图所示: 42 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/im_list.png) 43 | 44 | #### 配置独立模式 45 | 上图的列表中,右侧有一个**应用配置**按钮,点击这里进入下一步的配置工作,如下图所示。 46 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/im_config.png) 47 | 48 | #### 获取云通讯服务配置信息 49 | 从云通信控制台获取`IM_SDKAPPID`、`IM_ACCOUNTTYPE`、`ADMINISTRATOR`、`PRIVATEKEY`、`PUBLICKEY`,后面配置服务器会用到: 50 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/im_config_info.png) 51 | 52 | 从验证方式中下载公私钥,解压出来将private_key用文本编辑器打开,如: 53 | 54 | ```bash 55 | -----BEGIN PRIVATE KEY----- 56 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 57 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 58 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 59 | -----END PRIVATE KEY----- 60 | ``` 61 | 62 | 将其转换成字符串形式如下所示,后面在server配置文件中使用,请注意每行后面要加入\r\n: 63 | 64 | ```bash 65 | "-----BEGIN PRIVATE KEY-----\r\n"+ 66 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"+ 67 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"+ 68 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"+ 69 | "-----END PRIVATE KEY-----\r\n" 70 | ``` 71 | PUBLICKEY也采用同样的方式编辑,供后续使用。 72 | 73 | ## 3.修改配置信息 74 | 后台使用 spring 框架搭建,开发环境是 IntelliJ IDEA,java 需要使用 1.8 或以上。用 IntelliJ IDEA 导入工程源码,把`\src\main\java\com\tencent\qcloud\roomservice\common`目录下的 Config.java 中`APP_ID、APP_BIZID、PUSH_SECRET_KEY、APIKEY、IM_SDKAPPID、IM_ACCOUNTTYPE、ADMINISTRATOR、PRIVATEKEY、PUBLICKEY`等配置项替换成您的腾讯云账号信息。 75 | 76 | ```java 77 | public class Config { 78 | 79 | /** 80 | * 需要开通云直播服务 81 | * 参考指引 @https://cloud.tencent.com/document/product/454/15187#.E4.BA.91.E6.9C.8D.E5.8A.A1.E5.BC.80.E9.80.9A 82 | * 有介绍APP_BIZID 和 PUSH_SECRET_KEY的获取方法。 83 | */ 84 | public class Live { 85 | /** 86 | * 云直播 APP_ID = 和 APIKEY 主要用于腾讯云直播common cgi请求。appid 用于表示您是哪个客户,APIKey参与了请求签名sign的生成。 87 | * 后台用他们来校验common cgi调用的合法性 88 | */ 89 | public final static int APP_ID = 0; 90 | 91 | /** 92 | * 云直播 APP_BIZID = 和pushSecretKey 主要用于推流地址的生成,填写错误,会导致推流地址不合法,推流请求被腾讯云直播服务器拒绝 93 | */ 94 | public final static int APP_BIZID = 0; 95 | 96 | /** 97 | * 云直播 推流防盗链key = 和 APP_BIZID 主要用于推流地址的生成,填写错误,会导致推流地址不合法,推流请求被腾讯云直播服务器拒绝 98 | */ 99 | public final static String PUSH_SECRET_KEY = ""; 100 | 101 | /** 102 | * 云直播 API鉴权key = 和appID 主要用于common cgi请求。appid 用于表示您是哪个客户,APIKey参与了请求签名sign的生成。 103 | * 后台用他们来校验common cgi调用的合法性。 104 | */ 105 | public final static String APIKEY = ""; 106 | 107 | // 云直播 推流有效期单位秒 默认7天 108 | public final static int validTime = 3600 * 24 * 7; 109 | } 110 | 111 | /** 112 | * 需要开通云通信服务 113 | * 参考指引 @https://cloud.tencent.com/document/product/454/7953#3.-.E4.BA.91.E9.80.9A.E8.AE.AF.E6.9C.8D.E5.8A.A1.EF.BC.88im.EF.BC.89 114 | * 有介绍appid 和 accType的获取方法。以及私钥文件的下载方法。 115 | */ 116 | public class IM { 117 | /** 118 | * 云通信 IM_SDKAPPID = IM_ACCOUNTTYPE 和 PRIVATEKEY 是云通信独立模式下,为您的独立账号 identifer, 119 | * 派发访问云通信服务的userSig票据的重要信息,填写错误会导致IM登录失败,IM功能不可用 120 | */ 121 | public final static long IM_SDKAPPID = 0; 122 | 123 | /** 124 | * 云通信 账号集成类型 IM_ACCOUNTTYPE = IM_SDKAPPID 和 PRIVATEKEY 是云通信独立模式下,为您的独立账户identifer, 125 | * 派发访问云通信服务的userSig票据的重要信息,填写错误会导致IM登录失败,IM功能不可用 126 | */ 127 | public final static String IM_ACCOUNTTYPE = ""; 128 | 129 | // 云通信 管理员账号 130 | public final static String ADMINISTRATOR = "admin"; 131 | 132 | /** 133 | * 云通信 派发usersig 采用非对称加密算法RSA,用私钥生成签名。PRIVATEKEY就是用于生成签名的私钥,私钥文件可以在互动直播控制台获取 134 | * 配置privateKey 135 | * 将private_key文件的内容按下面的方式填写到 PRIVATEKEY。 136 | */ 137 | public final static String PRIVATEKEY = "-----BEGIN PRIVATE KEY-----\r\n"+ 138 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"+ 139 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"+ 140 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"+ 141 | "-----END PRIVATE KEY-----\r\n"; 142 | /** 143 | * 云通信 验证usersig 所用的公钥 144 | */ 145 | public final static String PUBLICKEY = "-----BEGIN PUBLIC KEY-----\n" + 146 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"+ 147 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"+ 148 | "-----END PUBLIC KEY-----\n"; 149 | } 150 | 151 | 152 | /** 153 | * 多人音视频房间相关参数 154 | */ 155 | public class MultiRoom { 156 | // 房间容量上限 157 | public final static int maxMembers = 4; 158 | 159 | // 心跳超时 单位秒 160 | public final static int heartBeatTimeout = 20; 161 | 162 | // 空闲房间超时 房间创建后一直没有人进入,超过给定时间将会被后台回收,单位秒 163 | public final static int maxIdleDuration = 30; 164 | } 165 | 166 | /** 167 | * 双人音视频房间相关参数 168 | */ 169 | public class DoubleRoom { 170 | // 心跳超时 单位秒 171 | public final static int heartBeatTimeout = 20; 172 | 173 | // 空闲房间超时 房间创建后一直没有人进入,超过给定时间将会被后台回收,单位秒 174 | public final static int maxIdleDuration = 30; 175 | } 176 | 177 | /** 178 | * 直播连麦房间相关参数 179 | */ 180 | public class LiveRoom { 181 | // 房间容量上限 182 | public final static int maxMembers = 4; 183 | 184 | // 心跳超时 单位秒 185 | public final static int heartBeatTimeout = 20; 186 | 187 | // 空闲房间超时 房间创建后一直没有人进入,超过给定时间将会被后台回收,单位秒 188 | public final static int maxIdleDuration = 30; 189 | 190 | // 最大观众列表长度 191 | public final static int maxAudiencesLen = 30; 192 | } 193 | 194 | /** 195 | * 创建者退出的时候是否需要删除房间 196 | * 默认false。表示房间所有成员是对等的,第一个进房的人退出并不会销毁房间,只有房间没人的时候才会销毁房间。 197 | * 此配置项只针对双人和多人实时音视频 198 | */ 199 | public final static boolean isCreatorDestroyRoom = false; 200 | } 201 | ``` 202 | ## 4.准备发布包 203 | 首先到 [IntelliJ IDEA 官网](https://www.jetbrains.com/idea/download/#section=windows) 下载 IntelliJ IDEA 工具,注意这里要选择 Ultimate 版 。因为只有 Ultimate 版本才拥有Java EE 相关插件以及功能。 204 | ![](https://main.qcloudimg.com/raw/3cad5d08eb48255e39fcd304aa7c97e6.png) 205 | 206 | 成功安装 IntelliJ IDEA,接着配置按照第 3 步讲述, 修改下配置信息。 207 | 208 | 配置修改好之后,选择 Build -> Build Artifacts 开始打包,打包完成后到输出路径拿到 roomservice.war 包。选择 File -> Project Structure 可以查看输出路径,如下图中的 E:\RoomService\rtcroom_server_java-master\target。 209 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/build_war.png) 210 | 211 | 注意:roomservice.war 包解压后,里面是应该是 .class 的文件,否则说明打包有问题。 212 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_server_java/raw/master/image/war_decompression.png) 213 | 214 | ## 5.部署服务器 215 | 以部署到腾讯云服务器为例,描述部署过程。采用CentOS + nginx + Apache Tomcat + java 的 环境。 216 | 217 | > 注意: 218 | > jdk版本需要为1.8以上。 219 | > 小程序和ios都要求服务器支持HTTPS请求。 220 | 221 | ### 申请云服务器 222 | 1、新建 CVM 主机。 223 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/new_cvm.png) 224 | 225 | 2、从服务市场选取镜像。 226 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/cvm_image.png) 227 | 228 | 3、配置硬盘、网络、云主机访问密码,并且妥善保管好密码,然后设置安全组。 229 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/config_cvm.png) 230 | 231 | 4、付款后生成云主机。点击登录可以通过腾讯云的网页 shell 进行访问。 232 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/cvm_info.png) 233 | 234 | 5、查看/切换 JDK 版本。 235 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/change_jdk_1.png) 236 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/change_jdk_2.png) 237 | 修改完成后可以用 `sh version.sh` 命令查看是否修改成功。 238 | 239 | 使用 `sudo update-alternatives --display java` 命令可以查看当前已安装的 JDK 版本。 240 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/check_jdk.png) 241 | 242 | ### 部署war包到服务器 243 | 建议使用 FileZilla 或者 FlashFXP 等可视化界面工具。 244 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/upload_war.png) 245 | 246 | 将打包好的 webrtc.war 包上传 tomcat 的 webapps 目录下 247 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/upload_war_2.png) 248 | 249 | 250 | 6、重新启动 tomcat。 251 | 上传完成后,如果 tomcat 服务未启动,可以进入 tomcat/bin 目录,通过 ./startup.sh start 命令启动 tomcat。“Tomcat started.”表示服务启动成功。 252 | ![](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/raw/master/image/restart_tomcat.png) 253 | 254 | ### nginx 配置 255 | 如果您已经有**域名**以及域名对应的**SSL证书**存放在`/data/release/nginx/`目录下,请将下面配置内容中的 256 | - [1] 替换成您自己的域名。 257 | - [2-1] 替换成 SSL 证书的 crt 文件名。 258 | - [2-2] 替换成 SSL 证书的 key 文件名。 259 | 260 | ``` 261 | upstream app_weapp { 262 | server localhost:5757; 263 | keepalive 8; 264 | } 265 | 266 | #http请求转为 https请求 267 | server { 268 | listen 80; 269 | server_name [1]; 270 | 271 | rewrite ^(.*)$ https://$server_name$1 permanent; 272 | } 273 | 274 | #https请求 275 | server { 276 | listen 443; 277 | server_name [1]; 278 | 279 | ssl on; 280 | 281 | ssl_certificate /data/release/nginx/[2-1]; 282 | ssl_certificate_key /data/release/nginx/[2-2]; 283 | ssl_session_timeout 5m; 284 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 285 | ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA; 286 | ssl_session_cache shared:SSL:50m; 287 | ssl_prefer_server_ciphers on; 288 | 289 | # tomcat默认端口是8080,转发给tomcat处理 290 | location / { 291 | proxy_pass http://127.0.0.1:8080; 292 | proxy_redirect off; 293 | proxy_set_header X-Real-IP $remote_addr; 294 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 295 | } 296 | } 297 | ``` 298 | 299 | ### 运行服务 300 | 输入命令,启动Nginx服务。 301 | ``` 302 | nginx -s reload 303 | ``` 304 | 305 | ### 验证服务 306 | 通过浏览器地址栏访问接口,请求地址格式`https://您自己的域名/roomservice/weapp/utils/get_login_info`,如果没有配置证书,可以用http请求来验证服务。 307 | 308 | ## 6.小程序和windows Demo部署 309 | #### 1.小程序部署 310 | 下载 [小程序](https://github.com/TencentVideoCloudMLVBDev/RTCRoomDemo) 源码,将 wxlite/config.js 文件中的`serverUrl`和 `roomServiceUrl`修改成: 311 | ``` 312 | https://您自己的域名/roomservice/ 313 | ``` 314 | 315 | #### 2.windows Demo部署 316 | 下载 [windows web demo](https://github.com/TencentVideoCloudMLVBDev/webexe_web) 源码,将 liveroom.html、double.html 文件中的`RoomServerDomain`修改成: 317 | ``` 318 | https://您自己的域名/roomservice/ 319 | ``` 320 | 321 | ## 7.开发者资源 322 | * [项目结构](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/blob/master/doc/codeStructure.md) - 后台源码结构 323 | * [协议文档](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/blob/master/doc/protocol.md) - 后台协议文档 324 | * [常见错误码](https://github.com/TencentVideoCloudMLVBDev/roomservice_java/blob/master/doc/errorCode.md) - 错误码文档 -------------------------------------------------------------------------------- /doc/codeStructure.md: -------------------------------------------------------------------------------- 1 | ## 项目结构 2 | ``` 3 | roomserver 4 | ├── README.md //项目总体说明文档 5 | ├── pom.xml //maven 依赖配置文件 6 | └── src/main 7 | ├── java/com/tencent/qcloud/roomservice 8 | │ ├── common 9 | │ │   ├── CORSFilter.java //跨域设置类 10 | │ │ └── Config.java //后台配置,需要修改这里 11 | │ ├── controller 12 | │ │ ├── DoubleRoom.java //双人房间后台协议总入口 13 | │ │ ├── LiveRoom.java //直播房间后台协议总入口 14 | │ │ ├── MultiRoom.java //多人房间后台协议总入口 15 | │ │ └── Util.java //通用后台协议总入口 16 | │ ├── logic 17 | │ │ ├── IMMgr.java //云通信逻辑实现 18 | │ │ ├── LiveUtil.java //流管理逻辑实现 19 | │ │ └── RoomMgr.java //房间管理实现 20 | │ ├── pojo 21 | │ │ ├── Audience.java //观众结构定义 22 | │ │ ├── Pusher.java //推流者结构定义 23 | │ │ ├── Request 24 | │ │ │ ├── AddAudienceReq.java //增加观众请求定义 25 | │ │ │ ├── AddPusherReq.java //增加推流者请求定义 26 | │ │ │ ├── CreateGroupReq.java //建群请求定义,后台调用云通信rest api创建群组时候用到 27 | │ │ │ ├── CreateRoomReq.java //建房请求定义 28 | │ │ │ ├── DelAudienceReq.java //删除观众请求定义 29 | │ │ │ ├── DeletePusherReq.java //删除推流者请求定义 30 | │ │ │ ├── DestroyGroupReq.java //销毁群请求定义,后台调用云通信rest api销毁群组时候用到 31 | │ │ │ ├── DestroyRoomReq.java //销毁房间请求定义 32 | │ │ │ ├── GetAudiencesReq.java //获取观众列表请求定义 33 | │ │ │ ├── GetCustomInfoReq.java //获取直播房间自定义信息请求定义 34 | │ │ │ ├── GetLoginInfoReq.java //获取登录信息请求定义 35 | │ │ │ ├── GetPushersReq.java //获取推流者列表请求定义 36 | │ │ │ ├── GetPushUrlReq.java //获取推流地址请求定义 37 | │ │ │ ├── GetRoomListReq.java //获取房间列表请求定义 38 | │ │ │ ├── NotifyPusherChangeReq.java //通知推流者成员变化请求定义,后台调用云通信rest api发送群系统通知pusher变化的时候用到 39 | │ │ │ ├── PusherHeartbeatReq.java //pusher心跳请求定义 40 | │ │ │ └── SetCustomInfoReq.java //直播房间设置自定义信息请求定义 41 | │ │ ├── Response 42 | │ │ │ ├── BaseRsp.java。 //回包通用定义 43 | │ │ │ ├── CreateRoomRsp.java //建房回包定义 44 | │ │ │ ├── GetAudiencesRsp.java //获取观众列表回包定义 45 | │ │ │ ├── GetCustomInfoRsp.java //获取直播房间设置自定义信息回包定义 46 | │ │ │ ├── GetLoginInfoRsp.java //获取登录信息回包定义 47 | │ │ │ ├── GetPushUrlRsp.java //获取推流地址回包定义 48 | │ │ │ ├── GetRoomListRsp.java //获取房间列表回包定义 49 | │ │ │ ├── GetStreamStatusOutPut.java //获取流状态回包定义 50 | │ │ │ ├── GetStreamStatusRsp.java //获取流状态回包定义,包含GetStreamStatusOutPut 51 | │ │ │ ├── GetTestPushUrlRsp.java //获取一组推流播放地址回包定义 52 | │ │ │ ├── GetTestRtmpAccUrlRsp.java //获取加速拉流地址回包定义 53 | │ │ │ ├── LoginRsp.java //登录回包定义 54 | │ │ │ └── MergeStreamRsp.java //混流回包定义 55 | │ │ ├── Room.java //房间结构定义 56 | │ │ └── StreamIDS.java //流id结构定义 57 | │ ├── service 58 | │ │ ├── impl 59 | │ │ │ ├── RoomServiceImpl.java //房间管理服务service实现 60 | │ │ │ └── UtilServiceImpl.java //通用service实现 61 | │ │ ├── RoomService.java //房间管理服务service接口 62 | │ │ └── UtilService.java //通用service接口 63 | │ ├── utils 64 | │ │ ├── RestTemplateConfig.java //http请求配置 65 | │ │ └── Utils.java //通用类 66 | ├── java/com/tls 67 | │ ├── base64_url 68 | │ │ └── base64_url.java //base64编解码类 69 | │ ├── tls_sigature 70 | │ │ └── tls_sigature.java //云通信签名生成和校验类 71 | ├── resources 72 | │ ├── applicationContext.xml //spring配置文件 73 | │ └── logback.xml //log配置文件 74 | └── webapp/WEB-INF 75 | ├── dispatcher-servlet.xml //spring配置文件 76 | ├── web.xml //后台配置文件 77 | └── lib 78 | ``` 79 | 后台使用spring框架搭建,开发环境是IntelliJ IDEA,java需要使用1.8 80 | 81 | `pom.xml` 是 maven 依赖配置文件。 82 | 83 | `DoubleRoom.java` 是 服务器端 双人房间业务逻辑。 84 | 85 | `LiveRoom.java` 是 服务器端 直播房间业务逻辑。 86 | 87 | `MultiRoom.java` 是 服务器端 多人房间业务逻辑。 88 | 89 | `Util.java` 是 服务器端 辅助业务逻辑。 90 | 91 | `logic/IMMgr.java` 云通信相关的处理,主要功能有: 92 | ``` 93 | getLoginInfo, // 获取云通信的登录信息,主要是计算云通信 账号登录IM所需要的userSig票据 94 | createGroup, // 创建IM聊天室,通过云通信提供的服务端对接用的RestFul API实现 95 | destroyGroup, // 销毁IM聊天室,通过云通信提供的服务端对接用的RestFul API实现 96 | notifyPushersChange // IM聊天室成员进入和退出系统消息通知,通过云通信提供的服务端对接用的RestFul API实现 97 | ``` 98 | 99 | `logic/RoomMgr.java` 实时音视频房间管理模块,负责`双人`、`多人`、`直播`视频房间的创建,销毁,增加成员,删除成员,获取房间列表,获取房间成员列表等功能函数;另外也负责房间成员的心跳检查,对超时的成员进行删除处理。 100 | 101 | `logic/LiveUtil.java` 云直播辅助函数,负责生成推流地址以及播放地址。外加一些用户ID分配和房间ID分配的功能函数。 102 | 103 | 后台使用logback来生成日志,主要记录info、warn、error三大类日志。日志存储在服务器Apache-Tomcat的logs目录`{CATALINA_HOME}/logs/`下,根据级别日志文件名分别为`roomservice_info.log`、`roomservice_warn.log`、`roomservice_error.log`。以上默认配置可以通过修改`logback.xml`来调整。 104 | 日志配置: 105 | ```xml 106 | 107 | 108 | 109 | UTF-8 110 | 111 | [%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n 112 | 113 | 114 | DEBUG 115 | 116 | 117 | 118 | 119 | ${CATALINA_HOME}/logs/roomservice_info.log 120 | 121 | ${CATALINA_HOME}/logs/roomservice_info.log.%d{yyyy-MM-dd}.gz 122 | true 123 | 10 124 | 125 | 126 | [%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n 127 | 128 | 129 | INFO 130 | ACCEPT 131 | DENY 132 | 133 | 134 | 135 | 136 | ${CATALINA_HOME}/logs/roomservice_warn.log 137 | 138 | ${CATALINA_HOME}/logs/roomservice_warn.log.%d{yyyy-MM-dd}.gz 139 | true 140 | 10 141 | 142 | 143 | [%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n 144 | 145 | 146 | WARN 147 | ACCEPT 148 | DENY 149 | 150 | 151 | 152 | 153 | 154 | ${CATALINA_HOME}/logs/roomservice_error.log 155 | 156 | ${CATALINA_HOME}/logs/roomservice_error.log.%d{yyyy-MM-dd}.gz 157 | true 158 | 10 159 | 160 | 161 | [%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n 162 | 163 | 164 | ERROR 165 | ACCEPT 166 | DENY 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | ``` -------------------------------------------------------------------------------- /doc/errorCode.md: -------------------------------------------------------------------------------- 1 | ## 常见错误码含义 2 | 3 | | 错误码 | 具体含义 | 4 | |---------|---------| 5 | | 415 | Unsupported Media Type。请求包体不是json格式 | 6 | | 400 | Bad Request。请求包体是json格式,但是字段不符合协议。在用postman调试后台接口的时候,body至少要填一对花括号{} | 7 | | 500 | Internal Server Error 服务器内部错误,根据具体的错误信息分析。一般是Config.java配置有问题或者java版本不合要求导致 | 8 | -------------------------------------------------------------------------------- /doc/protocol.md: -------------------------------------------------------------------------------- 1 | # 后台协议设计 2 | 3 | * 接口命名采用c风格,举例 get_push_url 4 | * 接口参数命名采用驼峰风格,举例 roomID 5 | * 大部分请求方式是POST,请求的body是Json格式。有少数get请求接口,会在接口说明里指出。 6 | 7 | ## 通用`utils`接口说明 8 | 9 | ### get_login_info 获取sdkAppID、accType、userID、userSig信息 10 | 11 | 注意:get请求接口,参数是拼在URL后面的 12 | 13 | #### 参数说明 14 | 15 | | 参数名称 | 类型 | 是否必填 | 说明 | 16 | | ------ | ---------- | ---- | ------------------ | 17 | | userID | String | 是 | 用户id,可为空,为空则随机分配 | 18 | 19 | #### 返回结果说明 20 | 21 | | 属性名称 | 类型 | 说明 | 22 | | ------- | ---------- | ------ | 23 | | code | int | 返回码,0表示成功 | 24 | | message | String | 描述信息 | 25 | | sdkAppID | String | 云通信的sdkAppID | 26 | | accType | String | 云通信的accountType | 27 | | userID | String | 用户id | 28 | | userSig | String | 用户签名 | 29 | 30 | ### get_test_pushurl 获取一个随机的推流地址 31 | 32 | 注意:get请求接口 33 | 34 | #### 参数说明 35 | 36 | 无入参 37 | 38 | #### 返回结果说明 39 | 40 | | 属性名称 | 类型 | 说明 | 41 | | ------- | ---------- | ------ | 42 | | url_push | String | 推流地址 | 43 | | url_play_flv | String | flv播放地址 | 44 | | url_play_rtmp | String | rtmp播放地址 | 45 | | url_play_hls | String | hls播放地址 | 46 | | url_play_acc | String | 低延时播放地址 | 47 | 48 | ### get_test_rtmpaccurl 获取体验低延时播放的地址 49 | 50 | #### 参数说明 51 | 52 | 无入参 53 | 54 | #### 返回结果说明 55 | 56 | | 属性名称 | 类型 | 说明 | 57 | | ------- | ---------- | ------ | 58 | | url_rtmpacc | String | 体验低延时播放的地址 | 59 | 60 | ## 直播房间`live_room`接口说明 61 | 62 | ### login 登录接口,校验im签名合法性并派发token,后续请求需要校验token。 63 | 64 | #### 参数说明 65 | 66 | 注意:参数拼接在URL后面 67 | 68 | | 参数名称 | 类型 | 是否必填 | 说明 | 69 | | ------ | ---------- | ---- | ------------------ | 70 | | sdkAppID | String | 是 | 云通信的sdkAppID | 71 | | accountType | String | 是 | 云通信的accountType | 72 | | userID | String | 是 | userID | 73 | | userSig | String | 是 | 云通信派发的userSig | 74 | 75 | #### 返回结果说明 76 | 77 | | 属性名称 | 类型 | 说明 | 78 | | ------- | ---------- | ------ | 79 | | code | int | 返回码,0表示成功 | 80 | | message | String | 描述信息 | 81 | | userID | String | 用户id | 82 | | token | String | 登录成功后产生的token | 83 | 84 | ### logout 退出登录接口,清理后台保存的token信息 85 | 86 | #### 参数说明 87 | 88 | 注意:userID和token参数拼接在URL后面。后续所有接口都需要带上userID和token,且是拼接在URL后面的,后面不再赘述。 89 | 90 | | 参数名称 | 类型 | 是否必填 | 说明 | 91 | | ------ | ---------- | ---- | ------------------ | 92 | | userID | String | 是 | userID | 93 | | token | String | 是 | 登录成功后返回的token | 94 | 95 | #### 返回结果说明 96 | 97 | | 属性名称 | 类型 | 说明 | 98 | | ------- | ---------- | ------ | 99 | | code | int | 返回码,0表示成功 | 100 | | message | String | 描述信息 | 101 | 102 | ### get_push_url 获取推流地址接口 103 | 104 | #### 参数说明 105 | 106 | | 参数名称 | 类型 | 是否必填 | 说明 | 107 | | ------ | ---------- | ---- | ------------------ | 108 | | userID | String | 是 | userID | 109 | | token | String | 是 | 登录成功后返回的token | 110 | 111 | #### 返回结果说明 112 | 113 | | 属性名称 | 类型 | 说明 | 114 | | ------- | ---------- | ------ | 115 | | code | int | 返回码,0表示成功 | 116 | | message | String | 描述信息 | 117 | | pushURL | String | 推流地址 | 118 | 119 | ### get_room_list 获取直播房间列表 120 | 121 | #### 参数说明 122 | 123 | | 参数名称 | 类型 | 是否必填 | 说明 | 124 | | ------ | ---------- | ---- | ------------------ | 125 | | userID | String | 是 | userID | 126 | | token | String | 是 | 登录成功后返回的token | 127 | | cnt | int | 是 | 房间数量 | 128 | | index | int | 是 | 起始下标 | 129 | 130 | #### 返回结果说明 131 | 132 | | 属性名称 | 类型 | 说明 | 133 | | ------- | ---------- | ------ | 134 | | code | int | 返回码,0表示成功 | 135 | | message | String | 描述信息 | 136 | | rooms | List | 直播房间列表 | 137 | 138 | #### Room 属性说明 139 | 140 | | 属性名称 | 类型 | 说明 | 141 | | ------- | ---------- | ------ | 142 | | roomID | String | 房间ID | 143 | | roomInfo | String | 房间名称 | 144 | | roomCreator | String | 房间创建者userID | 145 | | mixedPlayURL | String | 混流播放地址 | 146 | | pushers | List | 推流者列表 | 147 | 148 | #### Pusher 属性说明 149 | 150 | | 属性名称 | 类型 | 说明 | 151 | | ------- | ---------- | ------ | 152 | | userID | String | 用户id | 153 | | userName | String | 用户昵称 | 154 | | userAvatar | String | 用户头像 | 155 | | accelerateURL | String | 低延时播放地址 | 156 | 157 | ### get_pushers 获取指定房间的推流者列表 158 | 159 | #### 参数说明 160 | 161 | | 参数名称 | 类型 | 是否必填 | 说明 | 162 | | ------ | ---------- | ---- | ------------------ | 163 | | userID | String | 是 | userID | 164 | | token | String | 是 | 登录成功后返回的token | 165 | | roomID | String | 是 | 房间ID | 166 | 167 | #### 返回结果说明 168 | 169 | | 属性名称 | 类型 | 说明 | 170 | | ------- | ---------- | ------ | 171 | | code | int | 返回码,0表示成功 | 172 | | message | String | 描述信息 | 173 | | roomID | String | 房间ID | 174 | | roomInfo | String | 房间名称 | 175 | | roomCreator | String | 房间创建者userID | 176 | | mixedPlayURL | String | 混流播放地址 | 177 | | pushers | List | 推流者列表 | 178 | 179 | #### Pusher 属性说明 180 | 181 | | 属性名称 | 类型 | 说明 | 182 | | ------- | ---------- | ------ | 183 | | userID | String | 用户id | 184 | | userName | String | 用户昵称 | 185 | | userAvatar | String | 用户头像 | 186 | | accelerateURL | String | 低延时播放地址 | 187 | 188 | ### create_room 创建直播房间 189 | 190 | #### 参数说明 191 | 192 | | 参数名称 | 类型 | 是否必填 | 说明 | 193 | | ------ | ---------- | ---- | ------------------ | 194 | | userID | String | 是 | userID | 195 | | token | String | 是 | 登录成功后返回的token | 196 | | roomInfo | String | 是 | 房间名称 | 197 | | roomID | String | 否 | 房间ID | 198 | 199 | #### 返回结果说明 200 | 201 | | 属性名称 | 类型 | 说明 | 202 | | ------- | ---------- | ------ | 203 | | code | int | 返回码,0表示成功 | 204 | | message | String | 描述信息 | 205 | | roomID | String | 房间ID | 206 | 207 | ### destroy_room 销毁直播房间 208 | 209 | #### 参数说明 210 | 211 | | 参数名称 | 类型 | 是否必填 | 说明 | 212 | | ------ | ---------- | ---- | ------------------ | 213 | | userID | String | 是 | userID | 214 | | token | String | 是 | 登录成功后返回的token | 215 | | roomID | String | 是 | 房间ID | 216 | 217 | #### 返回结果说明 218 | 219 | | 属性名称 | 类型 | 说明 | 220 | | ------- | ---------- | ------ | 221 | | code | int | 返回码,0表示成功 | 222 | | message | String | 描述信息 | 223 | 224 | ### add_pusher 增加一个推流者 225 | 226 | #### 参数说明 227 | 228 | | 参数名称 | 类型 | 是否必填 | 说明 | 229 | | ------ | ---------- | ---- | ------------------ | 230 | | userID | String | 是 | userID | 231 | | token | String | 是 | 登录成功后返回的token | 232 | | roomID | String | 是 | 房间ID | 233 | | roomInfo | String | 否 | 房间名称 | 234 | | userName | String | 是 | 用户名称 | 235 | | userAvatar | String | 是 | 用户头像 | 236 | | pushURL | String | 是 | 推流地址 | 237 | 238 | #### 返回结果说明 239 | 240 | | 属性名称 | 类型 | 说明 | 241 | | ------- | ---------- | ------ | 242 | | code | int | 返回码,0表示成功 | 243 | | message | String | 描述信息 | 244 | 245 | ### delete_pusher 删除一个推流者 246 | 247 | #### 参数说明 248 | 249 | | 参数名称 | 类型 | 是否必填 | 说明 | 250 | | ------ | ---------- | ---- | ------------------ | 251 | | userID | String | 是 | userID | 252 | | token | String | 是 | 登录成功后返回的token | 253 | | roomID | String | 是 | 房间ID | 254 | 255 | #### 返回结果说明 256 | 257 | | 属性名称 | 类型 | 说明 | 258 | | ------- | ---------- | ------ | 259 | | code | int | 返回码,0表示成功 | 260 | | message | String | 描述信息 | 261 | 262 | ### pusher_heartbeat 推流者心跳 263 | 264 | #### 参数说明 265 | 266 | | 参数名称 | 类型 | 是否必填 | 说明 | 267 | | ------ | ---------- | ---- | ------------------ | 268 | | userID | String | 是 | userID | 269 | | token | String | 是 | 登录成功后返回的token | 270 | | roomID | String | 是 | 房间ID | 271 | 272 | #### 返回结果说明 273 | 274 | | 属性名称 | 类型 | 说明 | 275 | | ------- | ---------- | ------ | 276 | | code | int | 返回码,0表示成功 | 277 | | message | String | 描述信息 | 278 | 279 | ### merge_stream 连麦混流接口 280 | 281 | #### 参数说明 282 | 283 | | 参数名称 | 类型 | 是否必填 | 说明 | 284 | | ------ | ---------- | ---- | ------------------ | 285 | | userID | String | 是 | userID | 286 | | token | String | 是 | 登录成功后返回的token | 287 | | roomID | String | 是 | 房间ID | 288 | | mergeParams | String | 是 | 混流参数json格式字符串 | 289 | 290 | #### 返回结果说明 291 | 292 | | 属性名称 | 类型 | 说明 | 293 | | ------- | ---------- | ------ | 294 | | code | int | 返回码,0表示成功 | 295 | | message | String | 描述信息 | 296 | | result | Result | 混流结果 | 297 | 298 | #### Result 属性说明 299 | 300 | | 属性名称 | 类型 | 说明 | 301 | | ------- | ---------- | ------ | 302 | | code | int | 后台混流返回码 | 303 | | message | String | 后台混流返回信息描述 | 304 | | timestamp | int | 时间戳 | 305 | 306 | ### get_custom_info 获取房间自定义信息,可以用来实现统计房间观众数等功能。 307 | 308 | #### 参数说明 309 | 310 | | 参数名称 | 类型 | 是否必填 | 说明 | 311 | | ------ | ---------- | ---- | ------------------ | 312 | | userID | String | 是 | userID | 313 | | token | String | 是 | 登录成功后返回的token | 314 | | roomID | String | 是 | 房间ID | 315 | 316 | #### 返回结果说明 317 | 318 | | 属性名称 | 类型 | 说明 | 319 | | ------- | ---------- | ------ | 320 | | code | int | 返回码,0表示成功 | 321 | | message | String | 描述信息 | 322 | | customInfo | String | 自定义信息json字符串 | 323 | 324 | ### set_custom_field 设置房间自定义字段,可以用来实现统计房间观众数等功能。 325 | 326 | #### 参数说明 327 | 328 | | 参数名称 | 类型 | 是否必填 | 说明 | 329 | | ------ | ---------- | ---- | ------------------ | 330 | | userID | String | 是 | userID | 331 | | token | String | 是 | 登录成功后返回的token | 332 | | roomID | String | 是 | 房间ID | 333 | | fieldName | String | 是 | 属性名称 | 334 | | operation | String | 是 | 操作类型 | 335 | 336 | #### 返回结果说明 337 | 338 | | 属性名称 | 类型 | 说明 | 339 | | ------- | ---------- | ------ | 340 | | code | int | 返回码,0表示成功 | 341 | | message | String | 描述信息 | 342 | | customInfo | String | 自定义信息json字符串 | 343 | 344 | ### add_audience 增加一个观众 345 | 346 | #### 参数说明 347 | 348 | | 参数名称 | 类型 | 是否必填 | 说明 | 349 | | ------ | ---------- | ---- | ------------------ | 350 | | userID | String | 是 | userID | 351 | | token | String | 是 | 登录成功后返回的token | 352 | | roomID | String | 是 | 房间ID | 353 | | userInfo | String | 是 | 观众信息 | 354 | 355 | #### 返回结果说明 356 | 357 | | 属性名称 | 类型 | 说明 | 358 | | ------- | ---------- | ------ | 359 | | code | int | 返回码,0表示成功 | 360 | | message | String | 描述信息 | 361 | 362 | ### delete_audience 删除一个观众 363 | 364 | #### 参数说明 365 | 366 | | 参数名称 | 类型 | 是否必填 | 说明 | 367 | | ------ | ---------- | ---- | ------------------ | 368 | | userID | String | 是 | userID | 369 | | token | String | 是 | 登录成功后返回的token | 370 | | roomID | String | 是 | 房间ID | 371 | 372 | #### 返回结果说明 373 | 374 | | 属性名称 | 类型 | 说明 | 375 | | ------- | ---------- | ------ | 376 | | code | int | 返回码,0表示成功 | 377 | | message | String | 描述信息 | 378 | 379 | ### get_audiences 获取观众列表 380 | 381 | #### 参数说明 382 | 383 | | 参数名称 | 类型 | 是否必填 | 说明 | 384 | | ------ | ---------- | ---- | ------------------ | 385 | | userID | String | 是 | userID | 386 | | token | String | 是 | 登录成功后返回的token | 387 | | roomID | String | 是 | 房间ID | 388 | 389 | #### 返回结果说明 390 | 391 | | 属性名称 | 类型 | 说明 | 392 | | ------- | ---------- | ------ | 393 | | code | int | 返回码,0表示成功 | 394 | | message | String | 描述信息 | 395 | | audiences | List | 观众列表 | 396 | 397 | #### Audience 属性说明 398 | 399 | | 属性名称 | 类型 | 说明 | 400 | | ------- | ---------- | ------ | 401 | | userID | String | userID | 402 | | userInfo | String | 用户信息 | 403 | 404 | ## 双人房间`double_room`接口说明 405 | 406 | ### login 登录接口,校验im签名合法性并派发token,后续请求需要校验token。 407 | 408 | #### 参数说明 409 | 410 | 注意:参数拼接在URL后面 411 | 412 | | 参数名称 | 类型 | 是否必填 | 说明 | 413 | | ------ | ---------- | ---- | ------------------ | 414 | | sdkAppID | String | 是 | 云通信的sdkAppID | 415 | | accountType | String | 是 | 云通信的accountType | 416 | | userID | String | 是 | userID | 417 | | userSig | String | 是 | 云通信派发的userSig | 418 | 419 | #### 返回结果说明 420 | 421 | | 属性名称 | 类型 | 说明 | 422 | | ------- | ---------- | ------ | 423 | | code | int | 返回码,0表示成功 | 424 | | message | String | 描述信息 | 425 | | userID | String | 用户id | 426 | | token | String | 登录成功后产生的token | 427 | 428 | ### logout 退出登录接口,清理后台保存的token信息 429 | 430 | #### 参数说明 431 | 432 | 注意:userID和token参数拼接在URL后面。后续所有接口都需要带上userID和token,且是拼接在URL后面的,后面不再赘述。 433 | 434 | | 参数名称 | 类型 | 是否必填 | 说明 | 435 | | ------ | ---------- | ---- | ------------------ | 436 | | userID | String | 是 | userID | 437 | | token | String | 是 | 登录成功后返回的token | 438 | 439 | #### 返回结果说明 440 | 441 | | 属性名称 | 类型 | 说明 | 442 | | ------- | ---------- | ------ | 443 | | code | int | 返回码,0表示成功 | 444 | | message | String | 描述信息 | 445 | 446 | ### get_push_url 获取推流地址接口 447 | 448 | #### 参数说明 449 | 450 | | 参数名称 | 类型 | 是否必填 | 说明 | 451 | | ------ | ---------- | ---- | ------------------ | 452 | | userID | String | 是 | userID | 453 | | token | String | 是 | 登录成功后返回的token | 454 | 455 | #### 返回结果说明 456 | 457 | | 属性名称 | 类型 | 说明 | 458 | | ------- | ---------- | ------ | 459 | | code | int | 返回码,0表示成功 | 460 | | message | String | 描述信息 | 461 | | pushURL | String | 推流地址 | 462 | 463 | ### get_room_list 获取双人房间列表 464 | 465 | #### 参数说明 466 | 467 | | 参数名称 | 类型 | 是否必填 | 说明 | 468 | | ------ | ---------- | ---- | ------------------ | 469 | | userID | String | 是 | userID | 470 | | token | String | 是 | 登录成功后返回的token | 471 | | cnt | int | 是 | 房间数量 | 472 | | index | int | 是 | 起始下标 | 473 | 474 | #### 返回结果说明 475 | 476 | | 属性名称 | 类型 | 说明 | 477 | | ------- | ---------- | ------ | 478 | | code | int | 返回码,0表示成功 | 479 | | message | String | 描述信息 | 480 | | rooms | List | 双人房间列表 | 481 | 482 | #### Room 属性说明 483 | 484 | | 属性名称 | 类型 | 说明 | 485 | | ------- | ---------- | ------ | 486 | | roomID | String | 房间ID | 487 | | roomInfo | String | 房间名称 | 488 | | roomCreator | String | 房间创建者userID | 489 | | mixedPlayURL | String | 混流播放地址 | 490 | | pushers | List | 推流者列表 | 491 | 492 | #### Pusher 属性说明 493 | 494 | | 属性名称 | 类型 | 说明 | 495 | | ------- | ---------- | ------ | 496 | | userID | String | 用户id | 497 | | userName | String | 用户昵称 | 498 | | userAvatar | String | 用户头像 | 499 | | accelerateURL | String | 低延时播放地址 | 500 | 501 | ### get_pushers 获取推流者列表 502 | 503 | #### 参数说明 504 | 505 | | 参数名称 | 类型 | 是否必填 | 说明 | 506 | | ------ | ---------- | ---- | ------------------ | 507 | | userID | String | 是 | userID | 508 | | token | String | 是 | 登录成功后返回的token | 509 | | roomID | String | 是 | 房间ID | 510 | 511 | #### 返回结果说明 512 | 513 | | 属性名称 | 类型 | 说明 | 514 | | ------- | ---------- | ------ | 515 | | code | int | 返回码,0表示成功 | 516 | | message | String | 描述信息 | 517 | | roomID | String | 房间ID | 518 | | roomInfo | String | 房间名称 | 519 | | roomCreator | String | 房间创建者userID | 520 | | mixedPlayURL | String | 混流播放地址 | 521 | | pushers | List | 推流者列表 | 522 | 523 | #### Pusher 属性说明 524 | 525 | | 属性名称 | 类型 | 说明 | 526 | | ------- | ---------- | ------ | 527 | | userID | String | 用户id | 528 | | userName | String | 用户昵称 | 529 | | userAvatar | String | 用户头像 | 530 | | accelerateURL | String | 低延时播放地址 | 531 | 532 | ### create_room 创建双人房间 533 | 534 | #### 参数说明 535 | 536 | | 参数名称 | 类型 | 是否必填 | 说明 | 537 | | ------ | ---------- | ---- | ------------------ | 538 | | userID | String | 是 | userID | 539 | | token | String | 是 | 登录成功后返回的token | 540 | | roomInfo | String | 是 | 房间名称 | 541 | | roomID | String | 否 | 房间ID | 542 | 543 | #### 返回结果说明 544 | 545 | | 属性名称 | 类型 | 说明 | 546 | | ------- | ---------- | ------ | 547 | | code | int | 返回码,0表示成功 | 548 | | message | String | 描述信息 | 549 | | roomID | String | 房间ID | 550 | 551 | ### destroy_room 销毁双人房间 552 | 553 | #### 参数说明 554 | 555 | | 参数名称 | 类型 | 是否必填 | 说明 | 556 | | ------ | ---------- | ---- | ------------------ | 557 | | userID | String | 是 | userID | 558 | | token | String | 是 | 登录成功后返回的token | 559 | | roomID | String | 是 | 房间ID | 560 | 561 | #### 返回结果说明 562 | 563 | | 属性名称 | 类型 | 说明 | 564 | | ------- | ---------- | ------ | 565 | | code | int | 返回码,0表示成功 | 566 | | message | String | 描述信息 | 567 | 568 | ### add_pusher 增加一个推流者 569 | 570 | #### 参数说明 571 | 572 | | 参数名称 | 类型 | 是否必填 | 说明 | 573 | | ------ | ---------- | ---- | ------------------ | 574 | | userID | String | 是 | userID | 575 | | token | String | 是 | 登录成功后返回的token | 576 | | roomID | String | 是 | 房间ID | 577 | | roomInfo | String | 否 | 房间名称 | 578 | | userName | String | 是 | 用户名称 | 579 | | userAvatar | String | 是 | 用户头像 | 580 | | pushURL | String | 是 | 推流地址 | 581 | 582 | #### 返回结果说明 583 | 584 | | 属性名称 | 类型 | 说明 | 585 | | ------- | ---------- | ------ | 586 | | code | int | 返回码,0表示成功 | 587 | | message | String | 描述信息 | 588 | 589 | ### delete_pusher 删除一个推流者 590 | 591 | #### 参数说明 592 | 593 | | 参数名称 | 类型 | 是否必填 | 说明 | 594 | | ------ | ---------- | ---- | ------------------ | 595 | | userID | String | 是 | userID | 596 | | token | String | 是 | 登录成功后返回的token | 597 | | roomID | String | 是 | 房间ID | 598 | 599 | #### 返回结果说明 600 | 601 | | 属性名称 | 类型 | 说明 | 602 | | ------- | ---------- | ------ | 603 | | code | int | 返回码,0表示成功 | 604 | | message | String | 描述信息 | 605 | 606 | ### pusher_heartbeat 推流者心跳 607 | 608 | #### 参数说明 609 | 610 | | 参数名称 | 类型 | 是否必填 | 说明 | 611 | | ------ | ---------- | ---- | ------------------ | 612 | | userID | String | 是 | userID | 613 | | token | String | 是 | 登录成功后返回的token | 614 | | roomID | String | 是 | 房间ID | 615 | 616 | #### 返回结果说明 617 | 618 | | 属性名称 | 类型 | 说明 | 619 | | ------- | ---------- | ------ | 620 | | code | int | 返回码,0表示成功 | 621 | | message | String | 描述信息 | 622 | 623 | ## 多人房间`multi_room`接口说明 624 | 625 | ### login 登录接口,校验im签名合法性并派发token,后续请求需要校验token。 626 | 627 | #### 参数说明 628 | 629 | 注意:参数拼接在URL后面 630 | 631 | | 参数名称 | 类型 | 是否必填 | 说明 | 632 | | ------ | ---------- | ---- | ------------------ | 633 | | sdkAppID | String | 是 | 云通信的sdkAppID | 634 | | accountType | String | 是 | 云通信的accountType | 635 | | userID | String | 是 | userID | 636 | | userSig | String | 是 | 云通信派发的userSig | 637 | 638 | #### 返回结果说明 639 | 640 | | 属性名称 | 类型 | 说明 | 641 | | ------- | ---------- | ------ | 642 | | code | int | 返回码,0表示成功 | 643 | | message | String | 描述信息 | 644 | | userID | String | 用户id | 645 | | token | String | 登录成功后产生的token | 646 | 647 | ### logout 退出登录接口,清理后台保存的token信息 648 | 649 | #### 参数说明 650 | 651 | 注意:userID和token参数拼接在URL后面。后续所有接口都需要带上userID和token,且是拼接在URL后面的,后面不再赘述。 652 | 653 | | 参数名称 | 类型 | 是否必填 | 说明 | 654 | | ------ | ---------- | ---- | ------------------ | 655 | | userID | String | 是 | userID | 656 | | token | String | 是 | 登录成功后返回的token | 657 | 658 | #### 返回结果说明 659 | 660 | | 属性名称 | 类型 | 说明 | 661 | | ------- | ---------- | ------ | 662 | | code | int | 返回码,0表示成功 | 663 | | message | String | 描述信息 | 664 | 665 | ### get_push_url 获取推流地址接口 666 | 667 | #### 参数说明 668 | 669 | | 参数名称 | 类型 | 是否必填 | 说明 | 670 | | ------ | ---------- | ---- | ------------------ | 671 | | userID | String | 是 | userID | 672 | | token | String | 是 | 登录成功后返回的token | 673 | 674 | #### 返回结果说明 675 | 676 | | 属性名称 | 类型 | 说明 | 677 | | ------- | ---------- | ------ | 678 | | code | int | 返回码,0表示成功 | 679 | | message | String | 描述信息 | 680 | | pushURL | String | 推流地址 | 681 | 682 | ### get_room_list 获取多人房间列表 683 | 684 | #### 参数说明 685 | 686 | | 参数名称 | 类型 | 是否必填 | 说明 | 687 | | ------ | ---------- | ---- | ------------------ | 688 | | userID | String | 是 | userID | 689 | | token | String | 是 | 登录成功后返回的token | 690 | | cnt | int | 是 | 房间数量 | 691 | | index | int | 是 | 起始下标 | 692 | 693 | #### 返回结果说明 694 | 695 | | 属性名称 | 类型 | 说明 | 696 | | ------- | ---------- | ------ | 697 | | code | int | 返回码,0表示成功 | 698 | | message | String | 描述信息 | 699 | | rooms | List | 多人房间列表 | 700 | 701 | #### Room 属性说明 702 | 703 | | 属性名称 | 类型 | 说明 | 704 | | ------- | ---------- | ------ | 705 | | roomID | String | 房间ID | 706 | | roomInfo | String | 房间名称 | 707 | | roomCreator | String | 房间创建者userID | 708 | | mixedPlayURL | String | 混流播放地址 | 709 | | pushers | List | 推流者列表 | 710 | 711 | #### Pusher 属性说明 712 | 713 | | 属性名称 | 类型 | 说明 | 714 | | ------- | ---------- | ------ | 715 | | userID | String | 用户id | 716 | | userName | String | 用户昵称 | 717 | | userAvatar | String | 用户头像 | 718 | | accelerateURL | String | 低延时播放地址 | 719 | 720 | ### get_pushers 获取推流者列表 721 | 722 | #### 参数说明 723 | 724 | | 参数名称 | 类型 | 是否必填 | 说明 | 725 | | ------ | ---------- | ---- | ------------------ | 726 | | userID | String | 是 | userID | 727 | | token | String | 是 | 登录成功后返回的token | 728 | | roomID | String | 是 | 房间ID | 729 | 730 | #### 返回结果说明 731 | 732 | | 属性名称 | 类型 | 说明 | 733 | | ------- | ---------- | ------ | 734 | | code | int | 返回码,0表示成功 | 735 | | message | String | 描述信息 | 736 | | roomID | String | 房间ID | 737 | | roomInfo | String | 房间名称 | 738 | | roomCreator | String | 房间创建者userID | 739 | | mixedPlayURL | String | 混流播放地址 | 740 | | pushers | List | 推流者列表 | 741 | 742 | #### Pusher 属性说明 743 | 744 | | 属性名称 | 类型 | 说明 | 745 | | ------- | ---------- | ------ | 746 | | userID | String | 用户id | 747 | | userName | String | 用户昵称 | 748 | | userAvatar | String | 用户头像 | 749 | | accelerateURL | String | 低延时播放地址 | 750 | 751 | ### create_room 创建多人房间 752 | 753 | #### 参数说明 754 | 755 | | 参数名称 | 类型 | 是否必填 | 说明 | 756 | | ------ | ---------- | ---- | ------------------ | 757 | | userID | String | 是 | userID | 758 | | token | String | 是 | 登录成功后返回的token | 759 | | roomInfo | String | 是 | 房间名称 | 760 | | roomID | String | 否 | 房间ID | 761 | 762 | #### 返回结果说明 763 | 764 | | 属性名称 | 类型 | 说明 | 765 | | ------- | ---------- | ------ | 766 | | code | int | 返回码,0表示成功 | 767 | | message | String | 描述信息 | 768 | | roomID | String | 房间ID | 769 | 770 | ### destroy_room 销毁多人房间 771 | 772 | #### 参数说明 773 | 774 | | 参数名称 | 类型 | 是否必填 | 说明 | 775 | | ------ | ---------- | ---- | ------------------ | 776 | | userID | String | 是 | userID | 777 | | token | String | 是 | 登录成功后返回的token | 778 | | roomID | String | 是 | 房间ID | 779 | 780 | #### 返回结果说明 781 | 782 | | 属性名称 | 类型 | 说明 | 783 | | ------- | ---------- | ------ | 784 | | code | int | 返回码,0表示成功 | 785 | | message | String | 描述信息 | 786 | 787 | ### add_pusher 增加一个推流者 788 | 789 | #### 参数说明 790 | 791 | | 参数名称 | 类型 | 是否必填 | 说明 | 792 | | ------ | ---------- | ---- | ------------------ | 793 | | userID | String | 是 | userID | 794 | | token | String | 是 | 登录成功后返回的token | 795 | | roomID | String | 是 | 房间ID | 796 | | roomInfo | String | 否 | 房间名称 | 797 | | userName | String | 是 | 用户名称 | 798 | | userAvatar | String | 是 | 用户头像 | 799 | | pushURL | String | 是 | 推流地址 | 800 | 801 | #### 返回结果说明 802 | 803 | | 属性名称 | 类型 | 说明 | 804 | | ------- | ---------- | ------ | 805 | | code | int | 返回码,0表示成功 | 806 | | message | String | 描述信息 | 807 | 808 | ### delete_pusher 删除一个推流者 809 | 810 | #### 参数说明 811 | 812 | | 参数名称 | 类型 | 是否必填 | 说明 | 813 | | ------ | ---------- | ---- | ------------------ | 814 | | userID | String | 是 | userID | 815 | | token | String | 是 | 登录成功后返回的token | 816 | | roomID | String | 是 | 房间ID | 817 | 818 | #### 返回结果说明 819 | 820 | | 属性名称 | 类型 | 说明 | 821 | | ------- | ---------- | ------ | 822 | | code | int | 返回码,0表示成功 | 823 | | message | String | 描述信息 | 824 | 825 | ### pusher_heartbeat 推流者心跳 826 | 827 | #### 参数说明 828 | 829 | | 参数名称 | 类型 | 是否必填 | 说明 | 830 | | ------ | ---------- | ---- | ------------------ | 831 | | userID | String | 是 | userID | 832 | | token | String | 是 | 登录成功后返回的token | 833 | | roomID | String | 是 | 房间ID | 834 | 835 | #### 返回结果说明 836 | 837 | | 属性名称 | 类型 | 说明 | 838 | | ------- | ---------- | ------ | 839 | | code | int | 返回码,0表示成功 | 840 | | message | String | 描述信息 | -------------------------------------------------------------------------------- /image/build_war.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/build_war.png -------------------------------------------------------------------------------- /image/change_jdk_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/change_jdk_1.png -------------------------------------------------------------------------------- /image/change_jdk_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/change_jdk_2.png -------------------------------------------------------------------------------- /image/check_jdk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/check_jdk.png -------------------------------------------------------------------------------- /image/config_cvm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/config_cvm.png -------------------------------------------------------------------------------- /image/cvm_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/cvm_image.png -------------------------------------------------------------------------------- /image/cvm_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/cvm_info.png -------------------------------------------------------------------------------- /image/im_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/im_config.png -------------------------------------------------------------------------------- /image/im_config_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/im_config_info.png -------------------------------------------------------------------------------- /image/im_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/im_list.png -------------------------------------------------------------------------------- /image/im_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/im_new.png -------------------------------------------------------------------------------- /image/im_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/im_open.png -------------------------------------------------------------------------------- /image/live_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/live_code.png -------------------------------------------------------------------------------- /image/live_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/live_config.png -------------------------------------------------------------------------------- /image/live_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/live_open.png -------------------------------------------------------------------------------- /image/new_cvm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/new_cvm.png -------------------------------------------------------------------------------- /image/restart_tomcat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/restart_tomcat.png -------------------------------------------------------------------------------- /image/upload_war.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/upload_war.png -------------------------------------------------------------------------------- /image/upload_war_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/upload_war_2.png -------------------------------------------------------------------------------- /image/war_decompression.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TencentVideoCloudMLVBDev/roomservice_server_java/b8ac1439617055a0b5056e13b54ad6813ef54c3d/image/war_decompression.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.tencent.qcloud.roomservice 5 | roomservice 6 | war 7 | 1.0-SNAPSHOT 8 | roomservice Maven Webapp 9 | http://maven.apache.org 10 | 11 | 12 | UTF-8 13 | UTF-8 14 | UTF-8 15 | 16 | 17 | 4.0.0.RELEASE 18 | 3.4.1 19 | 1.3.0 20 | 21 | 22 | 23 | junit 24 | junit 25 | 3.8.1 26 | test 27 | 28 | 29 | 30 | 31 | org.apache.tomcat 32 | tomcat-servlet-api 33 | 7.0.64 34 | 35 | 36 | 37 | 38 | org.springframework 39 | spring-webmvc 40 | 41 | 42 | ${org.spring.version} 43 | 44 | 45 | 46 | org.springframework 47 | spring-oxm 48 | ${org.spring.version} 49 | 50 | 51 | org.springframework 52 | spring-jdbc 53 | ${org.spring.version} 54 | 55 | 56 | org.springframework 57 | spring-tx 58 | ${org.spring.version} 59 | 60 | 61 | org.springframework 62 | spring-test 63 | ${org.spring.version} 64 | 65 | 66 | 67 | org.aspectj 68 | aspectjweaver 69 | 1.7.3 70 | 71 | 72 | org.aspectj 73 | aspectjrt 74 | 1.6.11 75 | 76 | 77 | 78 | 79 | org.codehaus.jackson 80 | jackson-mapper-asl 81 | 1.9.12 82 | 83 | 84 | 85 | org.json 86 | json 87 | 20160810 88 | 89 | 90 | 91 | 92 | ch.qos.logback 93 | logback-classic 94 | 1.1.2 95 | compile 96 | 97 | 98 | ch.qos.logback 99 | logback-core 100 | 1.1.2 101 | compile 102 | 103 | 104 | 105 | 106 | com.google.guava 107 | guava 108 | 20.0 109 | 110 | 111 | 112 | 113 | org.apache.commons 114 | commons-lang3 115 | 3.5 116 | 117 | 118 | 119 | 120 | commons-collections 121 | commons-collections 122 | 3.2.1 123 | 124 | 125 | joda-time 126 | joda-time 127 | 2.3 128 | 129 | 130 | 131 | 132 | org.hashids 133 | hashids 134 | 1.0.1 135 | 136 | 137 | 138 | org.bouncycastle 139 | bcpkix-jdk15on 140 | 1.52 141 | 142 | 143 | 144 | org.bouncycastle 145 | bcprov-jdk15on 146 | 1.52 147 | 148 | 149 | 150 | commons-codec 151 | commons-codec 152 | 1.10 153 | 154 | 155 | 156 | com.google.code.gson 157 | gson 158 | 2.3.1 159 | 160 | 161 | 162 | 163 | roomservice 164 | 165 | 166 | 167 | org.mybatis.generator 168 | mybatis-generator-maven-plugin 169 | 1.3.2 170 | 171 | true 172 | true 173 | 174 | 175 | 176 | 178 | 179 | org.apache.maven.plugins 180 | maven-compiler-plugin 181 | 182 | 183 | 1.8 184 | 1.8 185 | UTF-8 186 | 187 | 188 | 190 | 191 | ${project.basedir}/src/main/webapp/WEB-INF/lib 192 | 193 | 194 | 195 | 196 | 197 | org.mybatis.generator 198 | mybatis-generator-maven-plugin 199 | 1.3.2 200 | 201 | true 202 | true 203 | 204 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /roomservice.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/common/CORSFilter.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.common; 2 | 3 | import javax.servlet.*; 4 | import javax.servlet.http.HttpServletResponse; 5 | import java.io.IOException; 6 | 7 | public class CORSFilter implements Filter { 8 | @Override 9 | public void init(FilterConfig filterConfig) throws ServletException { 10 | 11 | } 12 | 13 | @Override 14 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 15 | HttpServletResponse response = (HttpServletResponse) servletResponse; 16 | response.setHeader("Access-Control-Allow-Origin", "*"); 17 | response.setHeader("Access-Control-Allow-Methods", "POST, GET"); 18 | response.setHeader("Access-Control-Max-Age", "3600"); 19 | response.setHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); 20 | filterChain.doFilter(servletRequest, servletResponse); 21 | } 22 | 23 | @Override 24 | public void destroy() { 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/common/Config.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.common; 2 | 3 | public class Config { 4 | 5 | /** 6 | * 需要开通云直播服务 7 | * 参考指引 @https://cloud.tencent.com/document/product/454/7953#1.-.E8.A7.86.E9.A2.91.E7.9B.B4.E6.92.AD.EF.BC.88lvb.EF.BC.89 8 | * 有介绍bizid 和 pushSecretKey的获取方法。 9 | */ 10 | public class Live { 11 | /** 12 | * 云直播 APP_ID = 和 APIKEY 主要用于腾讯云直播common cgi请求。appid 用于表示您是哪个客户,APIKey参与了请求签名sign的生成。 13 | * 后台用他们来校验common cgi调用的合法性 14 | */ 15 | public final static int APP_ID = 0; 16 | 17 | /** 18 | * 云直播 APP_BIZID = 和pushSecretKey 主要用于推流地址的生成,填写错误,会导致推流地址不合法,推流请求被腾讯云直播服务器拒绝 19 | */ 20 | public final static int APP_BIZID = 0; 21 | 22 | /** 23 | * 云直播 推流防盗链key = 和 APP_BIZID 主要用于推流地址的生成,填写错误,会导致推流地址不合法,推流请求被腾讯云直播服务器拒绝 24 | */ 25 | public final static String PUSH_SECRET_KEY = ""; 26 | 27 | /** 28 | * 云直播 API鉴权key = 和appID 主要用于common cgi请求。appid 用于表示您是哪个客户,APIKey参与了请求签名sign的生成。 29 | * 后台用他们来校验common cgi调用的合法性。 30 | */ 31 | public final static String APIKEY = ""; 32 | 33 | // 云直播 推流有效期单位秒 默认7天 34 | public final static int validTime = 3600 * 24 * 7; 35 | // 云直播 播放域名。https://console.cloud.tencent.com/live/domainmanage 36 | public final static String PLAYHOST = ""; 37 | // 云直播 推流域名。https://console.cloud.tencent.com/live/domainmanage 38 | public final static String PUSHHOST = ""; 39 | } 40 | 41 | /** 42 | * 需要开通云通信服务 43 | * 参考指引 @https://cloud.tencent.com/document/product/454/7953#3.-.E4.BA.91.E9.80.9A.E8.AE.AF.E6.9C.8D.E5.8A.A1.EF.BC.88im.EF.BC.89 44 | * 有介绍appid 和 accType的获取方法。以及私钥文件的下载方法。 45 | */ 46 | public class IM { 47 | /** 48 | * 云通信 IM_SDKAPPID = IM_ACCOUNTTYPE 和 PRIVATEKEY 是云通信独立模式下,为您的独立账号 identifer, 49 | * 派发访问云通信服务的userSig票据的重要信息,填写错误会导致IM登录失败,IM功能不可用 50 | */ 51 | public final static long IM_SDKAPPID = 0; 52 | 53 | /** 54 | * 云通信 账号集成类型 IM_ACCOUNTTYPE = IM_SDKAPPID 和 PRIVATEKEY 是云通信独立模式下,为您的独立账户identifer, 55 | * 派发访问云通信服务的userSig票据的重要信息,填写错误会导致IM登录失败,IM功能不可用 56 | */ 57 | public final static String IM_ACCOUNTTYPE = ""; 58 | 59 | // 云通信 管理员账号 60 | public final static String ADMINISTRATOR = "admin"; 61 | 62 | /** 63 | * 云通信 派发usersig 采用非对称加密算法RSA,用私钥生成签名。PRIVATEKEY就是用于生成签名的私钥,私钥文件可以在互动直播控制台获取 64 | * 配置privateKey 65 | * 将private_key文件的内容按下面的方式填写到 PRIVATEKEY。 66 | */ 67 | public final static String PRIVATEKEY = "-----BEGIN PRIVATE KEY-----\n" + 68 | "\n" + 69 | "\n" + 70 | "\n" + 71 | "-----END PRIVATE KEY-----"; 72 | /** 73 | * 云通信 验证usersig 所用的公钥 74 | */ 75 | public final static String PUBLICKEY = "-----BEGIN PUBLIC KEY-----\n" + 76 | "\n" + 77 | "\n" + 78 | "-----END PUBLIC KEY-----\n"; 79 | } 80 | 81 | 82 | /** 83 | * 多人音视频房间相关参数 84 | */ 85 | public class MultiRoom { 86 | // 房间容量上限 87 | public final static int maxMembers = 4; 88 | 89 | // 心跳超时 单位秒 90 | public final static int heartBeatTimeout = 20; 91 | 92 | // 空闲房间超时 房间创建后一直没有人进入,超过给定时间将会被后台回收,单位秒 93 | public final static int maxIdleDuration = 30; 94 | } 95 | 96 | /** 97 | * 双人音视频房间相关参数 98 | */ 99 | public class DoubleRoom { 100 | // 心跳超时 单位秒 101 | public final static int heartBeatTimeout = 20; 102 | 103 | // 空闲房间超时 房间创建后一直没有人进入,超过给定时间将会被后台回收,单位秒 104 | public final static int maxIdleDuration = 30; 105 | } 106 | 107 | /** 108 | * 直播连麦房间相关参数 109 | */ 110 | public class LiveRoom { 111 | // 房间容量上限 112 | public final static int maxMembers = 4; 113 | 114 | // 心跳超时 单位秒 115 | public final static int heartBeatTimeout = 20; 116 | 117 | // 空闲房间超时 房间创建后一直没有人进入,超过给定时间将会被后台回收,单位秒 118 | public final static int maxIdleDuration = 30; 119 | 120 | // 最大观众列表长度 121 | public final static int maxAudiencesLen = 30; 122 | } 123 | 124 | /** 125 | * 创建者退出的时候是否需要删除房间 126 | * 默认false。表示房间所有成员是对等的,第一个进房的人退出并不会销毁房间,只有房间没人的时候才会销毁房间。 127 | * 此配置项只针对双人和多人实时音视频 128 | */ 129 | public final static boolean isCreatorDestroyRoom = false; 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/controller/DoubleRoom.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.controller; 2 | 3 | import com.tencent.qcloud.roomservice.logic.RoomMgr; 4 | import com.tencent.qcloud.roomservice.pojo.Request.*; 5 | import com.tencent.qcloud.roomservice.pojo.Response.*; 6 | import com.tencent.qcloud.roomservice.pojo.Room; 7 | import com.tencent.qcloud.roomservice.service.RoomService; 8 | import com.tencent.qcloud.roomservice.service.UtilService; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.ResponseBody; 14 | 15 | /** 16 | * 双人房间接口 17 | */ 18 | @Controller 19 | @ResponseBody 20 | @RequestMapping("/weapp/double_room") 21 | public class DoubleRoom { 22 | @Autowired 23 | RoomService roomService; 24 | 25 | @Autowired 26 | UtilService utilService; 27 | 28 | @ResponseBody 29 | @RequestMapping("login") 30 | public LoginRsp login(String sdkAppID, String accountType, String userID, String userSig){ 31 | return roomService.login(sdkAppID, accountType, userID, userSig); 32 | } 33 | 34 | @ResponseBody 35 | @RequestMapping("logout") 36 | public BaseRsp logout(String userID, String token){ 37 | return roomService.logout(userID, token); 38 | } 39 | 40 | @ResponseBody 41 | @RequestMapping("get_push_url") 42 | public GetPushUrlRsp get_push_url(String userID, String token, @RequestBody GetPushUrlReq req){ 43 | return utilService.getPushUrl(userID, token, req); 44 | } 45 | 46 | @ResponseBody 47 | @RequestMapping("get_room_list") 48 | public GetRoomListRsp get_room_list(String userID, String token, @RequestBody GetRoomListReq req){ 49 | return roomService.getRoomList(userID, token, req, RoomMgr.DOUBLE_ROOM); 50 | } 51 | 52 | @ResponseBody 53 | @RequestMapping("get_pushers") 54 | public Room get_pushers(String userID, String token, @RequestBody GetPushersReq req){ 55 | return roomService.getPushers(userID, token, req, RoomMgr.DOUBLE_ROOM); 56 | } 57 | 58 | @ResponseBody 59 | @RequestMapping("create_room") 60 | public CreateRoomRsp create_room(String userID, String token, @RequestBody CreateRoomReq req){ 61 | return roomService.createRoom(userID, token, req, RoomMgr.DOUBLE_ROOM); 62 | } 63 | 64 | @ResponseBody 65 | @RequestMapping("destroy_room") 66 | public BaseRsp destroy_room(String userID, String token, @RequestBody DestroyRoomReq req) { 67 | return roomService.destroyRoom(userID, token, req, RoomMgr.DOUBLE_ROOM); 68 | } 69 | 70 | @ResponseBody 71 | @RequestMapping("add_pusher") 72 | public BaseRsp add_pusher(String userID, String token, @RequestBody AddPusherReq req) { 73 | return roomService.addPusher(userID, token, req, RoomMgr.DOUBLE_ROOM); 74 | } 75 | 76 | @ResponseBody 77 | @RequestMapping("delete_pusher") 78 | public BaseRsp delete_pusher(String userID, String token, @RequestBody DeletePusherReq req) { 79 | return roomService.deletePusher(userID, token, req, RoomMgr.DOUBLE_ROOM); 80 | } 81 | 82 | @ResponseBody 83 | @RequestMapping("pusher_heartbeat") 84 | public BaseRsp pusher_heartbeat(String userID, String token, @RequestBody PusherHeartbeatReq req) { 85 | return roomService.pusherHeartbeat(userID, token, req, RoomMgr.DOUBLE_ROOM); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/controller/LiveRoom.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.controller; 2 | 3 | import com.tencent.qcloud.roomservice.logic.RoomMgr; 4 | import com.tencent.qcloud.roomservice.pojo.Request.*; 5 | import com.tencent.qcloud.roomservice.pojo.Response.*; 6 | import com.tencent.qcloud.roomservice.pojo.Room; 7 | import com.tencent.qcloud.roomservice.service.RoomService; 8 | import com.tencent.qcloud.roomservice.service.UtilService; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.ResponseBody; 14 | 15 | import java.util.Map; 16 | 17 | /** 18 | * 直播-连麦 房间接口 19 | */ 20 | @Controller 21 | @ResponseBody 22 | @RequestMapping("/weapp/live_room") 23 | public class LiveRoom { 24 | @Autowired 25 | RoomService roomService; 26 | 27 | @Autowired 28 | UtilService utilService; 29 | 30 | @ResponseBody 31 | @RequestMapping("login") 32 | public LoginRsp login(String sdkAppID, String accountType, String userID, String userSig){ 33 | return roomService.login(sdkAppID, accountType, userID, userSig); 34 | } 35 | 36 | @ResponseBody 37 | @RequestMapping("logout") 38 | public BaseRsp logout(String userID, String token){ 39 | return roomService.logout(userID, token); 40 | } 41 | 42 | @ResponseBody 43 | @RequestMapping("get_push_url") 44 | public GetPushUrlRsp get_push_url(String userID, String token){ 45 | return utilService.getPushUrl(userID, token); 46 | } 47 | 48 | @ResponseBody 49 | @RequestMapping("get_room_list") 50 | public GetRoomListRsp get_room_list(String userID, String token, @RequestBody GetRoomListReq req){ 51 | return roomService.getRoomList(userID, token, req, RoomMgr.LIVE_ROOM); 52 | } 53 | 54 | @ResponseBody 55 | @RequestMapping("get_pushers") 56 | public Room get_pushers(String userID, String token, @RequestBody GetPushersReq req){ 57 | return roomService.getPushers(userID, token, req, RoomMgr.LIVE_ROOM); 58 | } 59 | 60 | @ResponseBody 61 | @RequestMapping("create_room") 62 | public CreateRoomRsp create_room(String userID, String token, @RequestBody CreateRoomReq req){ 63 | return roomService.createRoom(userID, token, req, RoomMgr.LIVE_ROOM); 64 | } 65 | 66 | @ResponseBody 67 | @RequestMapping("destroy_room") 68 | public BaseRsp destroy_room(String userID, String token, @RequestBody DestroyRoomReq req) { 69 | return roomService.destroyRoom(userID, token, req, RoomMgr.LIVE_ROOM); 70 | } 71 | 72 | @ResponseBody 73 | @RequestMapping("add_pusher") 74 | public BaseRsp add_pusher(String userID, String token, @RequestBody AddPusherReq req) { 75 | return roomService.addPusher(userID, token, req, RoomMgr.LIVE_ROOM); 76 | } 77 | 78 | @ResponseBody 79 | @RequestMapping("delete_pusher") 80 | public BaseRsp delete_pusher(String userID, String token, @RequestBody DeletePusherReq req) { 81 | return roomService.deletePusher(userID, token, req, RoomMgr.LIVE_ROOM); 82 | } 83 | 84 | @ResponseBody 85 | @RequestMapping("pusher_heartbeat") 86 | public BaseRsp pusher_heartbeat(String userID, String token, @RequestBody PusherHeartbeatReq req) { 87 | return roomService.pusherHeartbeat(userID, token, req, RoomMgr.LIVE_ROOM); 88 | } 89 | 90 | @ResponseBody 91 | @RequestMapping("merge_stream") 92 | public MergeStreamRsp merge_stream(String userID, String token, @RequestBody Map map) { 93 | return utilService.mergeStream(userID, token, map); 94 | } 95 | 96 | @ResponseBody 97 | @RequestMapping("get_custom_info") 98 | public GetCustomInfoRsp get_custom_info(String userID, String token, @RequestBody GetCustomInfoReq req) { 99 | return roomService.getCustomInfo(userID, token, req, RoomMgr.LIVE_ROOM); 100 | } 101 | 102 | @ResponseBody 103 | @RequestMapping("set_custom_field") 104 | public GetCustomInfoRsp set_custom_field(String userID, String token, @RequestBody SetCustomInfoReq req) { 105 | return roomService.setCustomInfo(userID, token, req, RoomMgr.LIVE_ROOM); 106 | } 107 | 108 | @ResponseBody 109 | @RequestMapping("add_audience") 110 | public BaseRsp add_audience(String userID, String token, @RequestBody AddAudienceReq req) { 111 | return roomService.addAudience(userID, token, req, RoomMgr.LIVE_ROOM); 112 | } 113 | 114 | @ResponseBody 115 | @RequestMapping("delete_audience") 116 | public BaseRsp delete_audience(String userID, String token, @RequestBody DelAudienceReq req) { 117 | return roomService.delAudience(userID, token, req, RoomMgr.LIVE_ROOM); 118 | } 119 | 120 | @ResponseBody 121 | @RequestMapping("get_audiences") 122 | public GetAudiencesRsp get_audiences(String userID, String token, @RequestBody GetAudiencesReq req) { 123 | return roomService.getAudiences(userID, token, req, RoomMgr.LIVE_ROOM); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/controller/MultiRoom.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.controller; 2 | 3 | import com.tencent.qcloud.roomservice.logic.RoomMgr; 4 | import com.tencent.qcloud.roomservice.pojo.Audience; 5 | import com.tencent.qcloud.roomservice.pojo.Pusher; 6 | import com.tencent.qcloud.roomservice.pojo.Request.*; 7 | import com.tencent.qcloud.roomservice.pojo.Response.*; 8 | import com.tencent.qcloud.roomservice.pojo.Room; 9 | import com.tencent.qcloud.roomservice.service.RoomService; 10 | import com.tencent.qcloud.roomservice.service.UtilService; 11 | import org.json.JSONObject; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.ResponseBody; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Map; 20 | 21 | /** 22 | * 多人房间接口 23 | */ 24 | @Controller 25 | @ResponseBody 26 | @RequestMapping("/weapp/multi_room") 27 | public class MultiRoom { 28 | 29 | @Autowired 30 | RoomService roomService; 31 | 32 | @Autowired 33 | UtilService utilService; 34 | 35 | @ResponseBody 36 | @RequestMapping("login") 37 | public LoginRsp login(String sdkAppID, String accountType, String userID, String userSig){ 38 | return roomService.login(sdkAppID, accountType, userID, userSig); 39 | } 40 | 41 | @ResponseBody 42 | @RequestMapping("logout") 43 | public BaseRsp logout(String userID, String token){ 44 | return roomService.logout(userID, token); 45 | } 46 | 47 | @ResponseBody 48 | @RequestMapping("get_push_url") 49 | public GetPushUrlRsp get_push_url(String userID, String token, @RequestBody GetPushUrlReq req){ 50 | return utilService.getPushUrl(userID, token, req); 51 | } 52 | 53 | @ResponseBody 54 | @RequestMapping("get_room_list") 55 | public GetRoomListRsp get_room_list(String userID, String token, @RequestBody GetRoomListReq req){ 56 | return roomService.getRoomList(userID, token, req, RoomMgr.MULTI_ROOM); 57 | } 58 | 59 | @ResponseBody 60 | @RequestMapping("get_pushers") 61 | public Room get_pushers(String userID, String token, @RequestBody GetPushersReq req){ 62 | return roomService.getPushers(userID, token, req, RoomMgr.MULTI_ROOM); 63 | } 64 | 65 | @ResponseBody 66 | @RequestMapping("create_room") 67 | public CreateRoomRsp create_room(String userID, String token, @RequestBody CreateRoomReq req){ 68 | return roomService.createRoom(userID, token, req, RoomMgr.MULTI_ROOM); 69 | } 70 | 71 | @ResponseBody 72 | @RequestMapping("destroy_room") 73 | public BaseRsp destroy_room(String userID, String token, @RequestBody DestroyRoomReq req) { 74 | return roomService.destroyRoom(userID, token, req, RoomMgr.MULTI_ROOM); 75 | } 76 | 77 | @ResponseBody 78 | @RequestMapping("add_pusher") 79 | public BaseRsp add_pusher(String userID, String token, @RequestBody AddPusherReq req) { 80 | return roomService.addPusher(userID, token, req, RoomMgr.MULTI_ROOM); 81 | } 82 | 83 | @ResponseBody 84 | @RequestMapping("delete_pusher") 85 | public BaseRsp delete_pusher(String userID, String token, @RequestBody DeletePusherReq req) { 86 | return roomService.deletePusher(userID, token, req, RoomMgr.MULTI_ROOM); 87 | } 88 | 89 | @ResponseBody 90 | @RequestMapping("pusher_heartbeat") 91 | public BaseRsp pusher_heartbeat(String userID, String token, @RequestBody PusherHeartbeatReq req) { 92 | return roomService.pusherHeartbeat(userID, token, req, RoomMgr.MULTI_ROOM); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/controller/Util.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.controller; 2 | 3 | 4 | import com.tencent.qcloud.roomservice.logic.IMMgr; 5 | import com.tencent.qcloud.roomservice.pojo.Request.GetLoginInfoReq; 6 | import com.tencent.qcloud.roomservice.pojo.Response.GetLoginInfoRsp; 7 | import com.tencent.qcloud.roomservice.pojo.Response.GetTestPushUrlRsp; 8 | import com.tencent.qcloud.roomservice.pojo.Response.GetTestRtmpAccUrlRsp; 9 | import com.tencent.qcloud.roomservice.service.UtilService; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import javax.annotation.Resource; 15 | 16 | @Controller 17 | @ResponseBody 18 | @RequestMapping("/weapp/utils") 19 | public class Util { 20 | @Resource 21 | IMMgr imMgr; 22 | 23 | @Autowired 24 | UtilService utilService; 25 | 26 | @ResponseBody 27 | @RequestMapping(value = "get_login_info") 28 | public GetLoginInfoRsp get_login_info(@ModelAttribute GetLoginInfoReq req){ 29 | return imMgr.getLoginInfo(req.getUserID()); 30 | } 31 | 32 | @ResponseBody 33 | @RequestMapping(value = "get_test_pushurl", method = RequestMethod.GET) 34 | public GetTestPushUrlRsp get_test_pushurl(){ 35 | return utilService.getTestPushUrl(); 36 | } 37 | 38 | @ResponseBody 39 | @RequestMapping("get_test_rtmpaccurl") 40 | public GetTestRtmpAccUrlRsp get_test_rtmpaccurl(){ 41 | return utilService.getTestRtmpAccUrl(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/logic/IMMgr.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.logic; 2 | 3 | import com.tencent.qcloud.roomservice.common.Config; 4 | import com.tencent.qcloud.roomservice.pojo.Request.CreateGroupReq; 5 | import com.tencent.qcloud.roomservice.pojo.Request.DestroyGroupReq; 6 | import com.tencent.qcloud.roomservice.pojo.Request.NotifyPusherChangeReq; 7 | import com.tencent.qcloud.roomservice.pojo.Response.GetLoginInfoRsp; 8 | import com.tencent.qcloud.roomservice.pojo.Response.LoginRsp; 9 | import com.tencent.qcloud.roomservice.utils.Utils; 10 | import com.tls.tls_sigature.tls_sigature; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.http.*; 15 | import org.springframework.stereotype.Component; 16 | import org.springframework.web.client.RestTemplate; 17 | import org.springframework.web.util.UriComponentsBuilder; 18 | 19 | import java.io.IOException; 20 | import java.util.UUID; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | 23 | @Component 24 | public class IMMgr { 25 | private static Logger log= LoggerFactory.getLogger(IMMgr.class); 26 | 27 | private static final String HOST = "https://console.tim.qq.com/"; // IM后台RESTful API的主机地址。 28 | 29 | private ConcurrentHashMap tokenMap = new ConcurrentHashMap<>(); 30 | 31 | @Autowired 32 | RestTemplate restTemplate; 33 | 34 | public LoginRsp verifySig(String sdkAppID, String accountType, String userID, String userSig) { 35 | LoginRsp rsp = new LoginRsp(); 36 | if (sdkAppID == null || accountType == null || userID == null || userSig == null) { 37 | rsp.setCode(4); 38 | rsp.setMessage("请求参数不全,请检查sdkAppID,IM_ACCOUNTTYPE,userID,userSig参数是否都存在"); 39 | log.error("verifySig请求参数不全,IM_SDKAPPID:" + sdkAppID + ", IM_ACCOUNTTYPE:" + accountType + ", userID:" + userID + ", userSig:" + userSig); 40 | } else { 41 | try { 42 | tls_sigature.CheckTLSSignatureResult checkResult = tls_sigature.CheckTLSSignatureEx(userSig, Long.valueOf(sdkAppID), userID, Config.IM.PUBLICKEY); 43 | if (checkResult.verifyResult == false) { 44 | rsp.setCode(7); 45 | rsp.setMessage("鉴权失败, " + checkResult.errMessage); 46 | log.error("verifySig鉴权失败, " + checkResult.errMessage); 47 | } else { 48 | rsp.setCode(0); 49 | rsp.setMessage("请求成功"); 50 | rsp.setUserID(userID); 51 | String token = makeToken(sdkAppID, accountType, Config.Live.APIKEY, userID); 52 | tokenMap.put(userID, token); 53 | rsp.setToken(token); 54 | log.info("verifySig鉴权成功"); 55 | } 56 | } catch (Exception e) { 57 | rsp.setCode(4); 58 | rsp.setMessage("签名校验出错," + e.getMessage()); 59 | log.error("verifySig签名校验出错, " + e.getMessage()); 60 | e.printStackTrace(); 61 | } 62 | 63 | } 64 | return rsp; 65 | } 66 | 67 | 68 | /** 69 | * 构建一个token = md5(APIKEY+APP_ID+IM_ACCOUNTTYPE+userID+radom) 70 | */ 71 | private String makeToken(String appID, String accountType, String apiKey, String userID) { 72 | String Token = Utils.getMD5(apiKey + appID + accountType + userID + Utils.S4() + Utils.S4() + Utils.S4()); 73 | return Token; 74 | } 75 | 76 | public GetLoginInfoRsp getLoginInfo(String userID) { 77 | GetLoginInfoRsp rsp = new GetLoginInfoRsp(); 78 | try { 79 | if (userID.length() == 0) { 80 | userID = Utils.genUserIdByRandom(); 81 | } 82 | tls_sigature.GenTLSSignatureResult result = tls_sigature.GenTLSSignatureEx(Config.IM.IM_SDKAPPID, userID, Config.IM.PRIVATEKEY, 30 * 24 * 60 * 60); 83 | if (0 == result.urlSig.length()) { 84 | rsp.setCode(1); 85 | rsp.setMessage(result.errMessage); 86 | log.error("getLoginInfo生成usersig失败, userID: " + userID + ", errMsg: " + result.errMessage); 87 | } else { 88 | rsp.setCode(0); 89 | rsp.setMessage("请求成功"); 90 | rsp.setSdkAppID(Config.IM.IM_SDKAPPID); 91 | rsp.setAccType(Config.IM.IM_ACCOUNTTYPE); 92 | rsp.setUserID(userID); 93 | rsp.setUserSig(result.urlSig); 94 | log.info("getLoginInfo成功, userID:" + userID); 95 | } 96 | } catch (IOException e) { 97 | e.printStackTrace(); 98 | log.error("getLoginInfo出现异常, userID: " + userID + ", errMsg: " + e.getMessage()); 99 | } 100 | return rsp; 101 | } 102 | 103 | public int validation(String userID, String token) { 104 | int ret = 1; 105 | if (tokenMap.containsKey(userID) && tokenMap.get(userID).equals(token)) { 106 | ret = 0; 107 | } 108 | return ret; 109 | } 110 | 111 | public void delLoginSession(String userID) { 112 | if (tokenMap.containsKey(userID)) { 113 | tokenMap.remove(userID); 114 | } 115 | } 116 | 117 | /** 118 | * 建群 - 参考@https://cloud.tencent.com/document/product/269/1615 119 | */ 120 | public boolean createGroup(String groupID) { 121 | HttpHeaders headers = new HttpHeaders(); 122 | headers.setContentType(MediaType.APPLICATION_JSON); 123 | 124 | UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(HOST + "v4/group_open_http_svc/create_group" + getQueryString()); 125 | 126 | CreateGroupReq req = new CreateGroupReq(); 127 | req.setGroupId(groupID); 128 | req.setName("group_name"); 129 | req.setType("AVChatRoom"); 130 | req.setOwner_Account(Config.IM.ADMINISTRATOR); 131 | 132 | HttpEntity entity = new HttpEntity(Utils.objectToString(req), headers); 133 | 134 | ResponseEntity response = restTemplate.exchange( 135 | builder.build().encode().toUri(), 136 | HttpMethod.POST, 137 | entity, 138 | String.class); 139 | 140 | log.info("createGroup, groupID: " + groupID + ", body: " + response.toString()); 141 | if (response.getStatusCode().value() == HttpStatus.OK.value()){ 142 | return true; 143 | } else { 144 | log.error("createGroup失败, groupID: " + groupID + ", errMsg: " + response.toString()); 145 | return false; 146 | } 147 | } 148 | 149 | private String getQueryString() { 150 | String query = 151 | "?sdkappid=" + Config.IM.IM_SDKAPPID + 152 | "&identifier=" + Config.IM.ADMINISTRATOR + 153 | "&usersig=" + getLoginInfo(Config.IM.ADMINISTRATOR).getUserSig() + 154 | "&random=" + UUID.randomUUID().toString() + 155 | "&contenttype=json"; 156 | return query; 157 | } 158 | 159 | /** 160 | * 解散群 - 参考@https://cloud.tencent.com/document/product/269/1624 161 | */ 162 | public void destroyGroup(String groupID) { 163 | HttpHeaders headers = new HttpHeaders(); 164 | headers.setContentType(MediaType.APPLICATION_JSON); 165 | 166 | UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(HOST + "v4/group_open_http_svc/destroy_group" + getQueryString()); 167 | 168 | DestroyGroupReq req = new DestroyGroupReq(); 169 | req.setGroupId(groupID); 170 | 171 | HttpEntity entity = new HttpEntity(Utils.objectToString(req), headers); 172 | 173 | ResponseEntity response = restTemplate.exchange( 174 | builder.build().encode().toUri(), 175 | HttpMethod.POST, 176 | entity, 177 | String.class); 178 | log.info("destroyGroup, groupID: " + groupID + ", body: " + response.toString()); 179 | if (response.getStatusCode().value() != HttpStatus.OK.value()){ 180 | log.error("destroyGroup失败, groupID: " + groupID + ", errMsg: " + response.toString()); 181 | } 182 | } 183 | 184 | /** 185 | * 通知房间成员变动 - 参考@https://cloud.tencent.com/document/product/269/1630 186 | */ 187 | public void notifyPushersChange(String groupID) { 188 | HttpHeaders headers = new HttpHeaders(); 189 | headers.setContentType(MediaType.APPLICATION_JSON); 190 | 191 | UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(HOST + "v4/group_open_http_svc/send_group_system_notification" + getQueryString()); 192 | 193 | NotifyPusherChangeReq req = new NotifyPusherChangeReq(); 194 | req.setGroupId(groupID); 195 | NotifyPusherChangeReq.Content content = new NotifyPusherChangeReq.Content(); 196 | content.setCmd("notifyPusherChange"); 197 | content.setData(""); 198 | req.setContent(Utils.objectToString(content)); 199 | 200 | HttpEntity entity = new HttpEntity(Utils.objectToString(req), headers); 201 | 202 | ResponseEntity response = restTemplate.exchange( 203 | builder.build().encode().toUri(), 204 | HttpMethod.POST, 205 | entity, 206 | String.class); 207 | log.info("notifyPushersChange, groupID: " + groupID + ", body: " + response.toString()); 208 | if (response.getStatusCode().value() != HttpStatus.OK.value()){ 209 | log.error("notifyPushersChange失败, groupID: " + groupID + ", errMsg: " + response.toString()); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/logic/LiveUtil.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.logic; 2 | 3 | import com.tencent.qcloud.roomservice.common.Config; 4 | import com.tencent.qcloud.roomservice.pojo.Response.GetTestPushUrlRsp; 5 | import com.tencent.qcloud.roomservice.pojo.StreamIDS; 6 | import com.tencent.qcloud.roomservice.utils.Utils; 7 | import org.json.JSONObject; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.*; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.web.client.RestTemplate; 14 | import org.springframework.web.util.UriComponentsBuilder; 15 | 16 | import java.util.Map; 17 | 18 | @Component 19 | public class LiveUtil { 20 | @Autowired 21 | RestTemplate restTemplate; 22 | 23 | private static Logger log= LoggerFactory.getLogger(LiveUtil.class); 24 | 25 | /** 26 | * 生成推流地址 27 | */ 28 | public String genPushUrl(String userID) { 29 | return genPushUrl(userID, System.currentTimeMillis() / 1000 + Config.Live.validTime); 30 | } 31 | 32 | public String genPushUrl(String userID, long txTime) { 33 | String liveCode = Config.Live.APP_BIZID + "_" + userID; 34 | String txSecret = Utils.getMD5(Config.Live.PUSH_SECRET_KEY + liveCode + Long.toHexString(txTime).toUpperCase()); 35 | String ext = "?bizid=" + Config.Live.APP_BIZID + "&txSecret=" + txSecret + "&txTime=" + Long.toHexString(txTime).toUpperCase(); 36 | String pushUrl = "rtmp://" + Config.Live.PUSHHOST + "/live/" + liveCode + ext; 37 | log.info("genPushUrl , url: " + pushUrl); 38 | return pushUrl; 39 | } 40 | 41 | /** 42 | * 从推流地址中提取流ID,完整的(推流状态检查) + 去掉bizid前缀的(生成对应的播放地址) 43 | */ 44 | public StreamIDS getStreamIdFromPushUrl(String pushUrl) { 45 | StreamIDS ids = new StreamIDS(); 46 | int index = pushUrl.indexOf("?"); 47 | if (index == -1) { 48 | ids.setStreamID(""); 49 | ids.setSubID(""); 50 | } else { 51 | String substr = pushUrl.substring(0, index); 52 | int index_2 = substr.lastIndexOf("/"); 53 | String streamID = substr.substring(index_2 + 1, index); 54 | String prefix = Config.Live.APP_BIZID + "_"; 55 | String subID = streamID.substring(prefix.length(), streamID.length()); 56 | ids.setStreamID(streamID); 57 | ids.setSubID(subID); 58 | } 59 | return ids; 60 | } 61 | 62 | 63 | /** 64 | * 生成混流地址 65 | */ 66 | public String genMixedPlayUrl(String subID, String suffix) { 67 | String liveCode = Config.Live.APP_BIZID + "_" + subID; 68 | return "http://" + Config.Live.PLAYHOST + "/live/" + liveCode + "." + suffix; 69 | } 70 | 71 | /** 72 | * 生成加速拉流播放地址 73 | */ 74 | public String genAcceleratePlayUrl(String subID) { 75 | return genAcceleratePlayUrl(subID, System.currentTimeMillis() / 1000 + Config.Live.validTime); 76 | } 77 | 78 | /** 79 | * 生成加速拉流播放地址 80 | */ 81 | public String genAcceleratePlayUrl(String subID, long txTime) { 82 | String liveCode = Config.Live.APP_BIZID + "_" + subID; 83 | String txSecret = Utils.getMD5(Config.Live.PUSH_SECRET_KEY + liveCode + Long.toHexString(txTime).toUpperCase()); 84 | String ext = "?bizid=" + Config.Live.APP_BIZID + "&txSecret=" + txSecret + "&txTime=" + Long.toHexString(txTime).toUpperCase(); 85 | String accPlayUrl = "rtmp://" + Config.Live.PLAYHOST + "/live/" + liveCode + ext; 86 | return accPlayUrl; 87 | } 88 | 89 | /** 90 | * 生成一组播放地址 91 | */ 92 | public GetTestPushUrlRsp getTestPushUrl(String userID) { 93 | GetTestPushUrlRsp rsp = new GetTestPushUrlRsp(); 94 | long txTime = System.currentTimeMillis() / 1000 + Config.Live.validTime; 95 | String liveCode = Config.Live.APP_BIZID + "_" + userID; 96 | String txSecret = Utils.getMD5(Config.Live.PUSH_SECRET_KEY + liveCode + Long.toHexString(txTime).toUpperCase()); 97 | String ext = "?bizid=" + Config.Live.APP_BIZID + "&txSecret=" + txSecret + "&txTime=" + Long.toHexString(txTime).toUpperCase(); 98 | String pushUrl = "rtmp://" + Config.Live.PUSHHOST + "/live/" + liveCode + ext; 99 | String flvPlayUrl = "http://" + Config.Live.PLAYHOST + "/live/" + liveCode + ".flv"; 100 | String rtmpPlayUrl = "rtmp://" + Config.Live.PLAYHOST + "/live/" + liveCode; 101 | String hlsPlayUrl = "http://" + Config.Live.PLAYHOST + "/live/" + liveCode + ".m3u8"; 102 | String accPlayUrl = "rtmp://" + Config.Live.PLAYHOST + "/live/" + liveCode + ext; 103 | rsp.setUrl_push(pushUrl); 104 | rsp.setUrl_play_flv(flvPlayUrl); 105 | rsp.setUrl_play_rtmp(rtmpPlayUrl); 106 | rsp.setUrl_play_hls(hlsPlayUrl); 107 | rsp.setUrl_play_acc(accPlayUrl); 108 | return rsp; 109 | } 110 | 111 | /** 112 | * 向云直播后台请求混流操作 113 | */ 114 | public String mergeStream(Map map) { 115 | HttpHeaders headers = new HttpHeaders(); 116 | headers.setContentType(MediaType.APPLICATION_JSON); 117 | 118 | UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://fcgi.video.qcloud.com/common_access" + getQueryString()); 119 | 120 | HttpEntity entity; 121 | Object param = map.get("mergeParams"); 122 | if (param instanceof String) { 123 | entity = new HttpEntity((String) param, headers); 124 | } else { 125 | entity = new HttpEntity(JSONObject.valueToString(param), headers); 126 | } 127 | 128 | ResponseEntity response = restTemplate.exchange( 129 | builder.build().encode().toUri(), 130 | HttpMethod.POST, 131 | entity, 132 | String.class); 133 | 134 | log.info("mergeStream response: " + response.toString()); 135 | 136 | return response.getBody(); 137 | } 138 | 139 | private String getQueryString() { 140 | long txTime = System.currentTimeMillis() / 1000 + 60; 141 | String txSecret = Utils.getMD5(Config.Live.APIKEY + txTime); 142 | String query = "?appid=" + Config.Live.APP_ID + 143 | "&interface=mix_streamv2.start_mix_stream_advanced&t=" + txTime + "&sign=" + txSecret; 144 | return query; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/logic/RoomMgr.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.logic; 2 | 3 | import com.tencent.qcloud.roomservice.common.Config; 4 | import com.tencent.qcloud.roomservice.pojo.Audience; 5 | import com.tencent.qcloud.roomservice.pojo.Pusher; 6 | import com.tencent.qcloud.roomservice.pojo.Response.GetStreamStatusRsp; 7 | import com.tencent.qcloud.roomservice.pojo.Room; 8 | import com.tencent.qcloud.roomservice.utils.Utils; 9 | import org.codehaus.jackson.map.ObjectMapper; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.InitializingBean; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.http.*; 15 | import org.springframework.stereotype.Component; 16 | import org.springframework.web.client.RestTemplate; 17 | import org.springframework.web.util.UriComponentsBuilder; 18 | 19 | import javax.annotation.Resource; 20 | import java.io.IOException; 21 | import java.util.ArrayList; 22 | import java.util.Timer; 23 | import java.util.TimerTask; 24 | import java.util.concurrent.ConcurrentHashMap; 25 | 26 | @Component 27 | public class RoomMgr implements InitializingBean { 28 | public static final int MULTI_ROOM = 0; 29 | public static final int DOUBLE_ROOM = 1; 30 | public static final int LIVE_ROOM = 2; 31 | 32 | private ConcurrentHashMap liveRoomMap = new ConcurrentHashMap<>(); 33 | private ConcurrentHashMap doubleRoomMap = new ConcurrentHashMap<>(); 34 | private ConcurrentHashMap multiRoomMap = new ConcurrentHashMap<>(); 35 | 36 | private HeartTimer heartTimer = new HeartTimer(); 37 | private Timer timer = null; 38 | 39 | private static Logger log = LoggerFactory.getLogger(RoomMgr.class); 40 | 41 | @Autowired 42 | RestTemplate restTemplate; 43 | 44 | @Resource 45 | IMMgr imMgr; 46 | 47 | @Override 48 | public void afterPropertiesSet() throws Exception { 49 | // 开启心跳检查定时器 50 | if (timer == null) { 51 | timer = new Timer(); 52 | timer.schedule(heartTimer, 5 * 1000, 5 * 1000); 53 | } 54 | } 55 | 56 | /** 57 | * 获取房间列表 58 | */ 59 | public ArrayList getList(int cnt, int index, boolean withPushers, int type) { 60 | ConcurrentHashMap orgMap = getActuralMap(type); 61 | ArrayList resultList = new ArrayList<>(); 62 | int cursor = 0; 63 | int roomCnt = 0; 64 | //遍历 65 | for (Room value : orgMap.values()) { 66 | if (roomCnt >= cnt) 67 | break; 68 | log.info("getRoomList, type:" + type + " , roomID:" + value.getRoomID() + " ,isActived: " + value.isActived() + " ,pushers count: " + value.getPushersCnt()); 69 | if (value.isActived() && value.getPushersCnt() != 0) { 70 | if (cursor >= index) { 71 | resultList.add(value); 72 | ++roomCnt; 73 | } else { 74 | ++cursor; 75 | continue; 76 | } 77 | 78 | } 79 | } 80 | 81 | return resultList; 82 | } 83 | 84 | /** 85 | * 获取房间 86 | */ 87 | public Room getRoom(String roomID, int type) { 88 | ConcurrentHashMap orgMap = getActuralMap(type); 89 | if (orgMap.containsKey(roomID)) { 90 | return orgMap.get(roomID); 91 | } else { 92 | return null; 93 | } 94 | } 95 | 96 | /** 97 | * 创建房间 98 | */ 99 | public void creatRoom(String roomID, String roomInfo, int type) { 100 | ConcurrentHashMap orgMap = getActuralMap(type); 101 | Room room = new Room(); 102 | room.setRoomID(roomID); 103 | room.setActived(false); 104 | room.setRoomInfo(roomInfo); 105 | orgMap.put(roomID, room); 106 | log.info("creat_room, type: " + type + " , roomID: " + roomID + ", roomInfo: " + roomInfo); 107 | } 108 | 109 | /** 110 | * 房间是否存在 111 | */ 112 | public boolean isRoomExist(String roomID, int type) { 113 | ConcurrentHashMap orgMap = getActuralMap(type); 114 | return orgMap.containsKey(roomID); 115 | } 116 | 117 | /** 118 | * 是否是房间创建者 119 | */ 120 | public boolean isRoomCreator(String roomID, String userID, int type) { 121 | ConcurrentHashMap orgMap = getActuralMap(type); 122 | Room room = orgMap.get(roomID); 123 | if (room != null && room.getRoomCreator().equals(userID)) 124 | return true; 125 | return false; 126 | } 127 | 128 | /** 129 | * 删除房间 130 | */ 131 | public void delRoom(String roomID, int type) { 132 | ConcurrentHashMap orgMap = getActuralMap(type); 133 | orgMap.remove(roomID); 134 | log.info("delRoom, type: " + type + " , roomID: " + roomID); 135 | } 136 | 137 | private ConcurrentHashMap getActuralMap(int type) { 138 | ConcurrentHashMap actMap; 139 | switch (type) { 140 | case MULTI_ROOM: 141 | actMap = multiRoomMap; 142 | break; 143 | case DOUBLE_ROOM: 144 | actMap = doubleRoomMap; 145 | break; 146 | case LIVE_ROOM: 147 | actMap = liveRoomMap; 148 | break; 149 | default: 150 | actMap = new ConcurrentHashMap<>(); 151 | } 152 | return actMap; 153 | } 154 | 155 | /** 156 | * 新增房间并进房 157 | */ 158 | public void addRoom(String roomID, String roomInfo, String userID, String mixedPlayUrl, String userName, String userAvatar, String pushURL, String streamID, String acceleratePlayUrl, int type) { 159 | ConcurrentHashMap orgMap = getActuralMap(type); 160 | Room room = new Room(); 161 | room.setRoomID(roomID); 162 | room.setRoomInfo(roomInfo); 163 | room.setRoomCreator(userID); 164 | room.setMixedPlayURL(mixedPlayUrl); 165 | room.setActived(true); 166 | Pusher pusher = new Pusher(); 167 | pusher.setUserID(userID); 168 | pusher.setUserName(userName); 169 | pusher.setUserAvatar(userAvatar); 170 | pusher.setPushURL(pushURL); 171 | pusher.setStreamID(streamID); 172 | pusher.setAccelerateURL(acceleratePlayUrl); 173 | pusher.setUserID(userID); 174 | pusher.setTimestamp(System.currentTimeMillis() / 1000); 175 | room.addPusher(pusher); 176 | orgMap.put(roomID, room); 177 | log.info("addRoom, type: " + type + " , roomID: " + roomID + ", roomInfo: " + roomInfo + ", userID: " + userID + ", streamID: " + streamID); 178 | } 179 | 180 | public boolean isMember(String roomID, String userID, int type) { 181 | ConcurrentHashMap orgMap = getActuralMap(type); 182 | Room room = orgMap.get(roomID); 183 | if (room != null) { 184 | Pusher pusher = room.getPusher(userID); 185 | if (pusher != null) 186 | return true; 187 | } 188 | return false; 189 | } 190 | 191 | public void updateMember(String roomID, String userID, String mixedPlayUrl, String userName, String userAvatar, String pushURL, String streamID, String acceleratePlayUrl, int type) { 192 | addMember(roomID, userID, mixedPlayUrl, userName, userAvatar, pushURL, streamID, acceleratePlayUrl, type); 193 | updateMemberTS(roomID, userID, type); 194 | } 195 | 196 | /** 197 | * 心跳更新 198 | */ 199 | public void updateMemberTS(String roomID, String userID, int type) { 200 | ConcurrentHashMap orgMap = getActuralMap(type); 201 | Room room = orgMap.get(roomID); 202 | if (room != null) { 203 | Pusher pusher = room.getPusher(userID); 204 | if (pusher != null) 205 | pusher.setTimestamp(System.currentTimeMillis() / 1000); 206 | } 207 | } 208 | 209 | /** 210 | * 获取房间推流者人数 211 | */ 212 | public int getMemberCnt(String roomID, int type) { 213 | int count = 0; 214 | ConcurrentHashMap orgMap = getActuralMap(type); 215 | Room room = orgMap.get(roomID); 216 | if (room != null) { 217 | count = room.getPushersCnt(); 218 | } 219 | return count; 220 | } 221 | 222 | /** 223 | * 新增推流者 - 进房 224 | */ 225 | public void addMember(String roomID, String userID, String mixedPlayUrl, String userName, String userAvatar, String pushURL, String streamID, String acceleratePlayUrl, int type) { 226 | ConcurrentHashMap orgMap = getActuralMap(type); 227 | Room room = orgMap.get(roomID); 228 | if (room != null) { 229 | if (room.isActived() == false) { 230 | room.setActived(true); 231 | room.setRoomCreator(userID); 232 | room.setMixedPlayURL(mixedPlayUrl); 233 | } 234 | 235 | Pusher pusher = room.getPusher(userID); 236 | if (pusher != null) { 237 | pusher.setUserName(userName); 238 | pusher.setUserAvatar(userAvatar); 239 | pusher.setPushURL(pushURL); 240 | pusher.setStreamID(streamID); 241 | pusher.setAccelerateURL(acceleratePlayUrl); 242 | pusher.setTimestamp(System.currentTimeMillis() / 1000); 243 | } else { 244 | pusher = new Pusher(); 245 | pusher.setUserID(userID); 246 | pusher.setUserName(userName); 247 | pusher.setUserAvatar(userAvatar); 248 | pusher.setPushURL(pushURL); 249 | pusher.setStreamID(streamID); 250 | pusher.setAccelerateURL(acceleratePlayUrl); 251 | pusher.setTimestamp(System.currentTimeMillis() / 1000); 252 | } 253 | room.addPusher(pusher); 254 | log.info("addMember, type: " + type + " , roomID: " + roomID + ", userID: " + userID + ", streamID: " + streamID); 255 | } 256 | } 257 | 258 | /** 259 | * 删除推流者 260 | */ 261 | public void delPusher(String roomID, String userID, int type) { 262 | ConcurrentHashMap orgMap = getActuralMap(type); 263 | Room room = orgMap.get(roomID); 264 | if (room != null) { 265 | if (type == LIVE_ROOM && room.getRoomCreator().equals(userID)) { 266 | orgMap.remove(roomID); 267 | imMgr.destroyGroup(roomID); 268 | } else { 269 | room.delPusher(userID); 270 | imMgr.notifyPushersChange(roomID); 271 | if (room.getPushersCnt() == 0) { 272 | orgMap.remove(roomID); 273 | } 274 | } 275 | } 276 | } 277 | 278 | public String getCustomInfo(String roomID, int type) { 279 | Room room = getRoom(roomID, type); 280 | if (room != null) { 281 | return room.getCustomInfo(); 282 | } 283 | return ""; 284 | } 285 | 286 | public String setCustomInfo(String roomID, String fieldName, String operation, int type) { 287 | Room room = getRoom(roomID, type); 288 | if (room != null) { 289 | room.setCustomInfo(fieldName, operation); 290 | return room.getCustomInfo(); 291 | } 292 | return ""; 293 | } 294 | 295 | public class HeartTimer extends TimerTask { 296 | @Override 297 | public void run() { 298 | onTimer(); 299 | } 300 | } 301 | 302 | private void onTimer() { 303 | onTimerCheckHeartBeat(multiRoomMap, Config.MultiRoom.heartBeatTimeout, MULTI_ROOM); 304 | onTimerCheckHeartBeat(doubleRoomMap, Config.DoubleRoom.heartBeatTimeout, DOUBLE_ROOM); 305 | onTimerCheckHeartBeat(liveRoomMap, Config.LiveRoom.heartBeatTimeout, LIVE_ROOM); 306 | } 307 | 308 | /** 309 | * 心跳超时检查 310 | * timeout 过期时间,单位秒 311 | */ 312 | private void onTimerCheckHeartBeat(ConcurrentHashMap map, int timeout, int type) { 313 | // 遍历房间每个成员,检查pusher的时间戳是否超过timeout 314 | long currentTS = System.currentTimeMillis()/1000; 315 | for (Room room : map.values()) { 316 | if (room.isActived()) { 317 | for (Pusher pusher : room.getPushersMap().values()) { 318 | // 心跳超时 319 | if (pusher.getTimestamp() + timeout < currentTS) { 320 | if (getStreamStatus(pusher.getStreamID()) == 1) { 321 | // 流断了,删除用户 322 | delPusher(room.getRoomID(), pusher.getUserID(), type); 323 | } else { 324 | // 补一个心跳 325 | updateMemberTS(room.getRoomID(), pusher.getUserID(), type); 326 | } 327 | } 328 | } 329 | } 330 | } 331 | } 332 | 333 | /** 334 | * 辅助功能函数 - 心跳超时检查流状态 335 | * 0: 流状态是推流中 336 | * 1: 流状态是断流 337 | * 2: 请求错误 338 | */ 339 | private int getStreamStatus (String streamID) { 340 | HttpHeaders headers = new HttpHeaders(); 341 | headers.setContentType(MediaType.APPLICATION_JSON); 342 | 343 | // 5分钟 344 | long txTime = System.currentTimeMillis() / 1000 + 300; 345 | String txSecret = Utils.getMD5(Config.Live.APIKEY + txTime); 346 | 347 | UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://fcgi.video.qcloud.com/common_access?appid=" + Config.Live.APP_ID 348 | + "&interface=Live_Channel_GetStatus&Param.s.channel_id=" + streamID 349 | + "&t=" + txTime 350 | + "&sign=" + txSecret); 351 | 352 | HttpEntity entity = new HttpEntity(headers); 353 | 354 | ResponseEntity response = restTemplate.exchange( 355 | builder.build().encode().toUri(), 356 | HttpMethod.GET, 357 | entity, 358 | String.class); 359 | 360 | log.info("getStreamStatus, streamID:" + streamID + ", response :" + response.getBody()); 361 | 362 | // 错误 363 | if (response.getStatusCode().value() != HttpStatus.OK.value()) { 364 | log.warn("getStreamStatus出错, streamID: " + streamID + ", HttpCode: " + response.getStatusCode().value()); 365 | return 2; 366 | } 367 | 368 | ObjectMapper mapper = new ObjectMapper(); 369 | GetStreamStatusRsp rsp = null; 370 | 371 | try { 372 | rsp = mapper.readValue(response.getBody(), GetStreamStatusRsp.class); 373 | } catch (IOException e) { 374 | log.error("解析getStreamStatus响应出错, streamID: " + streamID + ", response body: " + response.getBody(), e); 375 | return 2; 376 | } 377 | 378 | // 推流中 379 | if (rsp != null && rsp.getRet() == 0 && rsp.getOutput().get(0) != null && rsp.getOutput().get(0).getStatus() == 1) { 380 | return 0; 381 | } 382 | 383 | return 1; 384 | } 385 | 386 | public void addAudience(String roomID, String userID, String userInfo, int type) { 387 | // 只有直播房间有观众 388 | if (type != LIVE_ROOM) 389 | return; 390 | 391 | Room room = liveRoomMap.get(roomID); 392 | if (room != null) { 393 | Audience audience = new Audience(); 394 | audience.setUserID(userID); 395 | audience.setUserInfo(userInfo); 396 | room.addAudience(audience); 397 | log.info("addAudience, roomID:" + roomID + ", userID :" + userID + ", userInfo:" + userInfo); 398 | } 399 | } 400 | 401 | public void delAudience(String roomID, String userID, int type) { 402 | // 只有直播房间有观众 403 | if (type != LIVE_ROOM) 404 | return; 405 | 406 | Room room = liveRoomMap.get(roomID); 407 | if (room != null) { 408 | room.delAudience(userID); 409 | log.info("delAudience, roomID:" + roomID + ", userID :" + userID); 410 | } 411 | } 412 | 413 | /** 414 | * 获取房间观众人数 415 | */ 416 | public int getAudienceCnt(String roomID, int type) { 417 | int count = 0; 418 | // 只有直播房间有观众 419 | if (type == LIVE_ROOM) { 420 | Room room = liveRoomMap.get(roomID); 421 | if (room != null) { 422 | count = room.getAudiencesCnt(); 423 | } 424 | } 425 | log.info("getAudienceCnt, roomID:" + roomID + ", count :" + count); 426 | return count; 427 | } 428 | 429 | public ArrayList getAudiences(String roomID, int type) { 430 | // 只有直播房间有观众 431 | if (type == LIVE_ROOM) { 432 | Room room = liveRoomMap.get(roomID); 433 | if (room != null) 434 | return room.getAudiences(); 435 | } 436 | return new ArrayList<>(); 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Audience.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo; 2 | 3 | /* 4 | * 观众信息 5 | */ 6 | public class Audience { 7 | private String userID = ""; 8 | private String userInfo = ""; 9 | 10 | public String getUserID() { 11 | return userID; 12 | } 13 | 14 | public void setUserID(String userID) { 15 | this.userID = userID; 16 | } 17 | 18 | public String getUserInfo() { 19 | return userInfo; 20 | } 21 | 22 | public void setUserInfo(String userInfo) { 23 | this.userInfo = userInfo; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Pusher.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo; 2 | 3 | import org.codehaus.jackson.annotate.JsonIgnore; 4 | 5 | 6 | /** 7 | * 推流者信息 8 | */ 9 | public class Pusher { 10 | private String userID = ""; 11 | private String userName = ""; 12 | private String userAvatar = ""; 13 | private String pushURL = ""; 14 | private String streamID = ""; 15 | private String accelerateURL = ""; 16 | private long timestamp = 0; 17 | 18 | public String getUserID() { 19 | return userID; 20 | } 21 | 22 | public void setUserID(String userID) { 23 | this.userID = userID; 24 | } 25 | 26 | public String getUserName() { 27 | return userName; 28 | } 29 | 30 | public void setUserName(String userName) { 31 | this.userName = userName; 32 | } 33 | 34 | public String getUserAvatar() { 35 | return userAvatar; 36 | } 37 | 38 | public void setUserAvatar(String userAvatar) { 39 | this.userAvatar = userAvatar; 40 | } 41 | 42 | @JsonIgnore 43 | public String getPushURL() { 44 | return pushURL; 45 | } 46 | 47 | public void setPushURL(String pushURL) { 48 | this.pushURL = pushURL; 49 | } 50 | 51 | @JsonIgnore 52 | public String getStreamID() { 53 | return streamID; 54 | } 55 | 56 | public void setStreamID(String streamID) { 57 | this.streamID = streamID; 58 | } 59 | 60 | public String getAccelerateURL() { 61 | return accelerateURL; 62 | } 63 | 64 | public void setAccelerateURL(String accelerateURL) { 65 | this.accelerateURL = accelerateURL; 66 | } 67 | 68 | @JsonIgnore 69 | public long getTimestamp() { 70 | return timestamp; 71 | } 72 | 73 | public void setTimestamp(long timestamp) { 74 | this.timestamp = timestamp; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/AddAudienceReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class AddAudienceReq { 4 | private String roomID = ""; 5 | private String userID = ""; 6 | private String userInfo = ""; 7 | 8 | public String getUserID() { 9 | return userID; 10 | } 11 | 12 | public void setUserID(String userID) { 13 | this.userID = userID; 14 | } 15 | 16 | public String getUserInfo() { 17 | return userInfo; 18 | } 19 | 20 | public void setUserInfo(String userInfo) { 21 | this.userInfo = userInfo; 22 | } 23 | 24 | public String getRoomID() { 25 | return roomID; 26 | } 27 | 28 | public void setRoomID(String roomID) { 29 | this.roomID = roomID; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/AddPusherReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class AddPusherReq { 4 | private String roomID = ""; 5 | private String roomInfo = ""; 6 | private String userID = ""; 7 | private String userName = ""; 8 | private String userAvatar = ""; 9 | private String pushURL = ""; 10 | 11 | public String getRoomID() { 12 | return roomID; 13 | } 14 | 15 | public void setRoomID(String roomID) { 16 | this.roomID = roomID; 17 | } 18 | 19 | public String getUserID() { 20 | return userID; 21 | } 22 | 23 | public void setUserID(String userID) { 24 | this.userID = userID; 25 | } 26 | 27 | public String getUserName() { 28 | return userName; 29 | } 30 | 31 | public void setUserName(String userName) { 32 | this.userName = userName; 33 | } 34 | 35 | public String getUserAvatar() { 36 | return userAvatar; 37 | } 38 | 39 | public void setUserAvatar(String userAvatar) { 40 | this.userAvatar = userAvatar; 41 | } 42 | 43 | public String getPushURL() { 44 | return pushURL; 45 | } 46 | 47 | public void setPushURL(String pushURL) { 48 | this.pushURL = pushURL; 49 | } 50 | 51 | public String getRoomInfo() { 52 | return roomInfo; 53 | } 54 | 55 | public void setRoomInfo(String roomInfo) { 56 | this.roomInfo = roomInfo; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/CreateGroupReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | import org.codehaus.jackson.annotate.JsonIgnore; 4 | import org.codehaus.jackson.annotate.JsonProperty; 5 | 6 | public class CreateGroupReq { 7 | @JsonProperty(value = "Owner_Account") 8 | private String Owner_Account; 9 | @JsonProperty(value = "Type") 10 | private String Type = ""; 11 | @JsonProperty(value = "GroupId") 12 | private String GroupId = ""; 13 | @JsonProperty(value = "Name") 14 | private String Name = ""; 15 | 16 | @JsonIgnore 17 | public String getOwner_Account() { 18 | return Owner_Account; 19 | } 20 | 21 | public void setOwner_Account(String owner_Account) { 22 | Owner_Account = owner_Account; 23 | } 24 | 25 | @JsonIgnore 26 | public String getType() { 27 | return Type; 28 | } 29 | 30 | public void setType(String type) { 31 | Type = type; 32 | } 33 | 34 | @JsonIgnore 35 | public String getGroupId() { 36 | return GroupId; 37 | } 38 | 39 | public void setGroupId(String groupId) { 40 | GroupId = groupId; 41 | } 42 | 43 | @JsonIgnore 44 | public String getName() { 45 | return Name; 46 | } 47 | 48 | public void setName(String name) { 49 | Name = name; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/CreateRoomReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class CreateRoomReq { 4 | private String userID = ""; 5 | private String roomInfo = ""; 6 | private String roomID = ""; 7 | 8 | public String getUserID() { 9 | return userID; 10 | } 11 | 12 | public void setUserID(String userID) { 13 | this.userID = userID; 14 | } 15 | 16 | public String getRoomInfo() { 17 | return roomInfo; 18 | } 19 | 20 | public void setRoomInfo(String roomInfo) { 21 | this.roomInfo = roomInfo; 22 | } 23 | 24 | public String getRoomID() { 25 | return roomID; 26 | } 27 | 28 | public void setRoomID(String roomID) { 29 | this.roomID = roomID; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/DelAudienceReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class DelAudienceReq { 4 | private String roomID = ""; 5 | private String userID = ""; 6 | 7 | public String getUserID() { 8 | return userID; 9 | } 10 | 11 | public void setUserID(String userID) { 12 | this.userID = userID; 13 | } 14 | 15 | public String getRoomID() { 16 | return roomID; 17 | } 18 | 19 | public void setRoomID(String roomID) { 20 | this.roomID = roomID; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/DeletePusherReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class DeletePusherReq { 4 | private String roomID = ""; 5 | private String userID = ""; 6 | 7 | public String getRoomID() { 8 | return roomID; 9 | } 10 | 11 | public void setRoomID(String roomID) { 12 | this.roomID = roomID; 13 | } 14 | 15 | public String getUserID() { 16 | return userID; 17 | } 18 | 19 | public void setUserID(String userID) { 20 | this.userID = userID; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/DestroyGroupReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | import org.codehaus.jackson.annotate.JsonIgnore; 4 | import org.codehaus.jackson.annotate.JsonProperty; 5 | 6 | public class DestroyGroupReq { 7 | @JsonProperty(value = "GroupId") 8 | private String GroupId = ""; 9 | 10 | @JsonIgnore 11 | public String getGroupId() { 12 | return GroupId; 13 | } 14 | 15 | public void setGroupId(String groupId) { 16 | GroupId = groupId; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/DestroyRoomReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class DestroyRoomReq { 4 | private String roomID = ""; 5 | private String userID = ""; 6 | 7 | public String getRoomID() { 8 | return roomID; 9 | } 10 | 11 | public void setRoomID(String roomID) { 12 | this.roomID = roomID; 13 | } 14 | 15 | public String getUserID() { 16 | return userID; 17 | } 18 | 19 | public void setUserID(String userID) { 20 | this.userID = userID; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/GetAudiencesReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class GetAudiencesReq { 4 | private String roomID = ""; 5 | 6 | public String getRoomID() { 7 | return roomID; 8 | } 9 | 10 | public void setRoomID(String roomID) { 11 | this.roomID = roomID; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/GetCustomInfoReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class GetCustomInfoReq { 4 | private String roomID = ""; 5 | 6 | public String getRoomID() { 7 | return roomID; 8 | } 9 | 10 | public void setRoomID(String roomID) { 11 | this.roomID = roomID; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/GetLoginInfoReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class GetLoginInfoReq { 4 | private String userID = ""; 5 | 6 | public String getUserID() { 7 | return userID; 8 | } 9 | 10 | public void setUserID(String userID) { 11 | this.userID = userID; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/GetPushUrlReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class GetPushUrlReq { 4 | private String roomID = ""; 5 | private String userID = ""; 6 | 7 | public String getRoomID() { 8 | return roomID; 9 | } 10 | 11 | public void setRoomID(String roomID) { 12 | this.roomID = roomID; 13 | } 14 | 15 | public String getUserID() { 16 | return userID; 17 | } 18 | 19 | public void setUserID(String userID) { 20 | this.userID = userID; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/GetPushersReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class GetPushersReq { 4 | private String roomID = ""; 5 | 6 | public String getRoomID() { 7 | return roomID; 8 | } 9 | 10 | public void setRoomID(String roomID) { 11 | this.roomID = roomID; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/GetRoomListReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class GetRoomListReq { 4 | private int cnt = 0; 5 | private int index = 0; 6 | 7 | public int getCnt() { 8 | return cnt; 9 | } 10 | 11 | public void setCnt(int cnt) { 12 | this.cnt = cnt; 13 | } 14 | 15 | public int getIndex() { 16 | return index; 17 | } 18 | 19 | public void setIndex(int index) { 20 | this.index = index; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/NotifyPusherChangeReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | import org.codehaus.jackson.annotate.JsonIgnore; 4 | import org.codehaus.jackson.annotate.JsonProperty; 5 | 6 | public class NotifyPusherChangeReq { 7 | @JsonProperty(value = "GroupId") 8 | private String GroupId = ""; 9 | @JsonProperty(value = "Content") 10 | private String Content = ""; 11 | 12 | @JsonIgnore 13 | public String getGroupId() { 14 | return GroupId; 15 | } 16 | 17 | public void setGroupId(String groupId) { 18 | GroupId = groupId; 19 | } 20 | 21 | @JsonIgnore 22 | public String getContent() { 23 | return Content; 24 | } 25 | 26 | public void setContent(String content) { 27 | this.Content = content; 28 | } 29 | 30 | public static class Content { 31 | private String cmd = ""; 32 | private String data = ""; 33 | 34 | public String getCmd() { 35 | return cmd; 36 | } 37 | 38 | public void setCmd(String cmd) { 39 | this.cmd = cmd; 40 | } 41 | 42 | public String getData() { 43 | return data; 44 | } 45 | 46 | public void setData(String data) { 47 | this.data = data; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/PusherHeartbeatReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class PusherHeartbeatReq { 4 | private String roomID = ""; 5 | private String userID = ""; 6 | 7 | public String getRoomID() { 8 | return roomID; 9 | } 10 | 11 | public void setRoomID(String roomID) { 12 | this.roomID = roomID; 13 | } 14 | 15 | public String getUserID() { 16 | return userID; 17 | } 18 | 19 | public void setUserID(String userID) { 20 | this.userID = userID; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Request/SetCustomInfoReq.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Request; 2 | 3 | public class SetCustomInfoReq { 4 | private String roomID = ""; 5 | private String fieldName = ""; 6 | private String operation = ""; 7 | 8 | public String getRoomID() { 9 | return roomID; 10 | } 11 | 12 | public void setRoomID(String roomID) { 13 | this.roomID = roomID; 14 | } 15 | 16 | public String getFieldName() { 17 | return fieldName; 18 | } 19 | 20 | public void setFieldName(String fieldName) { 21 | this.fieldName = fieldName; 22 | } 23 | 24 | public String getOperation() { 25 | return operation; 26 | } 27 | 28 | public void setOperation(String operation) { 29 | this.operation = operation; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/BaseRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | public class BaseRsp { 4 | private int code = 0; 5 | private String message = "请求成功"; 6 | 7 | public int getCode() { 8 | return code; 9 | } 10 | 11 | public void setCode(int code) { 12 | this.code = code; 13 | } 14 | 15 | public String getMessage() { 16 | return message; 17 | } 18 | 19 | public void setMessage(String message) { 20 | this.message = message; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/CreateRoomRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | public class CreateRoomRsp extends BaseRsp { 4 | private String roomID = ""; 5 | 6 | public String getRoomID() { 7 | return roomID; 8 | } 9 | 10 | public void setRoomID(String roomID) { 11 | this.roomID = roomID; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/GetAudiencesRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | import com.tencent.qcloud.roomservice.pojo.Audience; 4 | import org.codehaus.jackson.annotate.JsonIgnore; 5 | import org.codehaus.jackson.annotate.JsonProperty; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class GetAudiencesRsp extends BaseRsp { 10 | private int audienceCount = 0; 11 | @JsonProperty(value = "audiences") 12 | private ArrayList audiences = new ArrayList<>(); 13 | 14 | public int getAudienceCount() { 15 | return audienceCount; 16 | } 17 | 18 | @JsonIgnore 19 | public ArrayList getAudiences() { 20 | return audiences; 21 | } 22 | 23 | public void setAudiences(ArrayList audiences) { 24 | this.audiences = audiences; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/GetCustomInfoRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | public class GetCustomInfoRsp extends BaseRsp{ 4 | private String customInfo = ""; 5 | 6 | public String getCustomInfo() { 7 | return customInfo; 8 | } 9 | 10 | public void setCustomInfo(String customInfo) { 11 | this.customInfo = customInfo; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/GetLoginInfoRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | public class GetLoginInfoRsp extends BaseRsp { 4 | private long sdkAppID = 0; 5 | private String accType = ""; 6 | private String userID = ""; 7 | private String userSig = ""; 8 | 9 | public String getUserID() { 10 | return userID; 11 | } 12 | 13 | public void setUserID(String userID) { 14 | this.userID = userID; 15 | } 16 | 17 | public String getUserSig() { 18 | return userSig; 19 | } 20 | 21 | public void setUserSig(String userSig) { 22 | this.userSig = userSig; 23 | } 24 | 25 | public String getAccType() { 26 | return accType; 27 | } 28 | 29 | public void setAccType(String accType) { 30 | this.accType = accType; 31 | } 32 | 33 | public long getSdkAppID() { 34 | return sdkAppID; 35 | } 36 | 37 | public void setSdkAppID(long sdkAppID) { 38 | this.sdkAppID = sdkAppID; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/GetPushUrlRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | public class GetPushUrlRsp extends BaseRsp { 4 | private String pushURL = ""; 5 | 6 | public String getPushURL() { 7 | return pushURL; 8 | } 9 | 10 | public void setPushURL(String pushURL) { 11 | this.pushURL = pushURL; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/GetRoomListRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | import com.tencent.qcloud.roomservice.pojo.Room; 4 | import org.codehaus.jackson.annotate.JsonIgnore; 5 | import org.codehaus.jackson.annotate.JsonProperty; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class GetRoomListRsp extends BaseRsp { 10 | @JsonProperty(value = "rooms") 11 | private ArrayList rooms; 12 | 13 | @JsonIgnore 14 | public ArrayList getList() { 15 | return rooms; 16 | } 17 | 18 | public void setList(ArrayList list) { 19 | this.rooms = list; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/GetStreamStatusOutPut.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | public class GetStreamStatusOutPut { 4 | private int rate_type = 0; 5 | private int recv_type = 0; 6 | private int status = 0; 7 | private int height = 0; 8 | private int width = 0; 9 | 10 | public int getRate_type() { 11 | return rate_type; 12 | } 13 | 14 | public void setRate_type(int rate_type) { 15 | this.rate_type = rate_type; 16 | } 17 | 18 | public int getRecv_type() { 19 | return recv_type; 20 | } 21 | 22 | public void setRecv_type(int recv_type) { 23 | this.recv_type = recv_type; 24 | } 25 | 26 | public int getStatus() { 27 | return status; 28 | } 29 | 30 | public void setStatus(int status) { 31 | this.status = status; 32 | } 33 | 34 | public int getHeight() { 35 | return height; 36 | } 37 | 38 | public void setHeight(int height) { 39 | this.height = height; 40 | } 41 | 42 | public int getWidth() { 43 | return width; 44 | } 45 | 46 | public void setWidth(int width) { 47 | this.width = width; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/GetStreamStatusRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class GetStreamStatusRsp { 6 | public int getRet() { 7 | return ret; 8 | } 9 | 10 | public void setRet(int ret) { 11 | this.ret = ret; 12 | } 13 | 14 | public String getMessage() { 15 | return message; 16 | } 17 | 18 | public void setMessage(String message) { 19 | this.message = message; 20 | } 21 | 22 | public ArrayList getOutput() { 23 | return output; 24 | } 25 | 26 | public void setOutput(ArrayList output) { 27 | this.output = output; 28 | } 29 | 30 | public int getRetcode() { 31 | return retcode; 32 | } 33 | 34 | public void setRetcode(int retcode) { 35 | this.retcode = retcode; 36 | } 37 | 38 | public String getErrmsg() { 39 | return errmsg; 40 | } 41 | 42 | public void setErrmsg(String errmsg) { 43 | this.errmsg = errmsg; 44 | } 45 | 46 | 47 | private int ret = 0; 48 | private int retcode = 0; 49 | private String errmsg = ""; 50 | private String message = ""; 51 | private ArrayList output = new ArrayList<>(); 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/GetTestPushUrlRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | public class GetTestPushUrlRsp { 4 | private String url_push = ""; 5 | private String url_play_flv = ""; 6 | private String url_play_rtmp = ""; 7 | private String url_play_hls = ""; 8 | private String url_play_acc = ""; 9 | 10 | public String getUrl_push() { 11 | return url_push; 12 | } 13 | 14 | public void setUrl_push(String url_push) { 15 | this.url_push = url_push; 16 | } 17 | 18 | public String getUrl_play_flv() { 19 | return url_play_flv; 20 | } 21 | 22 | public void setUrl_play_flv(String url_play_flv) { 23 | this.url_play_flv = url_play_flv; 24 | } 25 | 26 | public String getUrl_play_rtmp() { 27 | return url_play_rtmp; 28 | } 29 | 30 | public void setUrl_play_rtmp(String url_play_rtmp) { 31 | this.url_play_rtmp = url_play_rtmp; 32 | } 33 | 34 | public String getUrl_play_hls() { 35 | return url_play_hls; 36 | } 37 | 38 | public void setUrl_play_hls(String url_play_hls) { 39 | this.url_play_hls = url_play_hls; 40 | } 41 | 42 | public String getUrl_play_acc() { 43 | return url_play_acc; 44 | } 45 | 46 | public void setUrl_play_acc(String url_play_acc) { 47 | this.url_play_acc = url_play_acc; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/GetTestRtmpAccUrlRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | public class GetTestRtmpAccUrlRsp { 4 | private String url_rtmpacc = ""; 5 | 6 | public String getUrl_rtmpacc() { 7 | return url_rtmpacc; 8 | } 9 | 10 | public void setUrl_rtmpacc(String url_rtmpacc) { 11 | this.url_rtmpacc = url_rtmpacc; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/LoginRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | public class LoginRsp extends BaseRsp { 4 | private String userID = ""; 5 | private String token = ""; 6 | 7 | public String getUserID() { 8 | return userID; 9 | } 10 | 11 | public void setUserID(String userID) { 12 | this.userID = userID; 13 | } 14 | 15 | public String getToken() { 16 | return token; 17 | } 18 | 19 | public void setToken(String token) { 20 | this.token = token; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Response/MergeStreamRsp.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo.Response; 2 | 3 | public class MergeStreamRsp extends BaseRsp { 4 | private Result result; 5 | 6 | public Result getResult() { 7 | return result; 8 | } 9 | 10 | public void setResult(Result result) { 11 | this.result = result; 12 | } 13 | 14 | public static class Result { 15 | private int code = 0; 16 | private String message = ""; 17 | private long timestamp = 0; 18 | 19 | public int getCode() { 20 | return code; 21 | } 22 | 23 | public void setCode(int code) { 24 | this.code = code; 25 | } 26 | 27 | public String getMessage() { 28 | return message; 29 | } 30 | 31 | public void setMessage(String message) { 32 | this.message = message; 33 | } 34 | 35 | public long getTimestamp() { 36 | return timestamp; 37 | } 38 | 39 | public void setTimestamp(long timestamp) { 40 | this.timestamp = timestamp; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/Room.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo; 2 | 3 | import com.tencent.qcloud.roomservice.pojo.Response.BaseRsp; 4 | import org.codehaus.jackson.annotate.JsonIgnore; 5 | import org.codehaus.jackson.annotate.JsonProperty; 6 | import org.codehaus.jackson.map.ObjectMapper; 7 | 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | /** 13 | * 房间信息 14 | */ 15 | public class Room extends BaseRsp { 16 | private String roomID = ""; 17 | private String roomInfo = ""; 18 | private String roomCreator = ""; 19 | private String mixedPlayURL = ""; 20 | private ConcurrentHashMap custom = new ConcurrentHashMap<>(); 21 | private boolean actived = false; 22 | 23 | @JsonProperty(value = "pushers") 24 | private ArrayList pushers = new ArrayList<>(); 25 | private ConcurrentHashMap pusherMap = new ConcurrentHashMap<>(); 26 | private ConcurrentHashMap audienceMap = new ConcurrentHashMap<>(); 27 | private long timestamp = 0; 28 | 29 | public String getRoomID() { 30 | return roomID; 31 | } 32 | 33 | public void setRoomID(String roomID) { 34 | this.roomID = roomID; 35 | } 36 | 37 | public String getRoomInfo() { 38 | return roomInfo; 39 | } 40 | 41 | public void setRoomInfo(String roomInfo) { 42 | this.roomInfo = roomInfo; 43 | } 44 | 45 | public String getRoomCreator() { 46 | return roomCreator; 47 | } 48 | 49 | public void setRoomCreator(String roomCreator) { 50 | this.roomCreator = roomCreator; 51 | } 52 | 53 | public String getMixedPlayURL() { 54 | return mixedPlayURL; 55 | } 56 | 57 | public void setMixedPlayURL(String mixedPlayURL) { 58 | this.mixedPlayURL = mixedPlayURL; 59 | } 60 | 61 | public Pusher getPusher(String userID) { 62 | return pusherMap.get(userID); 63 | } 64 | 65 | public void addPusher(Pusher pusher) { 66 | this.pusherMap.put(pusher.getUserID(), pusher); 67 | if (!this.pushers.contains(pusher)) { 68 | this.pushers.add(pusher); 69 | } 70 | } 71 | 72 | public void delPusher(String userID) { 73 | this.pushers.remove(this.pusherMap.get(userID)); 74 | this.pusherMap.remove(userID); 75 | } 76 | 77 | @JsonIgnore 78 | public boolean isActived() { 79 | return actived; 80 | } 81 | 82 | public void setActived(boolean actived) { 83 | this.actived = actived; 84 | } 85 | 86 | @JsonIgnore 87 | public String getCustomInfo() { 88 | String json = "{}"; 89 | if (custom.size() > 0) { 90 | ObjectMapper mapper = new ObjectMapper(); 91 | try { 92 | json = mapper.writeValueAsString(custom); 93 | } catch (IOException e) { 94 | e.printStackTrace(); 95 | } 96 | return json; 97 | } else { 98 | return "{}"; 99 | } 100 | } 101 | 102 | public void setCustomInfo(String fieldName, String op) { 103 | if (!custom.containsKey(fieldName)) { 104 | custom.put(fieldName, 0); 105 | } 106 | int value = custom.get(fieldName); 107 | if (op.equals("inc")) { 108 | custom.put(fieldName, ++value); 109 | } else if (op.equals("dec")) { 110 | custom.put(fieldName, --value); 111 | } 112 | } 113 | 114 | @JsonIgnore 115 | public long getTimestamp() { 116 | return timestamp; 117 | } 118 | 119 | 120 | public void setTimestamp(long timestamp) { 121 | this.timestamp = timestamp; 122 | } 123 | 124 | @JsonIgnore 125 | public int getPushersCnt() { 126 | return pusherMap.size(); 127 | } 128 | 129 | @JsonIgnore 130 | public ConcurrentHashMap getPushersMap() { 131 | return pusherMap; 132 | } 133 | 134 | @JsonIgnore 135 | public int getAudiencesCnt() { 136 | return audienceMap.size(); 137 | } 138 | 139 | @JsonIgnore 140 | public Audience getAudience(String userID) { 141 | return audienceMap.get(userID); 142 | } 143 | 144 | @JsonIgnore 145 | public ArrayList getAudiences() { 146 | ArrayList audiences = new ArrayList<>(); 147 | for(Audience value : audienceMap.values()) { 148 | audiences.add(value); 149 | } 150 | return audiences; 151 | } 152 | 153 | public void addAudience(Audience audience) { 154 | this.audienceMap.put(audience.getUserID(), audience); 155 | } 156 | 157 | public void delAudience(String userID) { 158 | this.audienceMap.remove(userID); 159 | } 160 | } -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/pojo/StreamIDS.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.pojo; 2 | 3 | /** 4 | * 流ID,完整的(推流状态检查) + 去掉bizid前缀的(生成对应的播放地址) 5 | */ 6 | public class StreamIDS { 7 | private String streamID = ""; 8 | private String subID = ""; 9 | 10 | public String getStreamID() { 11 | return streamID; 12 | } 13 | 14 | public void setStreamID(String streamID) { 15 | this.streamID = streamID; 16 | } 17 | 18 | public String getSubID() { 19 | return subID; 20 | } 21 | 22 | public void setSubID(String subID) { 23 | this.subID = subID; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/service/RoomService.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.service; 2 | 3 | import com.tencent.qcloud.roomservice.pojo.Request.*; 4 | import com.tencent.qcloud.roomservice.pojo.Response.*; 5 | import com.tencent.qcloud.roomservice.pojo.Room; 6 | 7 | import java.util.Map; 8 | 9 | public interface RoomService { 10 | LoginRsp login(String sdkAppID, String accountType, String userID, String userSig); 11 | 12 | BaseRsp logout(String userID, String token); 13 | 14 | GetRoomListRsp getRoomList(String userID, String token, GetRoomListReq req, int type); 15 | 16 | Room getPushers(String userID, String token, GetPushersReq req, int type); 17 | 18 | CreateRoomRsp createRoom(String userID, String token, CreateRoomReq req, int type); 19 | 20 | BaseRsp destroyRoom(String userID, String token, DestroyRoomReq req, int type); 21 | 22 | BaseRsp addPusher(String userID, String token, AddPusherReq req, int type); 23 | 24 | BaseRsp deletePusher(String userID, String token, DeletePusherReq req, int type); 25 | 26 | void test(); 27 | 28 | BaseRsp pusherHeartbeat(String userID, String token, PusherHeartbeatReq req, int type); 29 | 30 | GetCustomInfoRsp getCustomInfo(String userID, String token, GetCustomInfoReq req, int type); 31 | 32 | GetCustomInfoRsp setCustomInfo(String userID, String token, SetCustomInfoReq req, int type); 33 | 34 | BaseRsp addAudience(String userID, String token, AddAudienceReq req, int type); 35 | 36 | BaseRsp delAudience(String userID, String token, DelAudienceReq req, int type); 37 | 38 | GetAudiencesRsp getAudiences(String userID, String token, GetAudiencesReq req, int type); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/service/UtilService.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.service; 2 | 3 | import com.tencent.qcloud.roomservice.pojo.Request.AddAudienceReq; 4 | import com.tencent.qcloud.roomservice.pojo.Request.GetCustomInfoReq; 5 | import com.tencent.qcloud.roomservice.pojo.Request.GetPushUrlReq; 6 | import com.tencent.qcloud.roomservice.pojo.Request.SetCustomInfoReq; 7 | import com.tencent.qcloud.roomservice.pojo.Response.*; 8 | 9 | import java.util.Map; 10 | 11 | public interface UtilService { 12 | GetPushUrlRsp getPushUrl(String userID, String token); 13 | 14 | GetPushUrlRsp getPushUrl(String userID, String token, GetPushUrlReq req); 15 | 16 | MergeStreamRsp mergeStream(String userID, String token, Map map); 17 | 18 | GetTestPushUrlRsp getTestPushUrl(); 19 | 20 | GetTestRtmpAccUrlRsp getTestRtmpAccUrl(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/service/impl/RoomServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.service.impl; 2 | 3 | import com.tencent.qcloud.roomservice.common.Config; 4 | import com.tencent.qcloud.roomservice.logic.IMMgr; 5 | import com.tencent.qcloud.roomservice.logic.LiveUtil; 6 | import com.tencent.qcloud.roomservice.logic.RoomMgr; 7 | import com.tencent.qcloud.roomservice.pojo.Request.*; 8 | import com.tencent.qcloud.roomservice.pojo.Response.*; 9 | import com.tencent.qcloud.roomservice.pojo.Room; 10 | import com.tencent.qcloud.roomservice.pojo.StreamIDS; 11 | import com.tencent.qcloud.roomservice.service.RoomService; 12 | import com.tencent.qcloud.roomservice.utils.Utils; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.stereotype.Service; 16 | 17 | import javax.annotation.Resource; 18 | 19 | @Service 20 | public class RoomServiceImpl implements RoomService { 21 | @Resource 22 | RoomMgr roomMgr; 23 | 24 | @Resource 25 | IMMgr imMgr; 26 | 27 | @Resource 28 | LiveUtil liveUtil; 29 | 30 | private static Logger log= LoggerFactory.getLogger(RoomServiceImpl.class); 31 | 32 | 33 | @Override 34 | public LoginRsp login(String sdkAppID, String accountType, String userID, String userSig) { 35 | return imMgr.verifySig(sdkAppID, accountType, userID, userSig); 36 | } 37 | 38 | @Override 39 | public BaseRsp logout(String userID, String token) { 40 | BaseRsp rsp = new BaseRsp(); 41 | if (userID == null || token == null) { 42 | rsp.setCode(2); 43 | rsp.setMessage("请求失败,缺少参数"); 44 | log.error("logout失败:缺少参数:" + "userID:" + userID + ",token: " + token); 45 | return rsp; 46 | } 47 | if (imMgr.validation(userID, token) != 0) { 48 | rsp.setCode(7); 49 | rsp.setMessage("请求失败,鉴权失败"); 50 | log.error("logout失败:鉴权失败:" + "userID:" + userID); 51 | return rsp; 52 | 53 | } 54 | 55 | //删除session 56 | imMgr.delLoginSession(userID); 57 | return rsp; 58 | } 59 | 60 | @Override 61 | public GetRoomListRsp getRoomList(String userID, String token, GetRoomListReq req, int type) { 62 | GetRoomListRsp rsp = new GetRoomListRsp(); 63 | if (userID == null || token == null) { 64 | rsp.setCode(2); 65 | rsp.setMessage("请求失败,缺少参数"); 66 | log.error("getRoomList失败:缺少参数:" + "userID:" + userID + ",token: " + token + ",type: " + type); 67 | return rsp; 68 | } 69 | if (imMgr.validation(userID, token) != 0) { 70 | rsp.setCode(7); 71 | rsp.setMessage("请求失败,鉴权失败"); 72 | log.error("getRoomList失败:鉴权失败:" + "userID:" + userID); 73 | return rsp; 74 | } 75 | 76 | rsp.setList(roomMgr.getList(req.getCnt(), req.getIndex(), true, type)); 77 | return rsp; 78 | } 79 | 80 | @Override 81 | public Room getPushers(String userID, String token, GetPushersReq req, int type) { 82 | Room rsp = new Room(); 83 | if (userID == null || token == null) { 84 | rsp.setCode(2); 85 | rsp.setMessage("请求失败,缺少参数"); 86 | log.error("getPushers失败:缺少参数:" + "userID:" + userID + ",token: " + token + ",type: " + type); 87 | return rsp; 88 | } 89 | if (imMgr.validation(userID, token) != 0) { 90 | rsp.setCode(7); 91 | rsp.setMessage("请求失败,鉴权失败"); 92 | log.error("getPushers失败:鉴权失败:" + "userID:" + userID); 93 | return rsp; 94 | } 95 | 96 | rsp = roomMgr.getRoom(req.getRoomID(), type); 97 | log.info("getRoom, type: " + type + " , roomID: " + req.getRoomID() + ", pushers counts : " + (rsp == null? 0 : rsp.getPushersCnt())); 98 | if (rsp == null) { 99 | rsp = new Room(); 100 | rsp.setCode(1); 101 | rsp.setMessage("请求失败,房间不存在"); 102 | log.error("getPushers失败:房间不存在:" + "userID:" + userID + ",roomID: " + req.getRoomID() + ",type: " + type); 103 | } 104 | 105 | return rsp; 106 | } 107 | 108 | @Override 109 | public CreateRoomRsp createRoom(String userID, String token, CreateRoomReq req, int type) { 110 | CreateRoomRsp rsp = new CreateRoomRsp(); 111 | if (userID == null || token == null || req.getUserID() == null || req.getRoomInfo() == null) { 112 | rsp.setCode(2); 113 | rsp.setMessage("请求失败,缺少参数"); 114 | log.error("createRoom失败:缺少参数:" + "userID:" + userID + ",token: " + token + ",roomInfo:" + req.getRoomInfo() + ",type: " + type); 115 | return rsp; 116 | } 117 | 118 | if (req.getRoomInfo().length() > 1024) { 119 | rsp.setCode(11); 120 | rsp.setMessage("roomInfo 字符串长度不能超过1024字节"); 121 | log.error("createRoom失败:roomInfo 字符串长度不能超过1024字节:" + "userID:" + userID + ",roomInfo:" + req.getRoomInfo()); 122 | return rsp; 123 | } 124 | 125 | if (imMgr.validation(userID, token) != 0) { 126 | rsp.setCode(7); 127 | rsp.setMessage("请求失败,鉴权失败"); 128 | log.error("createRoom失败:鉴权失败:" + "userID:" + userID); 129 | return rsp; 130 | } 131 | 132 | String roomID = req.getRoomID(); 133 | if (roomID == null || roomID.length() == 0) { 134 | roomID = Utils.genRoomIdByRandom(); 135 | } else { 136 | if (roomMgr.isRoomExist(roomID, type)) { 137 | rsp.setCode(11); 138 | rsp.setMessage("房间已经存在"); 139 | return rsp; 140 | } 141 | } 142 | // 获取一个可用的roomid 143 | while (roomMgr.isRoomExist(roomID, type)) { 144 | roomID = Utils.genRoomIdByRandom(); 145 | } 146 | 147 | // 先创建im群 148 | if (!imMgr.createGroup(roomID)) { 149 | rsp.setCode(6); 150 | rsp.setMessage("群组创建失败"); 151 | log.error("createRoom失败:群组创建失败:" + "userID:" + userID + ", roomID: " + roomID); 152 | return rsp; 153 | } 154 | 155 | // 再创建房间 156 | roomMgr.creatRoom(roomID, req.getRoomInfo(), type); 157 | rsp.setRoomID(roomID); 158 | 159 | return rsp; 160 | } 161 | 162 | @Override 163 | public BaseRsp destroyRoom(String userID, String token, DestroyRoomReq req, int type) { 164 | BaseRsp rsp = new BaseRsp(); 165 | if (userID == null || token == null || req.getUserID() == null || req.getRoomID() == null) { 166 | rsp.setCode(2); 167 | rsp.setMessage("请求失败,缺少参数"); 168 | log.error("destroyRoom失败:缺少参数:" + "userID:" + userID + ",token: " + token + ",roomID:" + req.getRoomID() + ",type: " + type); 169 | return rsp; 170 | } 171 | if (imMgr.validation(userID, token) != 0) { 172 | rsp.setCode(7); 173 | rsp.setMessage("请求失败,鉴权失败"); 174 | log.error("destroyRoom失败:鉴权失败:" + "userID:" + userID + ",roomID:" + req.getRoomID()); 175 | return rsp; 176 | } 177 | 178 | if (!roomMgr.isRoomCreator(req.getRoomID(), req.getUserID(), type)) { 179 | rsp.setCode(3); 180 | rsp.setMessage("不是房间主人,无法销毁房间"); 181 | log.error("destroyRoom失败:不是房间主人,无法销毁房间:" + ",userID:" + userID + ",roomID:" + req.getRoomID()); 182 | return rsp; 183 | } 184 | 185 | roomMgr.delRoom(req.getRoomID(), type); 186 | imMgr.destroyGroup(req.getRoomID()); 187 | 188 | return rsp; 189 | } 190 | 191 | @Override 192 | public BaseRsp addPusher(String userID, String token, AddPusherReq req, int type) { 193 | CreateRoomRsp rsp = new CreateRoomRsp(); 194 | String roomID = req.getRoomID(); 195 | String userName = req.getUserName(); 196 | String userAvatar = req.getUserAvatar(); 197 | String pushURL = req.getPushURL(); 198 | if (userID == null 199 | || token == null 200 | || roomID == null 201 | || req.getUserID() == null 202 | || userName == null 203 | || userAvatar == null 204 | || pushURL == null) { 205 | rsp.setCode(2); 206 | rsp.setMessage("请求失败,缺少参数"); 207 | log.error("addPusher失败:缺少参数:" + "userID:" + userID + ",token: " + token + ", roomID:" + roomID + ", userName:" + userName + ", userAvatar:" + userAvatar + ", pushURL:" + pushURL + ",type: " + type); 208 | return rsp; 209 | } 210 | 211 | if (imMgr.validation(userID, token) != 0) { 212 | rsp.setCode(7); 213 | rsp.setMessage("请求失败,鉴权失败"); 214 | log.error("addPusher失败:鉴权失败:" + "userID:" + userID); 215 | return rsp; 216 | } 217 | 218 | // 如果房间不存在,先建房 219 | if (!roomMgr.isRoomExist(roomID, type)) { 220 | log.info("addPusher群组不存在,创建一下:" + "userID:" + userID + ", roomID:" + roomID); 221 | // 先创建im群 222 | if (!imMgr.createGroup(roomID)) { 223 | rsp.setCode(6); 224 | rsp.setMessage("群组创建失败"); 225 | log.error("addPusher群组创建失败:" + "userID:" + userID + ", roomID:" + roomID); 226 | return rsp; 227 | } 228 | 229 | String roomInfo = ""; 230 | if (req.getRoomInfo() != null) { 231 | roomInfo = req.getRoomInfo(); 232 | } 233 | 234 | // 再创建房间 235 | roomMgr.creatRoom(userID, roomInfo, type); 236 | 237 | // 进房 238 | StreamIDS ids = liveUtil.getStreamIdFromPushUrl(pushURL); 239 | roomMgr.addRoom(roomID, 240 | "", 241 | userID, 242 | liveUtil.genMixedPlayUrl(ids.getSubID(), "flv"), 243 | userName, 244 | userAvatar, 245 | pushURL, 246 | ids.getStreamID(), 247 | liveUtil.genAcceleratePlayUrl(ids.getSubID()), 248 | type); 249 | rsp.setRoomID(roomID); 250 | return rsp; 251 | } 252 | 253 | // 房间已经存在,直接进房,或更新成员信息。 254 | StreamIDS ids = liveUtil.getStreamIdFromPushUrl(pushURL); 255 | if (roomMgr.isMember(roomID, userID, type)) { 256 | roomMgr.updateMember(roomID, 257 | userID, 258 | liveUtil.genMixedPlayUrl(ids.getSubID(), "flv"), 259 | userName, 260 | userAvatar, 261 | pushURL, 262 | ids.getStreamID(), 263 | liveUtil.genAcceleratePlayUrl(ids.getSubID()), 264 | type); 265 | imMgr.notifyPushersChange(roomID); 266 | return rsp; 267 | } 268 | 269 | int maxMembers = 0; 270 | if (type == RoomMgr.DOUBLE_ROOM) { 271 | maxMembers = 2; 272 | } else if (type == RoomMgr.MULTI_ROOM) { 273 | maxMembers = Config.MultiRoom.maxMembers; 274 | } else if (type == RoomMgr.LIVE_ROOM) { 275 | maxMembers = Config.LiveRoom.maxMembers; 276 | } 277 | if (roomMgr.getMemberCnt(roomID, type) >= maxMembers) { 278 | rsp.setCode(5001); 279 | rsp.setMessage("超出房间人数上限"); 280 | log.error("addPusher超出房间人数上限:" + "userID:" + userID + ", roomID:" + roomID); 281 | return rsp; 282 | } 283 | 284 | roomMgr.addMember(roomID, 285 | userID, 286 | liveUtil.genMixedPlayUrl(ids.getSubID(), "flv"), 287 | userName, 288 | userAvatar, 289 | pushURL, 290 | ids.getStreamID(), 291 | liveUtil.genAcceleratePlayUrl(ids.getSubID()), 292 | type); 293 | imMgr.notifyPushersChange(roomID); 294 | return rsp; 295 | } 296 | 297 | @Override 298 | public BaseRsp deletePusher(String userID, String token, DeletePusherReq req, int type) { 299 | BaseRsp rsp = new BaseRsp(); 300 | String roomID = req.getRoomID(); 301 | if (userID == null 302 | || token == null 303 | || roomID == null 304 | || req.getUserID() == null) { 305 | rsp.setCode(2); 306 | rsp.setMessage("请求失败,缺少参数"); 307 | log.error("deletePusher失败:缺少参数:" + "userID:" + userID + ",token: " + token + ", roomID:" + roomID + ",type: " + type); 308 | return rsp; 309 | } 310 | 311 | if (imMgr.validation(userID, token) != 0) { 312 | rsp.setCode(7); 313 | rsp.setMessage("请求失败,鉴权失败"); 314 | log.error("deletePusher失败:鉴权失败:" + "userID:" + userID); 315 | return rsp; 316 | } 317 | 318 | if (!roomMgr.isMember(roomID, userID, type)) { 319 | rsp.setCode(5); 320 | rsp.setMessage("退出房间失败,不是房间成员"); 321 | log.error("deletePusher失败:不是房间成员:" + "userID:" + userID + ", roomID:" + roomID + ",type: " + type); 322 | return rsp; 323 | } 324 | 325 | if (type != RoomMgr.LIVE_ROOM && Config.isCreatorDestroyRoom && roomMgr.isRoomCreator(roomID, userID, type)) { 326 | roomMgr.delRoom(roomID, type); 327 | imMgr.destroyGroup(roomID); 328 | } else if(roomMgr.isMember(roomID, userID, type)) { 329 | roomMgr.delPusher(roomID, userID, type); 330 | // notify 331 | imMgr.notifyPushersChange(roomID); 332 | 333 | 334 | if (roomMgr.getMemberCnt(roomID, type) == 0) { 335 | roomMgr.delRoom(roomID, type); 336 | imMgr.destroyGroup(roomID); 337 | } 338 | } 339 | 340 | return rsp; 341 | } 342 | 343 | @Override 344 | public void test() { 345 | String roomID = Utils.genRoomIdByRandom(); 346 | } 347 | 348 | @Override 349 | public BaseRsp pusherHeartbeat(String userID, String token, PusherHeartbeatReq req, int type) { 350 | BaseRsp rsp = new BaseRsp(); 351 | String roomID = req.getRoomID(); 352 | if (userID == null 353 | || token == null 354 | || roomID == null 355 | || req.getUserID() == null) { 356 | rsp.setCode(2); 357 | rsp.setMessage("请求失败,缺少参数"); 358 | log.error("pusherHeartbeat失败:缺少参数:" + "userID:" + userID + ",token: " + token + ", roomID:" + roomID + ",type: " + type); 359 | return rsp; 360 | } 361 | 362 | if (imMgr.validation(userID, token) != 0) { 363 | rsp.setCode(7); 364 | rsp.setMessage("请求失败,鉴权失败"); 365 | log.error("pusherHeartbeat失败:鉴权失败:" + "userID:" + userID); 366 | return rsp; 367 | } 368 | 369 | if (!roomMgr.isMember(roomID, userID, type)) { 370 | rsp.setCode(5); 371 | rsp.setMessage("请求失败,不是房间成员"); 372 | log.error("pusherHeartbeat失败:不是房间成员:" + "userID:" + userID + ", roomID:" + roomID + ",type: " + type); 373 | return rsp; 374 | } 375 | 376 | roomMgr.updateMemberTS(roomID, userID, type); 377 | 378 | return rsp; 379 | } 380 | 381 | @Override 382 | public GetCustomInfoRsp getCustomInfo(String userID, String token, GetCustomInfoReq req, int type) { 383 | GetCustomInfoRsp rsp = new GetCustomInfoRsp(); 384 | String roomID = req.getRoomID(); 385 | if (userID == null || token == null || roomID == null) { 386 | rsp.setCode(2); 387 | rsp.setMessage("请求失败,缺少参数"); 388 | log.error("getCustomInfo失败:缺少参数:" + "userID:" + userID + ",token: " + token + ", roomID:" + roomID + ",type: " + type); 389 | return rsp; 390 | } 391 | 392 | if (imMgr.validation(userID, token) != 0) { 393 | rsp.setCode(7); 394 | rsp.setMessage("请求失败,鉴权失败"); 395 | log.error("getCustomInfo失败:鉴权失败:" + "userID:" + userID); 396 | return rsp; 397 | 398 | } 399 | 400 | if (!roomMgr.isRoomExist(roomID, type)) { 401 | rsp.setCode(3); 402 | rsp.setMessage("请求失败,房间不存在"); 403 | log.error("getCustomInfo失败:不是房间成员:" + "userID:" + userID + ", roomID:" + roomID + ",type: " + type); 404 | return rsp; 405 | } 406 | 407 | rsp.setCustomInfo(roomMgr.getCustomInfo(roomID, type)); 408 | 409 | return rsp; 410 | } 411 | 412 | @Override 413 | public GetCustomInfoRsp setCustomInfo(String userID, String token, SetCustomInfoReq req, int type) { 414 | GetCustomInfoRsp rsp = new GetCustomInfoRsp(); 415 | String roomID = req.getRoomID(); 416 | String fieldName = req.getFieldName(); 417 | String operation = req.getOperation(); 418 | if (userID == null || token == null || roomID == null || fieldName == null || operation == null) { 419 | rsp.setCode(2); 420 | rsp.setMessage("请求失败,缺少参数"); 421 | log.error("setCustomInfo失败:缺少参数:" + "userID:" + userID + ",token: " + token + ", roomID:" + roomID + ", fieldName:" + fieldName + ", operation:" + operation + ",type: " + type); 422 | return rsp; 423 | } 424 | 425 | if (imMgr.validation(userID, token) != 0) { 426 | rsp.setCode(7); 427 | rsp.setMessage("请求失败,鉴权失败"); 428 | log.error("setCustomInfo失败:鉴权失败:" + "userID:" + userID); 429 | return rsp; 430 | 431 | } 432 | 433 | if (!roomMgr.isRoomExist(roomID, type)) { 434 | rsp.setCode(3); 435 | rsp.setMessage("请求失败,房间不存在"); 436 | log.error("setCustomInfo失败:房间成员不存在:" + "userID:" + userID + ", roomID:" + roomID + ",type: " + type); 437 | return rsp; 438 | } 439 | 440 | rsp.setCustomInfo(roomMgr.setCustomInfo(roomID, fieldName, operation, type)); 441 | 442 | return null; 443 | } 444 | 445 | @Override 446 | public BaseRsp addAudience(String userID, String token, AddAudienceReq req, int type) { 447 | BaseRsp rsp = new BaseRsp(); 448 | String roomID = req.getRoomID(); 449 | String userInfo = req.getUserInfo(); 450 | if (userID == null || token == null || roomID == null || userInfo == null) { 451 | rsp.setCode(2); 452 | rsp.setMessage("请求失败,缺少参数"); 453 | log.error("addAudience失败:缺少参数:" + "userID:" + userID + ",token: " + token + ", roomID:" + roomID + ", userInfo:" + userInfo + ",type: " + type); 454 | return rsp; 455 | } 456 | 457 | if (imMgr.validation(userID, token) != 0) { 458 | rsp.setCode(7); 459 | rsp.setMessage("请求失败,鉴权失败"); 460 | log.error("addAudience失败:鉴权失败:" + "userID:" + userID); 461 | return rsp; 462 | 463 | } 464 | 465 | if (roomMgr.getAudienceCnt(roomID, RoomMgr.LIVE_ROOM) + 1 > Config.LiveRoom.maxAudiencesLen ) { 466 | rsp.setCode(5001); 467 | rsp.setMessage("观众列表满了"); 468 | log.error("addAudience失败:观众列表满了:" + "userID:" + userID + ", roomID:" + roomID ); 469 | return rsp; 470 | } 471 | 472 | roomMgr.addAudience(roomID, userID, userInfo, type); 473 | return rsp; 474 | } 475 | 476 | @Override 477 | public BaseRsp delAudience(String userID, String token, DelAudienceReq req, int type) { 478 | BaseRsp rsp = new BaseRsp(); 479 | String roomID = req.getRoomID(); 480 | if (userID == null || token == null || roomID == null) { 481 | rsp.setCode(2); 482 | rsp.setMessage("请求失败,缺少参数"); 483 | log.error("delAudience失败:缺少参数:" + "userID:" + userID + ",token: " + token + ", roomID:" + roomID + ",type: " + type); 484 | return rsp; 485 | } 486 | 487 | if (imMgr.validation(userID, token) != 0) { 488 | rsp.setCode(7); 489 | rsp.setMessage("请求失败,鉴权失败"); 490 | log.error("delAudience失败:鉴权失败:" + "userID:" + userID); 491 | return rsp; 492 | 493 | } 494 | 495 | roomMgr.delAudience(roomID, userID, type); 496 | return rsp; 497 | } 498 | 499 | @Override 500 | public GetAudiencesRsp getAudiences(String userID, String token, GetAudiencesReq req, int type) { 501 | GetAudiencesRsp rsp = new GetAudiencesRsp(); 502 | String roomID = req.getRoomID(); 503 | if (userID == null || token == null || roomID == null) { 504 | rsp.setCode(2); 505 | rsp.setMessage("请求失败,缺少参数"); 506 | log.error("getAudiences失败:缺少参数:" + "userID:" + userID + ",token: " + token + ", roomID:" + roomID + ",type: " + type); 507 | return rsp; 508 | } 509 | 510 | if (imMgr.validation(userID, token) != 0) { 511 | rsp.setCode(7); 512 | rsp.setMessage("请求失败,鉴权失败"); 513 | log.error("getAudiences失败:鉴权失败:" + "userID:" + userID); 514 | return rsp; 515 | 516 | } 517 | rsp.setAudiences(roomMgr.getAudiences(roomID, type)); 518 | return rsp; 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/service/impl/UtilServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.service.impl; 2 | 3 | import com.tencent.qcloud.roomservice.logic.IMMgr; 4 | import com.tencent.qcloud.roomservice.logic.LiveUtil; 5 | import com.tencent.qcloud.roomservice.logic.RoomMgr; 6 | import com.tencent.qcloud.roomservice.pojo.Request.GetPushUrlReq; 7 | import com.tencent.qcloud.roomservice.pojo.Response.GetPushUrlRsp; 8 | import com.tencent.qcloud.roomservice.pojo.Response.GetTestPushUrlRsp; 9 | import com.tencent.qcloud.roomservice.pojo.Response.GetTestRtmpAccUrlRsp; 10 | import com.tencent.qcloud.roomservice.pojo.Response.MergeStreamRsp; 11 | import com.tencent.qcloud.roomservice.service.UtilService; 12 | import com.tencent.qcloud.roomservice.utils.Utils; 13 | import org.json.JSONObject; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.stereotype.Service; 17 | 18 | import javax.annotation.Resource; 19 | import java.util.Map; 20 | 21 | @Service 22 | public class UtilServiceImpl implements UtilService { 23 | @Resource 24 | IMMgr imMgr; 25 | 26 | @Resource 27 | LiveUtil liveUtil; 28 | 29 | @Resource 30 | RoomMgr roomMgr; 31 | 32 | private static Logger log = LoggerFactory.getLogger(UtilServiceImpl.class); 33 | 34 | @Override 35 | public GetPushUrlRsp getPushUrl(String userID, String token) { 36 | GetPushUrlRsp rsp = new GetPushUrlRsp(); 37 | if (userID == null || token == null) { 38 | rsp.setCode(2); 39 | rsp.setMessage("请求失败,缺少参数"); 40 | log.error("getPushUrl失败:缺少参数:" + "userID:" + userID + ",token: " + token); 41 | return rsp; 42 | } 43 | 44 | if (imMgr.validation(userID, token) != 0) { 45 | rsp.setCode(7); 46 | rsp.setMessage("请求失败,鉴权失败"); 47 | log.error("getPushUrl失败:鉴权失败:" + "userID:" + userID); 48 | return rsp; 49 | } 50 | 51 | rsp.setPushURL(liveUtil.genPushUrl(userID)); 52 | return rsp; 53 | } 54 | 55 | @Override 56 | public GetPushUrlRsp getPushUrl(String userID, String token, GetPushUrlReq req) { 57 | GetPushUrlRsp rsp = new GetPushUrlRsp(); 58 | String roomID = req.getRoomID(); 59 | if (userID == null || token == null) { 60 | rsp.setCode(2); 61 | rsp.setMessage("请求失败,缺少参数"); 62 | log.error("getPushUrl失败:缺少参数:" + "userID:" + userID + ",token: " + token); 63 | return rsp; 64 | } 65 | 66 | if (imMgr.validation(userID, token) != 0) { 67 | rsp.setCode(7); 68 | rsp.setMessage("请求失败,鉴权失败"); 69 | log.error("getPushUrl失败:鉴权失败:" + "userID:" + userID); 70 | return rsp; 71 | } 72 | 73 | if (roomID != null && roomID.length() > 0) { 74 | userID = roomID + "_" + userID; 75 | } 76 | rsp.setPushURL(liveUtil.genPushUrl(userID)); 77 | return rsp; 78 | } 79 | 80 | @Override 81 | public MergeStreamRsp mergeStream(String userID, String token, Map map) { 82 | MergeStreamRsp rsp = new MergeStreamRsp(); 83 | if (userID == null || token == null || map == null || map.get("roomID") == null || map.get("mergeParams") == null) { 84 | rsp.setCode(2); 85 | rsp.setMessage("请求失败,缺少参数"); 86 | log.error("mergeStream失败:缺少参数:" + "userID:" + userID); 87 | return rsp; 88 | } 89 | if (imMgr.validation(userID, token) != 0) { 90 | rsp.setCode(7); 91 | rsp.setMessage("请求失败,鉴权失败"); 92 | log.error("mergeStream失败:鉴权失败:" + "userID:" + userID); 93 | return rsp; 94 | 95 | } 96 | JSONObject jsonObj = new JSONObject(liveUtil.mergeStream(map)); 97 | MergeStreamRsp.Result result = new MergeStreamRsp.Result(); 98 | result.setCode(jsonObj.optInt("code")); 99 | result.setMessage(jsonObj.optString("message")); 100 | result.setTimestamp(jsonObj.optLong("timestamp")); 101 | rsp.setResult(result); 102 | return rsp; 103 | } 104 | 105 | @Override 106 | public GetTestPushUrlRsp getTestPushUrl() { 107 | String userID = Utils.genUserIdByRandom(); 108 | return liveUtil.getTestPushUrl(userID); 109 | } 110 | 111 | @Override 112 | public GetTestRtmpAccUrlRsp getTestRtmpAccUrl() { 113 | GetTestRtmpAccUrlRsp rsp = new GetTestRtmpAccUrlRsp(); 114 | rsp.setUrl_rtmpacc(liveUtil.genAcceleratePlayUrl("testclock_rtmpacc")); 115 | return rsp; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/utils/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.utils; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.client.ClientHttpRequestFactory; 6 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | @Configuration 10 | public class RestTemplateConfig { 11 | @Bean 12 | public RestTemplate restTemplate(ClientHttpRequestFactory factory) { 13 | return new RestTemplate(factory); 14 | } 15 | 16 | @Bean 17 | public ClientHttpRequestFactory simpleClientHttpRequestFactory() { 18 | SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); 19 | factory.setReadTimeout(5000);// ms 20 | factory.setConnectTimeout(15000);// ms 21 | return factory; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/tencent/qcloud/roomservice/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.tencent.qcloud.roomservice.utils; 2 | 3 | import org.codehaus.jackson.map.ObjectMapper; 4 | 5 | import java.io.IOException; 6 | import java.io.StringReader; 7 | import java.io.StringWriter; 8 | import java.math.BigInteger; 9 | import java.security.MessageDigest; 10 | import java.security.NoSuchAlgorithmException; 11 | import java.util.UUID; 12 | 13 | public class Utils { 14 | public static String getMD5(String str) { 15 | try { 16 | MessageDigest md = MessageDigest.getInstance("MD5"); 17 | md.update(str.getBytes()); 18 | return byteArrayToHex(md.digest()); 19 | } catch (NoSuchAlgorithmException e) { 20 | e.printStackTrace(); 21 | } 22 | return ""; 23 | } 24 | 25 | public static String byteArrayToHex(byte[] byteArray) { 26 | char[] hexDigits = {'0','1','2','3','4','5','6','7','8','9', 'a','b','c','d','e','f' }; 27 | char[] resultCharArray =new char[byteArray.length * 2]; 28 | int index = 0; 29 | for (byte b : byteArray) { 30 | resultCharArray[index++] = hexDigits[b>>>4 & 0xf]; 31 | resultCharArray[index++] = hexDigits[b & 0xf]; 32 | } 33 | return new String(resultCharArray); 34 | } 35 | 36 | public static String S4 () { 37 | return UUID.randomUUID().toString().substring(9, 13); 38 | } 39 | 40 | /** 41 | * 随机生成user_id 42 | */ 43 | public static String genUserIdByRandom () { 44 | return "user_" + (S4() + S4() + "_" + S4()); 45 | } 46 | 47 | /** 48 | * 随机生成room_id 49 | */ 50 | public static String genRoomIdByRandom () { 51 | return "room_" + (S4() + S4() + "_" + S4()); 52 | } 53 | 54 | public static String objectToString(Object obj){ 55 | ObjectMapper mapper = new ObjectMapper(); 56 | StringWriter writer = new StringWriter(); 57 | 58 | try { 59 | mapper.writeValue(writer, obj); 60 | return writer.toString(); 61 | } catch (IOException e) { 62 | e.printStackTrace(); 63 | } 64 | return "{}"; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/tls/base64_url/base64_url.java: -------------------------------------------------------------------------------- 1 | package com.tls.base64_url; 2 | 3 | import org.apache.commons.codec.DecoderException; 4 | import org.apache.commons.codec.binary.Hex; 5 | import org.bouncycastle.util.Arrays; 6 | 7 | public class base64_url { 8 | static byte base64_table_url[] = 9 | { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 10 | 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 11 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 12 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 13 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '-', '\0'}; 14 | 15 | static byte base64_pad_url = '_'; 16 | 17 | static short base64_reverse_table_url[] = { 18 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 19 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 20 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, 63, -1, -1, 21 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 22 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 23 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 24 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 25 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 26 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 27 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 29 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 31 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 32 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 33 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 34 | }; 35 | 36 | public static int unsignedToBytes(int b) 37 | { 38 | return b & 0xFF; 39 | } 40 | 41 | //int base64_encode_url(const unsigned char *in_str, int length, char *out_str,int *ret_length) 42 | public static byte [] base64EncodeUrl(byte [] in_str) 43 | { 44 | byte [] out_str = new byte [1024] ; 45 | 46 | int out_current = 0; 47 | int current = 0; 48 | int length = in_str.length; 49 | 50 | while (length > 2) { /* keep going until we have less than 24 bits */ 51 | 52 | out_str[out_current++] = base64_table_url[unsignedToBytes((unsignedToBytes(in_str[current]) >>> 2))]; 53 | out_str[out_current++] = base64_table_url[unsignedToBytes(unsignedToBytes(unsignedToBytes(in_str[current]) & 0x03) << 4) + unsignedToBytes((unsignedToBytes(in_str[current+1]) >>> 4))]; 54 | out_str[out_current++] = base64_table_url[(unsignedToBytes((unsignedToBytes(in_str[current+1]) & 0x0f)) << 2) + unsignedToBytes((unsignedToBytes(in_str[current+2]) >>> 6))]; 55 | out_str[out_current++] = base64_table_url[unsignedToBytes((unsignedToBytes(in_str[current+2]) & 0x3f))]; 56 | current += 3; 57 | length -= 3; /* we just handle 3 octets of data */ 58 | } 59 | 60 | /* now deal with the tail end of things */ 61 | if (length != 0) { 62 | out_str[out_current++] = base64_table_url[unsignedToBytes(in_str[current]) >>> 2]; 63 | if (length > 1) { 64 | out_str[out_current++] = base64_table_url[unsignedToBytes((unsignedToBytes(in_str[current]) & 0x03) << 4) + unsignedToBytes(unsignedToBytes(in_str[current+1]) >>> 4)]; 65 | out_str[out_current++] = base64_table_url[unsignedToBytes((unsignedToBytes(in_str[current + 1]) & 0x0f) << 2)]; 66 | out_str[out_current++] = base64_pad_url; 67 | } else { 68 | out_str[out_current++] = base64_table_url[unsignedToBytes((unsignedToBytes(in_str[current]) & 0x03) << 4)]; 69 | out_str[out_current++] = base64_pad_url; 70 | out_str[out_current++] = base64_pad_url; 71 | } 72 | } 73 | 74 | //System.out.println("length in base64EncodeUrl: " + out_current ); 75 | byte [] out_bytes = new String(out_str).getBytes(); 76 | return Arrays.copyOfRange(out_bytes, 0, out_current); 77 | } 78 | 79 | //int base64_decode_url(const unsigned char *in_str, int length, char *out_str, int *ret_length) 80 | public static byte [] base64DecodeUrl(byte [] in_str) 81 | { 82 | // const unsigned char *current = in_str; 83 | int ch, i = 0, j = 0, k; 84 | 85 | int current = 0; 86 | int result = 0; 87 | byte [] out_str = new byte [1024] ; 88 | int length = in_str.length; 89 | /* this sucks for threaded environments */ 90 | 91 | /* run through the whole string, converting as we go */ 92 | //while ((ch = in_str[current++]) != '\0' && length-- > 0) { 93 | ch = in_str[0]; 94 | while(length-- > 0){ 95 | ch = in_str[current++]; 96 | if (ch == base64_pad_url) break; 97 | /* When Base64 gets POSTed, all pluses are interpreted as spaces. 98 | This line changes them back. It's not exactly the Base64 spec, 99 | but it is completely compatible with it (the spec says that 100 | spaces are invalid). This will also save many people considerable 101 | headache. - Turadg Aleahmad 102 | */ 103 | if (ch == ' ') ch = '*'; //never using '+' 104 | 105 | ch = base64_reverse_table_url[ch]; 106 | if (ch < 0) continue; 107 | 108 | switch(i % 4) { 109 | case 0: 110 | out_str[j] = (byte) unsignedToBytes( unsignedToBytes(ch) << 2); 111 | break; 112 | case 1: 113 | out_str[j++] |= (byte) unsignedToBytes(unsignedToBytes(ch) >>> 4); 114 | out_str[j] = (byte) unsignedToBytes(unsignedToBytes(unsignedToBytes(ch) & 0x0f) << 4); 115 | break; 116 | case 2: 117 | out_str[j++] |= (byte) unsignedToBytes(unsignedToBytes(ch) >>> 2); 118 | out_str[j] = (byte) unsignedToBytes(unsignedToBytes(unsignedToBytes(ch) & 0x03) << 6); 119 | break; 120 | case 3: 121 | out_str[j++] |= (byte) unsignedToBytes(ch); 122 | break; 123 | } 124 | i++; 125 | } 126 | k = j; 127 | /* mop things up if we ended on a boundary */ 128 | if (ch == base64_pad_url) { 129 | switch(i % 4) { 130 | case 0: 131 | case 1: 132 | byte [] error = new byte [1]; 133 | error[0] = '\0'; 134 | return error; 135 | case 2: 136 | k++; 137 | case 3: 138 | out_str[k++] = 0; 139 | } 140 | } 141 | return Arrays.copyOfRange(out_str, 0, j); 142 | } 143 | public static void main(String[] args) throws DecoderException { 144 | 145 | //String hexString = "5095"; 146 | String hexString = "789c6d8d4d4f83401884ff0b578c5dba0bbb98782015b1a1f8059a7222743fc8da96aecbdbdad6f8dfa5046fce6de6c9cc7c3bc522bfeec4baaa8dd1c2b9713c822e0a7ddfb91aa03c1a6d65552b90b6e794d2a0e7231c5a550d15b6ff96416fe590e38051c2281d732d640b5ae96172ea051c29be524c62e463e57382e854a08020a578c8febe38dfed5ba8e0642e9b98e21174bae97d16bfcde633299e0f49c81f27a26490b97665d7675dd0fdc35df1de994d23bc4301db349fbb918ea3e36b509878f7956d84dbb44b4ed23287b454f7119c12fea4968b64927d7c962179b9757e7e01ed1059d9"; 147 | 148 | byte [] test = Hex.decodeHex(hexString.toCharArray()); 149 | byte [] compressBytes = base64EncodeUrl(test); 150 | System.out.println("compress : " + new String(compressBytes)); 151 | byte [] uncompressBytes = base64DecodeUrl(compressBytes); 152 | System.out.println("uncompress: " + Hex.encodeHexString(uncompressBytes)); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/com/tls/tls_sigature/tls_sigature.java: -------------------------------------------------------------------------------- 1 | package com.tls.tls_sigature; 2 | 3 | import java.io.CharArrayReader; 4 | import java.io.IOException; 5 | import java.io.PrintWriter; 6 | import java.io.Reader; 7 | import java.security.PrivateKey; 8 | import java.security.PublicKey; 9 | import java.security.Security; 10 | import java.nio.charset.Charset; 11 | 12 | import java.security.Signature; 13 | import java.util.zip.DataFormatException; 14 | import java.util.zip.Deflater; 15 | import java.util.zip.Inflater; 16 | 17 | import org.apache.commons.codec.binary.Base64; 18 | import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; 19 | import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; 20 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 21 | import org.bouncycastle.openssl.PEMParser; 22 | import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; 23 | import org.bouncycastle.util.Arrays; 24 | import org.json.JSONObject; 25 | 26 | import com.tls.base64_url.base64_url; 27 | 28 | public class tls_sigature { 29 | public static class GenTLSSignatureResult 30 | { 31 | public String errMessage; 32 | public String urlSig; 33 | public int expireTime; 34 | public int initTime; 35 | public GenTLSSignatureResult() 36 | { 37 | errMessage = ""; 38 | urlSig = ""; 39 | } 40 | } 41 | 42 | public static class GenTLSSignatureWithbuffResult 43 | { 44 | public String errMessage; 45 | public String privMapEncrypt; 46 | public GenTLSSignatureWithbuffResult() 47 | { 48 | errMessage = ""; 49 | privMapEncrypt = ""; 50 | } 51 | } 52 | 53 | public static class CheckTLSSignatureResult 54 | { 55 | public String errMessage; 56 | public boolean verifyResult; 57 | public int expireTime; 58 | public int initTime; 59 | public CheckTLSSignatureResult() 60 | { 61 | errMessage = ""; 62 | verifyResult = false; 63 | } 64 | } 65 | 66 | 67 | /** 68 | * @brief 生成 tls 票据 69 | * @param expire 有效期,单位是秒,推荐一个月 70 | * @param strAppid3rd 填写与 sdkAppid 一致字符串形式的值 71 | * @param skdAppid 应用的 appid 72 | * @param identifier 用户 id 73 | * @param accountType 创建应用后在配置页面上展示的 acctype 74 | * @param privStr 生成 tls 票据使用的私钥内容 75 | * @return 如果出错,GenTLSSignatureResult 中的 urlSig为空,errMsg 为出错信息,成功返回有效的票据 76 | * @throws IOException 77 | */ 78 | @Deprecated 79 | public static GenTLSSignatureResult GenTLSSignature(long expire, 80 | String strAppid3rd, long skdAppid, 81 | String identifier, long accountType, 82 | String privStr ) throws IOException 83 | { 84 | 85 | GenTLSSignatureResult result = new GenTLSSignatureResult(); 86 | 87 | Security.addProvider(new BouncyCastleProvider()); 88 | Reader reader = new CharArrayReader(privStr.toCharArray()); 89 | JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); 90 | PEMParser parser = new PEMParser(reader); 91 | Object obj = parser.readObject(); 92 | parser.close(); 93 | PrivateKey privKeyStruct = converter.getPrivateKey((PrivateKeyInfo) obj); 94 | 95 | //Create Json string and serialization String 96 | String jsonString = "{" 97 | + "\"TLS.account_type\":\"" + accountType +"\"," 98 | +"\"TLS.identifier\":\"" + identifier +"\"," 99 | +"\"TLS.appid_at_3rd\":\"" + strAppid3rd +"\"," 100 | +"\"TLS.sdk_appid\":\"" + skdAppid +"\"," 101 | +"\"TLS.expire_after\":\"" + expire +"\"" 102 | +"}"; 103 | //System.out.println("#jsonString : \n" + jsonString); 104 | 105 | String time = String.valueOf(System.currentTimeMillis()/1000); 106 | String SerialString = 107 | "TLS.appid_at_3rd:" + strAppid3rd + "\n" + 108 | "TLS.account_type:" + accountType + "\n" + 109 | "TLS.identifier:" + identifier + "\n" + 110 | "TLS.sdk_appid:" + skdAppid + "\n" + 111 | "TLS.time:" + time + "\n" + 112 | "TLS.expire_after:" + expire +"\n"; 113 | 114 | 115 | //System.out.println("#SerialString : \n" + SerialString); 116 | //System.out.println("#SerialString Hex: \n" + Hex.encodeHexString(SerialString.getBytes())); 117 | 118 | try{ 119 | //Create Signature by SerialString 120 | Signature signature = Signature.getInstance("SHA256withECDSA", "BC"); 121 | signature.initSign(privKeyStruct); 122 | signature.update(SerialString.getBytes(Charset.forName("UTF-8"))); 123 | byte[] signatureBytes = signature.sign(); 124 | 125 | String sigTLS = Base64.encodeBase64String(signatureBytes); 126 | //System.out.println("#sigTLS : " + sigTLS); 127 | 128 | //Add TlsSig to jsonString 129 | JSONObject jsonObject= new JSONObject(jsonString); 130 | jsonObject.put("TLS.sig", (Object)sigTLS); 131 | jsonObject.put("TLS.time", (Object)time); 132 | jsonString = jsonObject.toString(); 133 | 134 | // System.out.println("#jsonString : \n" + jsonString); 135 | 136 | //compression 137 | Deflater compresser = new Deflater(); 138 | compresser.setInput(jsonString.getBytes(Charset.forName("UTF-8"))); 139 | 140 | compresser.finish(); 141 | byte [] compressBytes = new byte [512]; 142 | int compressBytesLength = compresser.deflate(compressBytes); 143 | compresser.end(); 144 | //System.out.println("#compressBytes "+ compressBytesLength+": " + Hex.encodeHexString(Arrays.copyOfRange(compressBytes,0,compressBytesLength))); 145 | 146 | //String userSig = Base64.encodeBase64URLSafeString(Arrays.copyOfRange(compressBytes,0,compressBytesLength)); 147 | String userSig = new String(base64_url.base64EncodeUrl(Arrays.copyOfRange(compressBytes,0,compressBytesLength))); 148 | 149 | result.urlSig = userSig; 150 | //System.out.println("urlSig: "+ userSig); 151 | } 152 | catch(Exception e) 153 | { 154 | e.printStackTrace(); 155 | result.errMessage = "generate usersig failed"; 156 | } 157 | 158 | return result; 159 | } 160 | 161 | /** 162 | * @brief 校验 tls 票据 163 | * @param urlSig 返回 tls 票据 164 | * @param strAppid3rd 填写与 sdkAppid 一致的字符串形式的值 165 | * @param skdAppid 应的 appid 166 | * @param identifier 用户 id 167 | * @param accountType 创建应用后在配置页面上展示的 acctype 168 | * @param publicKey 用于校验 tls 票据的公钥内容,但是需要先将公钥文件转换为 java 原生 api 使用的格式,下面是推荐的命令 169 | * openssl pkcs8 -topk8 -in ec_key.pem -outform PEM -out p8_priv.pem -nocrypt 170 | * @return 如果出错 CheckTLSSignatureResult 中的 verifyResult 为 false,错误信息在 errMsg,校验成功为 true 171 | * @throws DataFormatException 172 | */ 173 | @Deprecated 174 | public static CheckTLSSignatureResult CheckTLSSignature( String urlSig, 175 | String strAppid3rd, long skdAppid, 176 | String identifier, long accountType, 177 | String publicKey ) throws DataFormatException 178 | { 179 | CheckTLSSignatureResult result = new CheckTLSSignatureResult(); 180 | Security.addProvider(new BouncyCastleProvider()); 181 | 182 | //DeBaseUrl64 urlSig to json 183 | Base64 decoder = new Base64(); 184 | 185 | //byte [] compressBytes = decoder.decode(urlSig.getBytes()); 186 | byte [] compressBytes = base64_url.base64DecodeUrl(urlSig.getBytes(Charset.forName("UTF-8"))); 187 | 188 | //System.out.println("#compressBytes Passing in[" + compressBytes.length + "] " + Hex.encodeHexString(compressBytes)); 189 | 190 | //Decompression 191 | Inflater decompression = new Inflater(); 192 | decompression.setInput(compressBytes, 0, compressBytes.length); 193 | byte [] decompressBytes = new byte [1024]; 194 | int decompressLength = decompression.inflate(decompressBytes); 195 | decompression.end(); 196 | 197 | String jsonString = new String(Arrays.copyOfRange(decompressBytes, 0, decompressLength)); 198 | 199 | //System.out.println("#Json String passing in : \n" + jsonString); 200 | 201 | //Get TLS.Sig from json 202 | JSONObject jsonObject= new JSONObject(jsonString); 203 | String sigTLS = jsonObject.getString("TLS.sig"); 204 | 205 | //debase64 TLS.Sig to get serailString 206 | byte[] signatureBytes = decoder.decode(sigTLS.getBytes(Charset.forName("UTF-8"))); 207 | 208 | try{ 209 | 210 | String sigTime = jsonObject.getString("TLS.time"); 211 | String sigExpire = jsonObject.getString("TLS.expire_after"); 212 | 213 | //checkTime 214 | //System.out.println("#time check: "+ System.currentTimeMillis()/1000 + "-" 215 | //+ Long.parseLong(sigTime) + "-" + Long.parseLong(sigExpire)); 216 | if( System.currentTimeMillis()/1000 - Long.parseLong(sigTime) > Long.parseLong(sigExpire)) 217 | { 218 | result.errMessage = new String("TLS sig is out of date "); 219 | System.out.println("Timeout"); 220 | return result; 221 | } 222 | 223 | //Get Serial String from json 224 | String SerialString = 225 | "TLS.appid_at_3rd:" + strAppid3rd + "\n" + 226 | "TLS.account_type:" + accountType + "\n" + 227 | "TLS.identifier:" + identifier + "\n" + 228 | "TLS.sdk_appid:" + skdAppid + "\n" + 229 | "TLS.time:" + sigTime + "\n" + 230 | "TLS.expire_after:" + sigExpire + "\n"; 231 | 232 | //System.out.println("#SerialString : \n" + SerialString); 233 | 234 | Reader reader = new CharArrayReader(publicKey.toCharArray()); 235 | PEMParser parser = new PEMParser(reader); 236 | JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); 237 | Object obj = parser.readObject(); 238 | parser.close(); 239 | PublicKey pubKeyStruct = converter.getPublicKey((SubjectPublicKeyInfo) obj); 240 | 241 | Signature signature = Signature.getInstance("SHA256withECDSA","BC"); 242 | signature.initVerify(pubKeyStruct); 243 | signature.update(SerialString.getBytes(Charset.forName("UTF-8"))); 244 | boolean bool = signature.verify(signatureBytes); 245 | //System.out.println("#jdk ecdsa verify : " + bool); 246 | result.verifyResult = bool; 247 | } 248 | catch(Exception e) 249 | { 250 | e.printStackTrace(); 251 | result.errMessage = "Failed in checking sig"; 252 | } 253 | 254 | return result; 255 | } 256 | 257 | /** 258 | * @brief 生成 tls 票据,精简参数列表,有效期默认为 180 天 259 | * @param skdAppid 应用的 sdkappid 260 | * @param identifier 用户 id 261 | * @param privStr 私钥文件内容 262 | * @return 263 | * @throws IOException 264 | */ 265 | public static GenTLSSignatureResult GenTLSSignatureEx( 266 | long skdAppid, 267 | String identifier, 268 | String privStr) throws IOException { 269 | return GenTLSSignatureEx(skdAppid, identifier, privStr, 3600*24*180); 270 | } 271 | 272 | /** 273 | * @brief 生成 tls 票据,精简参数列表 274 | * @param skdAppid 应用的 sdkappid 275 | * @param identifier 用户 id 276 | * @param privStr 私钥文件内容 277 | * @param expire 有效期,以秒为单位,推荐时长一个月 278 | * @return 279 | * @throws IOException 280 | */ 281 | public static GenTLSSignatureResult GenTLSSignatureEx( 282 | long skdAppid, 283 | String identifier, 284 | String privStr, 285 | long expire) throws IOException { 286 | 287 | GenTLSSignatureResult result = new GenTLSSignatureResult(); 288 | 289 | Security.addProvider(new BouncyCastleProvider()); 290 | Reader reader = new CharArrayReader(privStr.toCharArray()); 291 | JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); 292 | PEMParser parser = new PEMParser(reader); 293 | Object obj = parser.readObject(); 294 | parser.close(); 295 | PrivateKey privKeyStruct = converter.getPrivateKey((PrivateKeyInfo) obj); 296 | 297 | String jsonString = "{" 298 | + "\"TLS.account_type\":\"" + 0 +"\"," 299 | +"\"TLS.identifier\":\"" + identifier +"\"," 300 | +"\"TLS.appid_at_3rd\":\"" + 0 +"\"," 301 | +"\"TLS.sdk_appid\":\"" + skdAppid +"\"," 302 | +"\"TLS.expire_after\":\"" + expire +"\"," 303 | +"\"TLS.version\": \"201512300000\"" 304 | +"}"; 305 | 306 | String time = String.valueOf(System.currentTimeMillis()/1000); 307 | String SerialString = 308 | "TLS.appid_at_3rd:" + 0 + "\n" + 309 | "TLS.account_type:" + 0 + "\n" + 310 | "TLS.identifier:" + identifier + "\n" + 311 | "TLS.sdk_appid:" + skdAppid + "\n" + 312 | "TLS.time:" + time + "\n" + 313 | "TLS.expire_after:" + expire +"\n"; 314 | 315 | try { 316 | //Create Signature by SerialString 317 | Signature signature = Signature.getInstance("SHA256withECDSA", "BC"); 318 | signature.initSign(privKeyStruct); 319 | signature.update(SerialString.getBytes(Charset.forName("UTF-8"))); 320 | byte[] signatureBytes = signature.sign(); 321 | 322 | String sigTLS = Base64.encodeBase64String(signatureBytes); 323 | 324 | //Add TlsSig to jsonString 325 | JSONObject jsonObject= new JSONObject(jsonString); 326 | jsonObject.put("TLS.sig", (Object)sigTLS); 327 | jsonObject.put("TLS.time", (Object)time); 328 | jsonString = jsonObject.toString(); 329 | 330 | //compression 331 | Deflater compresser = new Deflater(); 332 | compresser.setInput(jsonString.getBytes(Charset.forName("UTF-8"))); 333 | 334 | compresser.finish(); 335 | byte [] compressBytes = new byte [512]; 336 | int compressBytesLength = compresser.deflate(compressBytes); 337 | compresser.end(); 338 | String userSig = new String(base64_url.base64EncodeUrl(Arrays.copyOfRange(compressBytes,0,compressBytesLength))); 339 | 340 | result.urlSig = userSig; 341 | } 342 | catch(Exception e) 343 | { 344 | e.printStackTrace(); 345 | result.errMessage = "generate usersig failed"; 346 | } 347 | 348 | return result; 349 | } 350 | /** 351 | * @brief 生成 privMapEncrypt,精简参数列表,有效期默认为 180 天 352 | * @param skdAppid 应用的 sdkappid 353 | * @param identifier 用户 id 354 | * @param roomid 房间 id 355 | * @param privStr 私钥文件内容 356 | * @return 357 | * @throws IOException 358 | */ 359 | public static GenTLSSignatureWithbuffResult GenTLSSignatureWithbuffEx( 360 | long skdAppid, 361 | String identifier, 362 | String roomid, 363 | String privStr) throws IOException { 364 | return GenTLSSignatureWithbuffEx(skdAppid, identifier, roomid, privStr, 3600*24*180); 365 | } 366 | 367 | /** 368 | * @brief 生成 privMapEncrypt,精简参数列表 369 | * @param skdAppid 应用的 sdkappid 370 | * @param identifier 用户 id 371 | * @param roomid 房间 id 372 | * @param privStr 私钥文件内容 373 | * @param expire 有效期,以秒为单位,推荐时长一个月 374 | * @return 375 | * @throws IOException 376 | */ 377 | public static GenTLSSignatureWithbuffResult GenTLSSignatureWithbuffEx( 378 | long skdAppid, 379 | String identifier, 380 | String roomid, 381 | String privStr, 382 | long expire) throws IOException { 383 | 384 | GenTLSSignatureWithbuffResult result = new GenTLSSignatureWithbuffResult(); 385 | 386 | Security.addProvider(new BouncyCastleProvider()); 387 | Reader reader = new CharArrayReader(privStr.toCharArray()); 388 | JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); 389 | PEMParser parser = new PEMParser(reader); 390 | Object obj = parser.readObject(); 391 | parser.close(); 392 | PrivateKey privKeyStruct = converter.getPrivateKey((PrivateKeyInfo) obj); 393 | 394 | String time = String.valueOf(System.currentTimeMillis()/1000); 395 | 396 | //视频校验位需要用到的字段 397 | /* 398 | cVer unsigned char/1 版本号,填0 399 | wAccountLen unsigned short /2 第三方自己的帐号长度 400 | buffAccount wAccountLen 第三方自己的帐号字符 401 | dwSdkAppid unsigned int/4 sdkappid 402 | dwRoomId unsigned int/4 群组号码 403 | dwExpTime unsigned int/4 过期时间 (当前时间 + 有效期(单位:秒,建议300秒)) 404 | dwPrivilegeMap unsigned int/4 权限位 405 | dwAccountType unsigned int/4 第三方帐号类型 406 | */ 407 | int accountLength = identifier.length(); 408 | int offset = 0; 409 | byte[] bytes = new byte[1+2+accountLength+4+4+4+4+4]; 410 | bytes[offset++] = 0; 411 | bytes[offset++] = (byte)((accountLength & 0xFF00) >> 8); 412 | bytes[offset++] = (byte)(accountLength & 0x00FF); 413 | 414 | for (; offset < 3 + accountLength; ++offset) { 415 | bytes[offset] = (byte)identifier.charAt(offset - 3); 416 | } 417 | bytes[offset++] = (byte)((skdAppid & 0xFF000000) >> 24); 418 | bytes[offset++] = (byte)((skdAppid & 0x00FF0000) >> 16); 419 | bytes[offset++] = (byte)((skdAppid & 0x0000FF00) >> 8); 420 | bytes[offset++] = (byte)(skdAppid & 0x000000FF); 421 | 422 | long nRoomId = Long.valueOf(roomid); 423 | bytes[offset++] = (byte)((nRoomId & 0xFF000000) >> 24); 424 | bytes[offset++] = (byte)((nRoomId & 0x00FF0000) >> 16); 425 | bytes[offset++] = (byte)((nRoomId & 0x0000FF00) >> 8); 426 | bytes[offset++] = (byte)(nRoomId & 0x000000FF); 427 | 428 | long expiredTime = Long.valueOf(time) + expire; 429 | bytes[offset++] = (byte)((expiredTime & 0xFF000000) >> 24); 430 | bytes[offset++] = (byte)((expiredTime & 0x00FF0000) >> 16); 431 | bytes[offset++] = (byte)((expiredTime & 0x0000FF00) >> 8); 432 | bytes[offset++] = (byte)(expiredTime & 0x000000FF); 433 | 434 | bytes[offset++] = (byte)((255 & 0xFF000000) >> 24); 435 | bytes[offset++] = (byte)((255 & 0x00FF0000) >> 16); 436 | bytes[offset++] = (byte)((255 & 0x0000FF00) >> 8); 437 | bytes[offset++] = (byte)(255 & 0x000000FF); 438 | 439 | bytes[offset++] = (byte)((0 & 0xFF000000) >> 24); 440 | bytes[offset++] = (byte)((0 & 0x00FF0000) >> 16); 441 | bytes[offset++] = (byte)((0 & 0x0000FF00) >> 8); 442 | bytes[offset++] = (byte)(0 & 0x000000FF); 443 | 444 | String userbuff = Base64.encodeBase64String(bytes); 445 | 446 | String jsonString = "{" 447 | +"\"TLS.appid_at_3rd\":\"" + 0 +"\"," 448 | +"\"TLS.account_type\":\"" + 0 +"\"," 449 | +"\"TLS.identifier\":\"" + identifier +"\"," 450 | +"\"TLS.sdk_appid\":\"" + skdAppid +"\"," 451 | +"\"TLS.expire_after\":\"" + expire +"\"," 452 | +"\"TLS.version\": \"201512300000\"" 453 | +"}"; 454 | 455 | String SerialString = 456 | "TLS.appid_at_3rd:" + 0 + "\n" + 457 | "TLS.account_type:" + 0 + "\n" + 458 | "TLS.identifier:" + identifier + "\n" + 459 | "TLS.sdk_appid:" + skdAppid + "\n" + 460 | "TLS.time:" + time + "\n" + 461 | "TLS.expire_after:" + expire +"\n" + 462 | "TLS.userbuf:" + userbuff + "\n"; 463 | 464 | 465 | try { 466 | //Create Signature by SerialString 467 | Signature signature = Signature.getInstance("SHA256withECDSA", "BC"); 468 | signature.initSign(privKeyStruct); 469 | signature.update(SerialString.getBytes(Charset.forName("UTF-8"))); 470 | byte[] signatureBytes = signature.sign(); 471 | 472 | String sigTLS = Base64.encodeBase64String(signatureBytes); 473 | 474 | //Add TlsSig to jsonString 475 | JSONObject jsonObject= new JSONObject(jsonString); 476 | jsonObject.put("TLS.sig", (Object)sigTLS); 477 | jsonObject.put("TLS.time", (Object)time); 478 | jsonObject.put("TLS.userbuf", (Object)userbuff); 479 | jsonString = jsonObject.toString(); 480 | 481 | //compression 482 | Deflater compresser = new Deflater(); 483 | compresser.setInput(jsonString.getBytes(Charset.forName("UTF-8"))); 484 | 485 | compresser.finish(); 486 | byte [] compressBytes = new byte [512]; 487 | int compressBytesLength = compresser.deflate(compressBytes); 488 | compresser.end(); 489 | String userSig = new String(base64_url.base64EncodeUrl(Arrays.copyOfRange(compressBytes,0,compressBytesLength))); 490 | 491 | result.privMapEncrypt = userSig; 492 | } 493 | catch(Exception e) 494 | { 495 | e.printStackTrace(); 496 | result.errMessage = "generate privMapEncrypt failed"; 497 | } 498 | 499 | return result; 500 | } 501 | public static CheckTLSSignatureResult CheckTLSSignatureEx( 502 | String urlSig, 503 | long sdkAppid, 504 | String identifier, 505 | String publicKey ) throws DataFormatException { 506 | 507 | CheckTLSSignatureResult result = new CheckTLSSignatureResult(); 508 | Security.addProvider(new BouncyCastleProvider()); 509 | 510 | //DeBaseUrl64 urlSig to json 511 | Base64 decoder = new Base64(); 512 | 513 | byte [] compressBytes = base64_url.base64DecodeUrl(urlSig.getBytes(Charset.forName("UTF-8"))); 514 | 515 | //Decompression 516 | Inflater decompression = new Inflater(); 517 | decompression.setInput(compressBytes, 0, compressBytes.length); 518 | byte [] decompressBytes = new byte [1024]; 519 | int decompressLength = decompression.inflate(decompressBytes); 520 | decompression.end(); 521 | 522 | String jsonString = new String(Arrays.copyOfRange(decompressBytes, 0, decompressLength)); 523 | 524 | //Get TLS.Sig from json 525 | JSONObject jsonObject= new JSONObject(jsonString); 526 | String sigTLS = jsonObject.getString("TLS.sig"); 527 | 528 | //debase64 TLS.Sig to get serailString 529 | byte[] signatureBytes = decoder.decode(sigTLS.getBytes(Charset.forName("UTF-8"))); 530 | 531 | try { 532 | String strSdkAppid = jsonObject.getString("TLS.sdk_appid"); 533 | String sigTime = jsonObject.getString("TLS.time"); 534 | String sigExpire = jsonObject.getString("TLS.expire_after"); 535 | 536 | if (Integer.parseInt(strSdkAppid) != sdkAppid) 537 | { 538 | result.errMessage = new String( "sdkappid " 539 | + strSdkAppid 540 | + " in tls sig not equal sdkappid " 541 | + sdkAppid 542 | + " in request"); 543 | return result; 544 | } 545 | 546 | if ( System.currentTimeMillis()/1000 - Long.parseLong(sigTime) > Long.parseLong(sigExpire)) { 547 | result.errMessage = new String("TLS sig is out of date"); 548 | return result; 549 | } 550 | 551 | //Get Serial String from json 552 | String SerialString = 553 | "TLS.appid_at_3rd:" + 0 + "\n" + 554 | "TLS.account_type:" + 0 + "\n" + 555 | "TLS.identifier:" + identifier + "\n" + 556 | "TLS.sdk_appid:" + sdkAppid + "\n" + 557 | "TLS.time:" + sigTime + "\n" + 558 | "TLS.expire_after:" + sigExpire + "\n"; 559 | 560 | Reader reader = new CharArrayReader(publicKey.toCharArray()); 561 | PEMParser parser = new PEMParser(reader); 562 | JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); 563 | Object obj = parser.readObject(); 564 | parser.close(); 565 | PublicKey pubKeyStruct = converter.getPublicKey((SubjectPublicKeyInfo) obj); 566 | 567 | Signature signature = Signature.getInstance("SHA256withECDSA","BC"); 568 | signature.initVerify(pubKeyStruct); 569 | signature.update(SerialString.getBytes(Charset.forName("UTF-8"))); 570 | boolean bool = signature.verify(signatureBytes); 571 | result.expireTime = Integer.parseInt(sigExpire); 572 | result.initTime = Integer.parseInt(sigTime); 573 | result.verifyResult = bool; 574 | } 575 | catch(Exception e) 576 | { 577 | e.printStackTrace(); 578 | result.errMessage = "Failed in checking sig"; 579 | } 580 | 581 | return result; 582 | } 583 | 584 | } 585 | -------------------------------------------------------------------------------- /src/main/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | UTF-8 5 | 6 | [%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n 7 | 8 | 9 | DEBUG 10 | 11 | 12 | 13 | 14 | ${CATALINA_HOME}/logs/roomservice_info.log 15 | 16 | ${CATALINA_HOME}/logs/roomservice_info.log.%d{yyyy-MM-dd}.gz 17 | true 18 | 10 19 | 20 | 21 | [%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n 22 | 23 | 24 | INFO 25 | ACCEPT 26 | DENY 27 | 28 | 29 | 30 | 31 | ${CATALINA_HOME}/logs/roomservice_warn.log 32 | 33 | ${CATALINA_HOME}/logs/roomservice_warn.log.%d{yyyy-MM-dd}.gz 34 | true 35 | 10 36 | 37 | 38 | [%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n 39 | 40 | 41 | WARN 42 | ACCEPT 43 | DENY 44 | 45 | 46 | 47 | 48 | 49 | ${CATALINA_HOME}/logs/roomservice_error.log 50 | 51 | ${CATALINA_HOME}/logs/roomservice_error.log.%d{yyyy-MM-dd}.gz 52 | true 53 | 10 54 | 55 | 56 | [%d{HH:mm:ss.SSS}][%p][%c{40}][%t] %m%n 57 | 58 | 59 | ERROR 60 | ACCEPT 61 | DENY 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/dispatcher-servlet.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | text/plain;charset=UTF-8 26 | text/html;charset=UTF-8 27 | 28 | 29 | 30 | 31 | 32 | 33 | application/json;charset=UTF-8 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | Archetype Created Web Application 6 | 7 | 8 | CorsFilter 9 | com.tencent.qcloud.roomservice.common.CORSFilter 10 | 11 | 12 | CorsFilter 13 | /* 14 | 15 | 16 | 17 | 18 | characterEncodingFilter 19 | org.springframework.web.filter.CharacterEncodingFilter 20 | 21 | encoding 22 | UTF-8 23 | 24 | 25 | forceEncoding 26 | true 27 | 28 | 29 | 30 | characterEncodingFilter 31 | /* 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.springframework.web.context.request.RequestContextListener 39 | 40 | 41 | 42 | 43 | org.springframework.web.context.ContextLoaderListener 44 | 45 | 46 | contextConfigLocation 47 | 48 | classpath:applicationContext.xml 49 | 50 | 51 | 52 | 53 | 54 | dispatcher 55 | org.springframework.web.servlet.DispatcherServlet 56 | 1 57 | 58 | 59 | 60 | dispatcher 61 | / 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello World!

4 | 5 | 6 | --------------------------------------------------------------------------------