上一章我们在Web页面上可以创建各种会议申请,然后切换到部门经理或者总经理身份,可以对这些会议申请执行审批。线下会议还好说,到了开会的的时候,参会人都去某个会议室。至于说这些人怎么线下开会,程序不用过多干预。但是线上会议,参会人要用视频方式交流,这就得开辟视频会议室了,那么这节课咱们就来实践这个线上视频会议室。
咱们的视频会议室要依赖于腾讯云的TRTC技术,帮我们把参会人的视频数据复制,然后转发给其他的参会人。这样不会出现因为参会人的增多,导致视频质量下降。如果不使用TRTC技术,仅凭我们本地的带宽,参会人越多,大家看到的视频质量就越差。
一、配置后端项目
之前我们已经开通了腾讯云TRTC服务,所以我们到后端项目上配置一下。前端页面想要连接上TRTC服务器上面的视频会议室,首先需要创建TrtcClient对象,创建这个对象需要用到3个参数,分别是TRTC的AppID,用户UserId,还有用户签名。用户签名是用TRTC的AppID、密钥和UserId生成的。如果用户签名在前端页面计算生成,势必我们要在前端页面保存TRTC的AppID和密钥,任意开发者查看HTML源代码都能看到这些信息,所以决不能在前端页面生成用户签名。
正确的做法是在后端项目生成用户签名,我们在config
包里面创建tencent
包,然后创建一个TrtcUtil
工具类,用来生成用户签名。
package com.example.emos.api.config.tencent;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.zip.Deflater;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
@Component
public class TrtcUtil {
@Value("${tencent.trtc.appId}")
private int appId;
@Value("${tencent.trtc.expire}")
private int expire;
@Value("${tencent.trtc.secretKey}")
private String secretKey;
public String genUserSig(String userId) {
return GenTLSSignature(appId, userId, expire, null, secretKey);
}
private String GenTLSSignature(long sdkappid, String userId, long expire, byte[] userbuf, String priKeyContent) {
if (StrUtil.isEmpty(priKeyContent)) {
return "";
}
long currTime = System.currentTimeMillis() / 1000;
JSONObject sigDoc = new JSONObject();
sigDoc.set("TLS.ver", "2.0");
sigDoc.set("TLS.identifier", userId);
sigDoc.set("TLS.sdkappid", sdkappid);
sigDoc.set("TLS.expire", expire);
sigDoc.set("TLS.time", currTime);
String base64UserBuf = null;
if (null != userbuf) {
base64UserBuf = Base64.encode(userbuf);
sigDoc.set("TLS.userbuf", base64UserBuf);
}
String sig = hmacsha256(sdkappid, userId, currTime, expire, priKeyContent, base64UserBuf);
if (sig.length() == 0) {
return "";
}
sigDoc.set("TLS.sig", sig);
Deflater compressor = new Deflater();
compressor.setInput(sigDoc.toString().getBytes(Charset.forName("UTF-8")));
compressor.finish();
byte[] compressedBytes = new byte[2048];
int compressedBytesLength = compressor.deflate(compressedBytes);
compressor.end();
return new String(base64EncodeUrl(Arrays.copyOfRange(compressedBytes, 0, compressedBytesLength)));
}
private static String hmacsha256(long sdkappid, String userId, long currTime, long expire, String priKeyContent, String base64Userbuf) {
String contentToBeSigned = "TLS.identifier:" + userId + "\n"
+ "TLS.sdkappid:" + sdkappid + "\n"
+ "TLS.time:" + currTime + "\n"
+ "TLS.expire:" + expire + "\n";
if (null != base64Userbuf) {
contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";
}
try {
byte[] byteKey = priKeyContent.getBytes("UTF-8");
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");
hmac.init(keySpec);
byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes("UTF-8"));
return Base64.encode(byteSig);
} catch (UnsupportedEncodingException e) {
return "";
} catch (NoSuchAlgorithmException e) {
return "";
} catch (InvalidKeyException e) {
return "";
}
}
private static byte[] base64EncodeUrl(byte[] input) {
byte[] base64 = Base64.encode(input).getBytes();
for (int i = 0; i < base64.length; ++i)
switch (base64[i]) {
case '+':
base64[i] = '*';
break;
case '/':
base64[i] = '-';
break;
case '=':
base64[i] = '_';
break;
default:
break;
}
return base64;
}
}
在MeetingController.java
类中定义获取用户签名的Web方法。
public class MeetingController {
@Value("${tencent.trtc.appId}")
private int appId;
@Autowired
private TrtcUtil trtcUtil;
……
@GetMapping("/searchMyUserSig")
@Operation(summary = "获取用户签名")
@SaCheckLogin
public R searchMyUserSig() {
int userId = StpUtil.getLoginIdAsInt();
String userSig = trtcUtil.genUserSig(userId + "");
return R.ok().put("userSig", userSig).put("userId", userId).put("appId", appId);
}
}
二、查询视频会议室RoomID
每个视频会议室都有RoomID,这个RoomID是保存在Redis里面的,我们可以通过会议的UUID查找到RoomID。但是只有在会议开始前20分钟,工作流项目的定时器才会生成这个RoomID。
用户在前端页面进入到meeting_video.vue
页面之后,需要通过Ajax查询这个会议室的RoomID,然后让TrtcClient连接到视频会议室,所以我们要写后端的Web代码返回RoomID。
1. 编写业务层代码
在MeetingService.java
接口中,定义抽象方法。
public interface MeetingService {
……
public Long searchRoomIdByUUID(String uuid);
}
在MeetingServiceImpl.java
类中,实现抽象方法。
public class MeetingServiceImpl implements MeetingService {
@Autowired
private RedisTemplate redisTemplate;
……
@Override
public Long searchRoomIdByUUID(String uuid) {
if (redisTemplate.hasKey(uuid)) {
Object temp = redisTemplate.opsForValue().get(uuid);
long roomId = Long.parseLong(temp.toString());
return roomId;
}
return null;
}
}
2. 编写Web层代码
创建SearchRoomIdByUUIDForm.java
类,封装Ajax提交的请求。
@Data
@Schema(description = "查询在线会议室房间ID")
public class SearchRoomIdByUUIDForm {
@NotBlank(message = "uuid不能为空")
@Schema(description = "uuid")
private String uuid;
}
在MeetingController.java
类中,定义Web方法。
public class MeetingController {
……
@PostMapping("/searchRoomIdByUUID")
@Operation(summary = "查询会议房间RoomID")
@SaCheckLogin
public R searchRoomIdByUUID(@Valid @RequestBody SearchRoomIdByUUIDForm form) {
Long roomId = meetingService.searchRoomIdByUUID(form.getUuid());
return R.ok().put("roomId", roomId);
}
}