腾讯会议简单介绍

腾讯会议官网:视频会议_语音会议_远程会议软件_在线培训系统-腾讯会议

腾讯会议API文档:腾讯会议 简介 - API 文档 - 文档中心 - 腾讯云

1.产品简介

腾讯会议(Tencent Meeting,TM)是一款基于腾讯21年音视频通讯经验积累的高清流畅、便捷易用、安全可靠的云视频会议产品,让您随时随地高效开会,全方位满足不同场景下的会议需求。您可以使用腾讯会议进行远程音视频会议、在线协作、会管会控、会议录制、指定邀请、布局管理等。在线下办公场景时,腾讯会议提供会议室连接器,让现有会议室设备连上云会议。同时,腾讯会议也推出了简单、上手即用的新一代智能协作会议室腾讯会议 Rooms,为企业提供更先进更智能的会议室解决方案。
腾讯会议覆盖 PC、移动客户端,分为免费版、商业版(付费)、企业版(付费)。

2.应用场景

企业会议

PC 端、移动端、小程序随时随地一键开会,灵活扩容,提高企业会议质量与效率。支持与传统硬件视频会议系统对接,只需简单拨号,即可接入腾讯会议,突破企业设备的限制,降低远程视频会议的成本。

远程教学

利用在线文档协作与实时屏幕共享,为远程课堂营造专注、互动性强的教学氛围。

远程面试

高清音视频通话品质让交流更顺畅,背景虚化让交流更集中,AI 智能美颜让会议形象更美好。

3.腾讯会议API文档简单介绍

3.1.API文档地址

腾讯会议 简介 - API 文档 - 文档中心 - 腾讯云

3.2.API简介

腾讯会议(Tencent Meeting,TM)Rest API 是为参与腾讯会议生态系统建设的合作方开发者接入并访问腾讯会议资源提供的一组工具,是访问腾讯会议 SaaS 服务的入口。合作伙伴可以通过腾讯会议 API 进行二次开发,例如创建一个会议,修改会议,查询会议信息等。



3.3.鉴权方式

腾讯会议的鉴权方式有两种:

  1. 企业内部应用鉴权
  2. 第三方应用鉴权(OAuth2.0) 

3.3.1.企业内部应用鉴权

1)注册登录腾讯会议官网

视频会议_语音会议_远程会议软件_在线培训系统-腾讯会议


我们开发需要使用的四个参数:SecretKey,SecretID,AppID,SDKID

我在开发中使用的是腾讯会议的商业版和企业版,腾讯会议和obs都是一种推流方式,腾讯会议相比较多了一个会议的概念。

商业版需要跟腾讯官方付费购买账号用户,一个账号可以有多个用户,腾讯是按照用户标准收费的,一个用户有唯一的userId和手机号,腾讯会议API主要是通过userid作为唯一标志,用户可以创建会议,使用会议等等。

腾讯收费的标准是按照用户数量来收费的,但是不限制具体是某个用户,比如我一个账号买了三个用户,我只能在官网上配置三个用户,但是可以删除用户再新增用户。

一个用户可以同时建立多个同时间段内的会议,但是有直播并发数的限制。

还可以看到腾讯会议的会议信息和用户信息


2)公共参数

REST API 对每个访问请求进行身份验证,如未提供安全凭证和签名则无法调用 API 接口。则用户可以参考腾讯云 API 的鉴权规则计算签名。

API 调用方需申请或持有安全凭证。安全凭证包括 SecretId 和 SecretKey。

  • SecretId:用于表示 API 调用者身份。
  • SecretKey:用于加密签名字符串和服务器端验证签名字符串的密钥。

公共参数是用于标识用户和接口鉴权目的的参数,如非必要,在每个接口单独的接口文档中不再对这些参数进行说明,但每次请求均需要携带这些参数,才能正常发起请求。
API 采用 TC3-HMAC-SHA256 签名方法,公共参数需要统一放到 HTTP Header 请求头部中。

即:每次发送请求(除了生成签名),都需要这些公共参数,放在 HTTP Header 请求头部中.

看看这几个必填的参数,X-TC-Signature 是生成的签名,SdkId 如果存在的话必须填。

注意:构造请求头,需注意自定义字段名的大小写。签名验证以及服务器端读取字段值对大小写敏感。

3)生成签名串
参与签名的字符串包括:

String tobeSig =
        httpMethod + "\nX-TC-Key=" + secretId + "&X-TC-Nonce=" + headerNonce + "&X-TC-Timestamp=" + headerTimestamp + "\n" + requestUri + "\n" + requestBody;

