├── 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 | 
22 | 点击申请开通,之后会进入腾讯云人工审核阶段,审核通过后即可开通。
23 |
24 | #### 配置直播码
25 | 直播服务开通后,进入【直播控制台】>【直播码接入】>【[接入配置](https://console.cloud.tencent.com/live/livecodemanage)】 完成相关配置,即可开启直播码服务:
26 | 
27 | 点击【确定接入】按钮即可。
28 |
29 | #### 获取直播服务配置信息
30 | 从直播控制台获取`APP_ID`、`APP_BIZID`、`API_KEY`,后面配置服务器会用到:
31 | 
32 |
33 | ### 开通云通信服务
34 | #### 申请开通云通讯服务
35 | 进入[云通讯管理控制台](https://console.cloud.tencent.com/avc),如果还没有服务,直接点击**直接开通云通讯**按钮即可。新认证的腾讯云账号,云通讯的应用列表是空的,如下图:
36 | 
37 |
38 | 点击**创建应用接入**按钮创建一个新的应用接入,即您要接入腾讯云IM通讯服务的App的名字,我们的测试应用名称叫做“RTMPRoom”,如下图所示:
39 | 
40 |
41 | 点击确定按钮,之后就可以在应用列表中看到刚刚添加的项目了,如下图所示:
42 | 
43 |
44 | #### 配置独立模式
45 | 上图的列表中,右侧有一个**应用配置**按钮,点击这里进入下一步的配置工作,如下图所示。
46 | 
47 |
48 | #### 获取云通讯服务配置信息
49 | 从云通信控制台获取`IM_SDKAPPID`、`IM_ACCOUNTTYPE`、`ADMINISTRATOR`、`PRIVATEKEY`、`PUBLICKEY`,后面配置服务器会用到:
50 | 
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 | 
205 |
206 | 成功安装 IntelliJ IDEA,接着配置按照第 3 步讲述, 修改下配置信息。
207 |
208 | 配置修改好之后,选择 Build -> Build Artifacts 开始打包,打包完成后到输出路径拿到 roomservice.war 包。选择 File -> Project Structure 可以查看输出路径,如下图中的 E:\RoomService\rtcroom_server_java-master\target。
209 | 
210 |
211 | 注意:roomservice.war 包解压后,里面是应该是 .class 的文件,否则说明打包有问题。
212 | 
213 |
214 | ## 5.部署服务器
215 | 以部署到腾讯云服务器为例,描述部署过程。采用CentOS + nginx + Apache Tomcat + java 的 环境。
216 |
217 | > 注意:
218 | > jdk版本需要为1.8以上。
219 | > 小程序和ios都要求服务器支持HTTPS请求。
220 |
221 | ### 申请云服务器
222 | 1、新建 CVM 主机。
223 | 
224 |
225 | 2、从服务市场选取镜像。
226 | 
227 |
228 | 3、配置硬盘、网络、云主机访问密码,并且妥善保管好密码,然后设置安全组。
229 | 
230 |
231 | 4、付款后生成云主机。点击登录可以通过腾讯云的网页 shell 进行访问。
232 | 
233 |
234 | 5、查看/切换 JDK 版本。
235 | 
236 | 
237 | 修改完成后可以用 `sh version.sh` 命令查看是否修改成功。
238 |
239 | 使用 `sudo update-alternatives --display java` 命令可以查看当前已安装的 JDK 版本。
240 | 
241 |
242 | ### 部署war包到服务器
243 | 建议使用 FileZilla 或者 FlashFXP 等可视化界面工具。
244 | 
245 |
246 | 将打包好的 webrtc.war 包上传 tomcat 的 webapps 目录下
247 | 
248 |
249 |
250 | 6、重新启动 tomcat。
251 | 上传完成后,如果 tomcat 服务未启动,可以进入 tomcat/bin 目录,通过 ./startup.sh start 命令启动 tomcat。“Tomcat started.”表示服务启动成功。
252 | 
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 |
--------------------------------------------------------------------------------