全部开发者教程

企业级在线办公系统

首页 慕课教程 企业级在线办公系统 企业级在线办公系统 9-2 获取用户签名和视频会议室RoomID

上一章我们在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);
    }
}