参数解释:

httpMethod:是当前请求的请求方式,以取消会议接口为例,请求方式为POST

Header 请求头中参与签名的字段包含:X-TC-Nonce,X-TC-Timestamp,X-TC-Key。 组成 Header 签名串时,参与签名的参数按参数名做字典序升序排列。

requestUri:注意是URI,不是URL,取消会议的URL是  https://api.meeting.qq.com/v1/meetings/{meetingId}/cancel

URI 是 /v1/meetings/{meetingId}/cancel,注意{meetingId} 是实际的meetingId,比如 /v1/meetings/123456789/cancel

requestBody:接口的请求体json串,注意如果没有请求体,比如是Get请求,用空串“ ”表示,不能以null表示。

拼接好待签名的字符串后,用 SecretKey 密钥生成待签名字符串的 Hmac-SHA256 签名,将签名转换为16进制字符串形式,然后进行 Base64 编码。

以Java代码为例,所有的生成签名串代码如下:

    private static String HMAC_ALGORITHM = "HmacSHA256";

    private static char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    static String bytesToHex(byte[] bytes) {

        char[] buf = new char[bytes.length * 2];
        int index = 0;
        for (byte b : bytes) {
            buf[index++] = HEX_CHAR[b >>> 4 & 0xf];
            buf[index++] = HEX_CHAR[b & 0xf];
        }

        return new String(buf);
    }

    /**
     * 生成签名,开发版本oracle jdk 1.8.0_221
     *
     * @param secretId        邮件下发的secret_id
     * @param secretKey       邮件下发的secret_key
     * @param httpMethod      http请求方法 GET/POST/PUT等
     * @param headerNonce     X-TC-Nonce请求头,随机数
     * @param headerTimestamp X-TC-Timestamp请求头,当前时间的秒级时间戳
     * @param requestUri      请求uri,eg:/v1/meetings
     * @param requestBody     请求体,没有的设为空串
     * @return 签名,需要设置在请求头X-TC-Signature中
     * @throws NoSuchAlgorithmException e
     * @throws InvalidKeyException      e
     */
    static String sign(String secretId, String secretKey, String httpMethod, String headerNonce, String headerTimestamp, String requestUri, String requestBody)
            throws NoSuchAlgorithmException, InvalidKeyException {

        String tobeSig =
                httpMethod + "\nX-TC-Key=" + secretId + "&X-TC-Nonce=" + headerNonce + "&X-TC-Timestamp=" + headerTimestamp + "\n" + requestUri + "\n" + requestBody;
        Mac mac = Mac.getInstance(HMAC_ALGORITHM);
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), mac.getAlgorithm());
        mac.init(secretKeySpec);
        byte[] hash = mac.doFinal(tobeSig.getBytes(StandardCharsets.UTF_8));
        String hexHash = bytesToHex(hash);
        return new String(Base64.getEncoder().encode(hexHash.getBytes(StandardCharsets.UTF_8)));
    }

3.3.2.第三方应用鉴权(OAuth2.0)

我的系统是PC端推流,没有做移动端推流,所以是以第一种鉴权方式企业内部鉴权,如果做移动端推流,可以使用第二种方式第三方应用鉴权,跟做微信小程序的授权方式类似,此处不做详解,以腾讯会议参考文档为准。

腾讯会议 第三方应用鉴权(OAuth2.0) - API 文档 - 文档中心 - 腾讯云

3.4.常用API

常用的API主要有以下四个模块,会议管理,直播管理,用户管理,录制管理,详情可以看API文档。

我以会议的增删改查为例,以及用户的查询的Java代码

package com.dayee.tnst.outside.tencentMeeting.api.client;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.*;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.dayee.tnst.framework.response.Result;
import com.dayee.utilities.collection.CollectionUtil;
import com.dayee.utilities.lang.StringUtil;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

@Slf4j
public class TencentMeetingApiClient {

    private static final String tencent_meeting_url = "https://api.meeting.qq.com";

    private static final String POST   = "POST";

    private static final String GET    = "GET";

    private static final String PUT    = "PUT";

    private static final String DELETE    = "DELETE";

    private String secretId;

    private String secretKey;

    private String appId;

    private String sdkId;

    public TencentMeetingApiClient(String secretId, String secretKey, String appId, String sdkId) {
        this.secretId = secretId;
        this.secretKey = secretKey;
        this.appId = appId;
        this.sdkId = sdkId;
    }

    /**
     * 创建会议
     * @param userId  腾讯会议用户唯一标识
     * @param subject 会议主题
     * @param startDate 会议开始时间
     * @param endDate   会议结束时间
     * @return
     */
    public Result<String> createMeetings(String userId, String subject,
                                         Date startDate, Date endDate) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        log.info("创建会议,userId:{},subject:{},startDate:{},endDate:{}",userId,subject,startDate,endDate);

        // 开始日期和结束日期的秒级时间戳
        int startTime = getSecondTimestamp(startDate);
        int endTime = getSecondTimestamp(endDate);

        // 六位数随机数
        String password = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));

        // body参数
        HashMap<String, Object> parameters = new HashMap<>();
        parameters.put("userid",userId);//腾讯会议用户唯一标识
        parameters.put("instanceid",1);//用户的终端设备类型 1:PC
        parameters.put("subject",subject);//会议主题
        parameters.put("start_time",String.valueOf(startTime));//会议开始时间戳(单位秒)
        parameters.put("end_time",String.valueOf(endTime));//会议结束时间戳(单位秒)
        parameters.put("type",0);// 会议类型:0:预约会议
        parameters.put("meeting_type",0);// 0:普通会议
        parameters.put("password",password); // 入会密码,自动生成随机密码,长度不能超过6个字符
        parameters.put("enable_live",true);// 会议直播-开启会议直播
        // 会议媒体参数配置
        HashMap<String, Object> settings = new HashMap<>();
        settings.put("mute_enable_join",true);//会议设置-成员加入时自动静音:是
        settings.put("allow_in_before_host",false);//会议设置-允许成员在主持人进会前加入会议:否
        settings.put("allow_screen_shared_watermark",true);//会议设置-开启屏幕水印:是
        settings.put("auto_in_waiting_room",true);//会议设置-开启等候室:是
        settings.put("only_enterprise_user_allowed",false);//入会成员限制,默认所有用户均可入会
        parameters.put("settings",settings); // 会议的配置

        // 创建会议uri
        String requestUri=   "/v1/meetings";
        // 发送请求
        return RequestUtil.requestPost(this,requestUri,parameters);
    }

    /**
     * 修改会议
     * @param meetingId  会议的唯一 ID
     * @param userId  腾讯会议用户唯一标识
     * @param subject 会议主题
     * @param startDate 会议开始时间
     * @param endDate   会议结束时间
     * @return
     */
    public Result<String> updateMeetings(String meetingId,String userId, String subject,
                                         Date startDate, Date endDate) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        log.info("修改会议,meetingId:{},userId:{},subject:{},startDate:{},endDate:{}",meetingId,userId,subject,startDate,endDate);

        // 开始日期和结束日期的秒级时间戳
        int startTime = getSecondTimestamp(startDate);
        int endTime = getSecondTimestamp(endDate);

        //body参数
        HashMap<String, Object> parameters = new HashMap<>();
        parameters.put("userid",userId);//腾讯会议用户唯一标识
        parameters.put("instanceid",1);//用户的终端设备类型 1:PC
        parameters.put("subject",subject);//会议主题
        parameters.put("start_time",String.valueOf(startTime));//会议开始时间戳(单位秒)
        parameters.put("end_time",String.valueOf(endTime));//会议结束时间戳(单位秒)
        parameters.put("type",0);// 会议类型:0:预约会议
        parameters.put("meeting_type",0);// 0:普通会议
        parameters.put("enable_live",true);// 会议直播-开启会议直播
        // 会议媒体参数配置
        HashMap<String, Object> settings = new HashMap<>();
        settings.put("mute_enable_join",true);//会议设置-成员加入时自动静音:是
        settings.put("allow_in_before_host",false);//会议设置-允许成员在主持人进会前加入会议:否
        settings.put("allow_screen_shared_watermark",true);//会议设置-开启屏幕水印:是
        settings.put("auto_in_waiting_room",true);//会议设置-开启等候室:是
        settings.put("only_enterprise_user_allowed",false);//入会成员限制,默认所有用户均可入会
        parameters.put("settings",settings); // 会议的配置

        // 修改会议地址
        String requestUri=   "/v1/meetings/{meetingId}";
        requestUri = requestUri.replace("{meetingId}", meetingId);
        // 发送请求
        return RequestUtil.requestPut(this,requestUri,parameters);
    }

    /**
     * 取消会议
     * @param meetingId  会议的唯一 ID
     * @param userId  腾讯会议用户唯一标识
     * @return
     */
    public Result<String> cancelMeetings(String meetingId,String userId) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        log.info("取消会议,meetingId:{},userId:{}",meetingId,userId);

        //body参数
        HashMap<String, Object> parameters = new HashMap<>();
        parameters.put("userid",userId);//腾讯会议用户唯一标识
        parameters.put("instanceid",1);//用户的终端设备类型 1:PC
        parameters.put("reason_code",0);// 原因代码,可为用户自定义

        // 取消会议地址
        String requestUri =  "/v1/meetings/{meetingId}/cancel";
        requestUri = requestUri.replace("{meetingId}", meetingId);
        // 发送请求
        return RequestUtil.requestPost(this,requestUri,parameters);
    }

    /**
     * 查询会议
     * @param meetingId  会议的唯一 ID
     * @param userId  腾讯会议用户唯一标识
     * @return
     */
    public Result<String> findMeetingsById(String meetingId,String userId) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        log.info("查询会议,meetingId:{},userId:{}",meetingId,userId);

        // 通过会议ID查询地址uri
        String requestUri = "/v1/meetings/{meetingId}?userid={userid}&instanceid={instanceid}";
        requestUri = requestUri.replace("{meetingId}", meetingId).replace("{userid}", userId).replace("{instanceid}", "1");
        // 发送请求
        return RequestUtil.requestGet(this,requestUri);
    }


    private static final class RequestUtil {

        static Result<String> requestGet(TencentMeetingApiClient2 instance, String requestUri)
                throws NoSuchAlgorithmException, InvalidKeyException, IOException {

            return request(instance, GET, requestUri, null);
        }

        static Result<String> requestDelete(TencentMeetingApiClient2 instance, String requestUri)
                throws NoSuchAlgorithmException, InvalidKeyException, IOException {

            return request(instance, DELETE, requestUri, null);
        }

        static Result<String> requestPost(TencentMeetingApiClient2 instance, String requestUri,
                                          Map<String, Object> parameters) throws NoSuchAlgorithmException, InvalidKeyException, IOException {

            return request(instance, POST, requestUri, parameters);
        }

        static Result<String> requestPut(TencentMeetingApiClient2 instance, String requestUri,
                                         Map<String, Object> parameters) throws NoSuchAlgorithmException, InvalidKeyException, IOException {

            return request(instance, PUT, requestUri, parameters);
        }

        private static Result<String> request(TencentMeetingApiClient2 instance,
                                              String httpMethod,
                                              String requestUri,
                                              Map<String, Object> parameters) throws NoSuchAlgorithmException, InvalidKeyException, IOException {

            log.info("操作腾讯会议信息,SecretId:{},SecretKey:{},APPID:{},SDKID:{}",instance.secretId,instance.secretKey,instance.appId,instance.sdkId);

            // 当前时间的秒级时间戳
            String timestamp=String.valueOf(System.currentTimeMillis()/1000);
            // 随机数
            String nonce=String.valueOf(new Random().nextInt(Integer.MAX_VALUE));

            HashMap<String, String> header = getHeader(nonce,timestamp,instance.secretId,instance.appId,instance.sdkId);
            String parameterStr = "";
            if (parameters != null) {
                parameterStr = JSONArray.toJSONString(parameters);
            }

            // 签名
            String sign = SignatureUtil.sign(instance.secretId, instance.secretKey, httpMethod, nonce, timestamp, requestUri, parameterStr);
            header.put("X-TC-Signature", sign);

            requestUri = tencent_meeting_url + requestUri;
            String body = null;
            switch (httpMethod){
                case GET:
                    body = sendGet(requestUri,header);
                    break;
                case POST:
                    body = sendPost(requestUri,header,parameterStr);
                    break;
                case PUT:
                    body = sendPut(requestUri,header,parameterStr);
                    break;
                case DELETE:
                    body = sendDelete(requestUri,header);
            }
            return checkRequestSuccess(body);
        }

        private static HashMap<String, String> getHeader(String nonce, String timestamp, String secretId, String appId,String sdkId) {

            HashMap<String, String> header = new HashMap<>();
            header.put("X-TC-Key",secretId);
            header.put("AppId", appId);
            header.put("X-TC-Nonce",nonce);
            header.put("X-TC-Timestamp",timestamp);
            header.put("content-type", "application/json");
            header.put("SdkId", sdkId);//有就加,没有可以不加
            header.put("X-TC-Registered", "1");
            return header;
        }
    }

    /**
     * get请求
     * */
    public static String sendGet(String url,HashMap<String, String> header){
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);
        String jsonStr = "";
        try {
            httpGet.setHeader("X-TC-Key",header.get("X-TC-Key"));
            httpGet.setHeader("AppId", header.get("AppId"));
            httpGet.setHeader("SdkId", header.get("SdkId"));//有就加,没有可以不加
            httpGet.setHeader("X-TC-Nonce",header.get("X-TC-Nonce"));
            httpGet.setHeader("X-TC-Timestamp",header.get("X-TC-Timestamp"));
            httpGet.setHeader("X-TC-Signature", header.get("X-TC-Signature"));
            httpGet.setHeader("content-type", header.get("content-type"));
            httpGet.setHeader("X-TC-Registered", header.get("X-TC-Registered"));

            CloseableHttpResponse httpResponse = null;
            httpResponse = httpClient.execute(httpGet);
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                jsonStr = EntityUtils.toString(httpEntity,"UTF-8");
            }
        } catch (IOException e) {
        }finally{
            httpGet.releaseConnection();
            try {
                httpClient.close();
            } catch (IOException e) {
            }
        }
        return jsonStr;
    }

    /**
     * post请求
     * */
    public static String sendPost(String url,HashMap<String, String> header,String parameter){
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        String jsonStr = "";
        try {
            httpPost.setHeader("X-TC-Key",header.get("X-TC-Key"));
            httpPost.setHeader("AppId", header.get("AppId"));
            httpPost.setHeader("SdkId", header.get("SdkId"));//有就加,没有可以不加
            httpPost.setHeader("X-TC-Nonce",header.get("X-TC-Nonce"));
            httpPost.setHeader("X-TC-Timestamp",header.get("X-TC-Timestamp"));
            httpPost.setHeader("X-TC-Signature", header.get("X-TC-Signature"));
            httpPost.setHeader("content-type", header.get("content-type"));
            httpPost.setHeader("X-TC-Registered", header.get("X-TC-Registered"));
            httpPost.setEntity(new StringEntity(parameter,"utf-8"));

            CloseableHttpResponse httpResponse = null;
            httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                jsonStr = EntityUtils.toString(httpEntity,"UTF-8");
            }
        } catch (IOException e) {
        }finally{
            httpPost.releaseConnection();
            try {
                httpClient.close();
            } catch (IOException e) {
            }
        }
        return jsonStr;
    }

    /**
     * put请求
     * */
    public static String sendPut(String url,HashMap<String, String> header,String parameter){
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPut httpPut = new HttpPut(url);
        String jsonStr = "";
        try {
            httpPut.setHeader("X-TC-Key",header.get("X-TC-Key"));
            httpPut.setHeader("AppId", header.get("AppId"));
            httpPut.setHeader("SdkId", header.get("SdkId"));//有就加,没有可以不加
            httpPut.setHeader("X-TC-Nonce",header.get("X-TC-Nonce"));
            httpPut.setHeader("X-TC-Timestamp",header.get("X-TC-Timestamp"));
            httpPut.setHeader("X-TC-Signature", header.get("X-TC-Signature"));
            httpPut.setHeader("content-type", header.get("content-type"));
            httpPut.setHeader("X-TC-Registered", header.get("X-TC-Registered"));
            httpPut.setEntity(new StringEntity(parameter,"utf-8"));

            CloseableHttpResponse httpResponse = null;
            httpResponse = httpClient.execute(httpPut);
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                jsonStr = EntityUtils.toString(httpEntity,"UTF-8");
            }
        } catch (IOException e) {
        }finally{
            httpPut.releaseConnection();
            try {
                httpClient.close();
            } catch (IOException e) {
            }
        }
        return jsonStr;
    }

    /**
     * delete请求
     * */
    public static String sendDelete(String url,HashMap<String, String> header){
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpDelete httpDelete = new HttpDelete(url);
        String jsonStr = "";
        try {
            httpDelete.setHeader("X-TC-Key",header.get("X-TC-Key"));
            httpDelete.setHeader("AppId", header.get("AppId"));
            httpDelete.setHeader("SdkId", header.get("SdkId"));//有就加,没有可以不加
            httpDelete.setHeader("X-TC-Nonce",header.get("X-TC-Nonce"));
            httpDelete.setHeader("X-TC-Timestamp",header.get("X-TC-Timestamp"));
            httpDelete.setHeader("X-TC-Signature", header.get("X-TC-Signature"));
            httpDelete.setHeader("content-type", header.get("content-type"));
            httpDelete.setHeader("X-TC-Registered", header.get("X-TC-Registered"));

            CloseableHttpResponse httpResponse = null;
            httpResponse = httpClient.execute(httpDelete);
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                jsonStr = EntityUtils.toString(httpEntity,"UTF-8");
            }
        } catch (IOException e) {
        }finally{
            httpDelete.releaseConnection();
            try {
                httpClient.close();
            } catch (IOException e) {
            }
        }
        return jsonStr;
    }

    /**
     * 校验请求是否成功
     */
    public static Result<String> checkRequestSuccess(String responseBody){
        if(StringUtil.isNotBlank(responseBody)){
            JSONObject jsonObject = JSONObject.parseObject(responseBody);
            String error_info = jsonObject.getString("error_info");
            if(StringUtil.isNotBlank(error_info)){
                jsonObject = JSONObject.parseObject(error_info);
                return Result.fail(jsonObject.getString("message"));
            }
        }
        return Result.ok(responseBody);
    }

   
    /**
     * 获取日期时间戳(单位秒)
     * @param date
     * @return
     */
    public static int getSecondTimestamp(Date date){
        if (null == date) {
            return 0;
        }
        String timestamp = String.valueOf(date.getTime());
        int length = timestamp.length();
        if (length > 3) {
            return Integer.valueOf(timestamp.substring(0,length-3));
        } else {
            return 0;
        }
    }


    private static final class SignatureUtil{

        private static String HMAC_ALGORITHM = "HmacSHA256";

        private static char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

        static String bytesToHex(byte[] bytes) {

            char[] buf = new char[bytes.length * 2];
            int index = 0;
            for (byte b : bytes) {
                buf[index++] = HEX_CHAR[b >>> 4 & 0xf];
                buf[index++] = HEX_CHAR[b & 0xf];
            }

            return new String(buf);
        }

        /**
         * 生成签名,开发版本oracle jdk 1.8.0_221
         *
         * @param secretId        邮件下发的secret_id
         * @param secretKey       邮件下发的secret_key
         * @param httpMethod      http请求方法 GET/POST/PUT等
         * @param headerNonce     X-TC-Nonce请求头,随机数
         * @param headerTimestamp X-TC-Timestamp请求头,当前时间的秒级时间戳
         * @param requestUri      请求uri,eg:/v1/meetings
         * @param requestBody     请求体,没有的设为空串
         * @return 签名,需要设置在请求头X-TC-Signature中
         * @throws NoSuchAlgorithmException e
         * @throws InvalidKeyException      e
         */
        static String sign(String secretId, String secretKey, String httpMethod, String headerNonce, String headerTimestamp, String requestUri, String requestBody)
                throws NoSuchAlgorithmException, InvalidKeyException {

            String tobeSig =
                    httpMethod + "\nX-TC-Key=" + secretId + "&X-TC-Nonce=" + headerNonce + "&X-TC-Timestamp=" + headerTimestamp + "\n" + requestUri + "\n" + requestBody;
            Mac mac = Mac.getInstance(HMAC_ALGORITHM);
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), mac.getAlgorithm());
            mac.init(secretKeySpec);
            byte[] hash = mac.doFinal(tobeSig.getBytes(StandardCharsets.UTF_8));
            String hexHash = bytesToHex(hash);
            return new String(Base64.getEncoder().encode(hexHash.getBytes(StandardCharsets.UTF_8)));
        }
    }
}

注意1:一定要严格按照文档给的请求参数,比如取消会议中,因为在请求体中多传了一个meetingId字段,导致有时候可以取消,有时候不能取消,所以一定要注意。

注意2:如果是主持人创建会议,用腾讯会议客户端登录进入会议,一定要传这个参数,默认传1,

否则在客户端列表中不能出现会议列表,且腾讯会议客户端无法根据你的登录手机号,匹配到对应的腾讯会议,识别出你是会议主持人

附:用户的增删改查操作

注:腾讯会议官方没有提供查询所有用户的api,只有分页查询,查询所有用户需要我们自己封装处理。

 /**
     * 根据用户ID查询用户
     * @param userid
     * @return
     */
    public Result<String> findUserByUserId(String userid) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        log.info("根据用户ID查询用户,userid:{}",userid);

        // 获取用户详情地址
        String requestUri =  "/v1/users/{userid}";
        requestUri = requestUri.replace("{userid}",userid);
        // 发送请求
        return RequestUtil.requestGet(this,requestUri);
    }

    /**
     * 查询所有用户列表
     * @return Map<手机号,userId>
     */
    public Result<Map<String,String>> listUsers() throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        log.info("查询所有用户列表");

        Integer pageNo = 1;
        Integer pageSize = 20;

        Map<String,String> userMap = new HashMap<>();
        /**
         * 腾讯会议提供了查询所有用户列表的API,分页查询最多一次查询20条
         * 轮循查询用户,直到查询完为止
         * */
        while (true){
            // 查询用户列表
            Result result = pageListUsers(pageNo, pageSize);
            if(!result.isSuccess() || result.getResult() == null){
                return result;
            }

            String responseBody = (String)result.getResult();
            JSONObject jsonObject = JSONObject.parseObject(responseBody);

            // 总条数
            Integer totalNum = Integer.valueOf(jsonObject.getString("total_count"));
            // 如果总条数为0,则直接返回
            if("0".equals(totalNum)){
                break;
            }

            // 查询所有用户
            JSONArray users = JSONObject.parseArray(jsonObject.getString("users"));
            if(CollectionUtil.isEmpty(users)){
                break;
            }
            for (int i=0; i< users.size();i++){
                JSONObject jsonObject2 = users.getJSONObject(i);
                String userid = jsonObject2.getString("userid");
                String phone = jsonObject2.getString("phone");
                String status = jsonObject2.getString("status");
                if("1".equals(status)){
                    userMap.put(phone,userid);
                }
            }

            // 校验是否有下一页
            boolean hasNext = checkNextPage(totalNum, pageSize, pageNo);
            if(!hasNext){
                break;
            }
            pageNo ++;
        }
        return Result.ok(userMap);
    }

    /**
     * 分页查询用户列表
     * @param pageNo
     * @param pageSize
     * @return
     */
    public Result<String> pageListUsers(Integer pageNo,Integer pageSize) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        log.info("查询用户列表,pageNo:{},pageSize:{}",pageNo,pageSize);

        // 查询用户列表地址
        String requestUri =  "/v1/users/list?page={0}&page_size={1}";
        requestUri = requestUri.replace("{0}",pageNo.toString()).replace("{1}",pageSize.toString());
        // 发送请求
        return RequestUtil.requestGet(this,requestUri);
    }

    /**
     * 新增用户
     */
    public Result<String> saveUser(String userId,String phone,String userName) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        log.info("新增用户,userId:{},phone:{},userName:{}",userId,phone,userName);

        //body参数
        HashMap<String, Object> parameters = new HashMap<>();
        parameters.put("userid",userId);
        parameters.put("phone",phone);
        parameters.put("username",userName);

        // 新增用户地址
        String requestUri = "/v1/users";
        // 发送请求
        return RequestUtil.requestPost(this,requestUri,parameters);
    }

    /**
     * 修改用户
     */
    public Result<String> updateUser(String userId,String phone,String userName) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        log.info("修改用户,userId:{},phone:{},userName:{}",userId,phone,userName);

        //body参数
        HashMap<String, Object> parameters = new HashMap<>();
        parameters.put("userid",userId);
        parameters.put("phone",phone);
        parameters.put("username",userName);

        // 更新用户地址
        String requestUri = "/v1/users/{userid}";
        requestUri = requestUri.replace("{userid}", userId);
        // 发送请求
        return RequestUtil.requestPut(this,requestUri,parameters);
    }

    /**
     * 删除用户
     */
    public Result<String> deleteUser(String userId) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        log.info("删除用户,userId:{}",userId);

        // 删除用户地址
        String requestUri = "/v1/users/{userid}";
        requestUri = requestUri.replace("{userid}", userId);
        // 发送请求
        return RequestUtil.requestDelete(this,requestUri);
    }

 /**
     * 判断是否有下一页
     * @param totalNum
     * @param pageSize
     * @param pageNo
     * @return
     */
    public static boolean checkNextPage(Integer totalNum,Integer pageSize,Integer pageNo) {
        if (null == totalNum) {
            return false;
        }
        int totalPage = totalNum % pageSize == 0 ? totalNum / pageSize : totalNum / pageSize + 1;
        if(pageNo == totalPage){
            return false;
        }
        return true;
    }

单元测试类:

import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.dayee.tnst.framework.response.Result;
import com.dayee.tnst.outside.tencentMeeting.api.client.TencentMeetingApiClient;
import com.dayee.tnst.outside.tencentMeeting.api.client.TencentMeetingApiClient2;
import com.dayee.utilities.lang.DateUtil;
import com.dayee.utilities.lang.StringUtil;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Map;


@RunWith(SpringRunner.class)
@SpringBootTest()
@Transactional
@Slf4j
public class TencentMeetingServiceTests {

    public TencentMeetingApiClient tencentMeetingApiClient;

    public static String SecretId = "你自己的SecretId";
    public static String SecretKey = "你自己的SecretKey";
    public static String APPID = "你自己的APPID";
    public static String SDKID = "你自己的SDKID";

    @Before
    public void init() {
        tencentMeetingApiClient = new TencentMeetingApiClient(SecretId,SecretKey,APPID,SDKID);
    }

    @Test
    public void test() throws NoSuchAlgorithmException, InvalidKeyException, IOException {

        // 测试新增/修改/删除/查询用户
        // 将用户手机号作为userId
        testOperateUser("12345678911","12345678911");

        // 测试新增/修改/删除/查询会议
        testOperateMeetings("12345678911");

    }

    /**
     * 测试新增/修改/删除用户
     * 测试根据用户ID查询用户
     * 测试查询所有用户列表
     *
     * @param userid
     * @param phone
     */
    public void testOperateUser(String userid,String phone) throws NoSuchAlgorithmException, InvalidKeyException, IOException {
        Result<String> result;

        // 新增用户
        String userName = "凤雏";
        result = tencentMeetingApiClient.saveUser(userid, phone, userName);
        System.out.println("=================测试新增用户: " + result.getResult());

        // 根据用户ID查询用户
        result = tencentMeetingApiClient.findUserByUserId(userid);
        System.out.println("=================根据用户ID查询用户结果:" + result.getResult());

        // 修改用户
        String userName2 = "凤雏2";
        result = tencentMeetingApiClient.updateUser(userid,phone,userName2);
        System.out.println("=================测试修改用户:" + result.getResult());

        // 根据用户ID查询用户
        result = tencentMeetingApiClient.findUserByUserId(userid);
        System.out.println("=================根据用户ID查询用户结果:" + result.getResult());

        // 删除用户
        result = tencentMeetingApiClient.deleteUser(userid);
        System.out.println("=================测试删除用户:" + result.getResult());

        // 根据用户ID查询用户
        result = tencentMeetingApiClient.findUserByUserId(userid);
        System.out.println("=================根据用户ID查询用户结果:" + result.getResult());

        // 查询所有用户列表
        Result<Map<String,String>> result2 = tencentMeetingApiClient.listUsers();
        System.out.println("=================查询所有用户列表结果:" + result2.getResult());
    }


    /**
     * 测试新增/修改/删除/查询会议
     * @param
     */
    public void testOperateMeetings(String userid) throws NoSuchAlgorithmException, InvalidKeyException, IOException {
        Result<String> result;

        // 测试创建会议
        // 会议名称
        String subject = "风云";
        // 会议开始时间,今天
        Date startDate = new Date();
        // 会议结束时间,明天
        Date endDate = DateUtil.getDateAfter(startDate,1);
        // 创建会议
        result = tencentMeetingApiClient.createMeetings(userid, subject, startDate, endDate);
        System.out.println("========创建会议结果=========" + result.getResult());

        // 解析会议ID
        String meetingId = parseTencMeetString(result.getResult());
        System.out.println("========解析会议ID结果========" + meetingId);

        // 测试查询会议
        result = tencentMeetingApiClient.findMeetingsById(meetingId,userid);
        System.out.println("========查询会议结果========" + result.getResult());

        // 测试修改会议
        // 会议名称
        String subject2 = "风云2";
        // 会议开始时间,今天
        Date startDate2 = new Date();
        // 会议结束时间,后天
        Date endDate2 = DateUtil.getDateAfter(startDate,2);
        // 修改会议
        result = tencentMeetingApiClient.updateMeetings(meetingId,userid,subject2,startDate2,endDate2);
        System.out.println("========修改会议结果========" + result.getResult());

        // 测试查询会议
        result = tencentMeetingApiClient.findMeetingsById(meetingId,userid);
        System.out.println("========查询会议结果========" + result.getResult());

        // 测试取消会议
        result = tencentMeetingApiClient.cancelMeetings(meetingId, userid);
        System.out.println("=================取消会议结果:" + result.getResult());
    }


    // 解析创建会议返回数据,获取会议ID
    public static String parseTencMeetString(String responseBody){
        JSONObject jsonObject = JSONObject.parseObject(responseBody);
        String meetingList = jsonObject.getString("meeting_info_list");
        if(StringUtil.isNotBlank(meetingList)){
            JSONArray jsonArray = JSONObject.parseArray(meetingList);
            for (int i=0; i< jsonArray.size();i++){
                JSONObject jsonObject2 = jsonArray.getJSONObject(i);
                String meetingId = jsonObject2.getString("meeting_id");
                return meetingId;
            }
        }
        return null;
    }
}

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值