Java腾讯会议api接口,创建会议、查询会议,取消会议调用实例,以及踩过的坑_腾讯会议调用api实现视频咨询-程序员宅基地

技术标签: Java  java  腾讯云  


最近公司需要做一个腾讯会议API的对接,经过查看官方文档和腾讯相关技术人员周旋~~以及不断踩坑,终于在我的不懈努力下,成功将API接口调通了

腾讯会议API接口文档:https://cloud.tencent.com/document/product/1095/42407

一、腾讯会议JWT鉴权

  • 腾讯会议API接口文档中详细写了使用API需要做的准备: 包括-创建应用,以及获取相应应用的SecretId,SecretKey,AppId等
  • 根据不同的应用创建方式,选择的鉴权方式也不同。
  • 腾讯会议API提供了两套鉴权方式,在这里我只介绍企业自建应用鉴权(JWT)

1、公共参数

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

参数名称 类型 必选 描述
Content-Type String 内容类型,传入格式必须为 application/json。
X-TC-Action String 操作的接口名称。取值参考接口文档中输入参数公共参数 Action 的说明。例如云服务器的查询实例列表接口,取值为 DescribeInstances。
X-TC-Region String 地域参数,用来标识希望操作哪个地域的数据。接口接受的地域取值参考接口文档中输入参数公共参数 Region 的说明。注意:某些接口不需要传递该参数,接口文档中会对此特别说明,此时即使传递该参数也不会生效。
X-TC-Key String 此参数参与签名计算。腾讯云 API 接入,申请的安全凭证密钥对中的 SecretId,其 Secretkey 用于签名。企业管理员可以登录 腾讯会议官网,单击右上角用户中心,在左侧菜单栏中的企业管理 > 高级 > restApi中进行查看。
X-TC-Timestamp String 此参数参与签名计算。当前 UNIX 时间戳,可记录发起 API 请求的时间。例如1529223702,单位为秒。注意:如果与服务器时间相差超过5分钟,会引起签名过期错误。
X-TC-Nonce String 此参数参与签名计算。随机正整数。
X-TC-Version String 应用 App 的版本号,建议设置,以便灰度和查找问题。
X-TC-Signature String 放置由下面的签名方法产生的签名。
X-TC-Token String 临时证书所用的 Token ,需要结合临时密钥一起使用。临时密钥和 Token 需要到访问管理服务调用接口获取。长期密钥不需要 Token。
AppId String 腾讯会议分配给三方开发应用的 App ID。企业管理员可以登录 腾讯会议官网,单击右上角用户中心,在左侧菜单栏中的企业管理 > 高级 > restApi中进行查看。
SdkId String 用户子账号或开发的应用 ID,企业管理员可以登录 腾讯会议官网,单击右上角用户中心,在左侧菜单栏中的企业管理 > 高级 > restApi中进行查看(如存在 SdkId 则必须填写,早期申请 API 且未分配 SdkId 的客户可不填写)。
X-TC-Registered String 启用账户通讯录,传入值必须为1,创建的会议可出现在用户的会议列表中。 启用账户通讯录说明: 1. 通过 SSO 接入腾讯会议账号体系。 2. 通过调用接口创建企业用户。 3. 通过企业管理后台添加或批量导入企业用户。

2、签名算法

可参考腾讯会议API-企业自建应用鉴权(JWT)

https://cloud.tencent.com/document/product/1095/42413

import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

/**
   * 生成签名,开发版本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)));
  }

  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);
  }

有了签名算法,我们可以生成公共请求头了

二、封装获取公共请求头方法

JWT鉴权方式需要在每次请求时加入公共请求头参数,以及通过签名算法生成的签名也需携带入请求头,借此,我封装了生成请求头的方法。

其中secretIdsecretKeyappIdsdkId是创建企业自建应用生成的,参考腾讯会议API文档-创建企业自建应用

需要注意的地方:

1、Timestamp参数是Unix时间戳,单位是秒

2、GET方法请求体需传""

/**
   * 获取公共请求头
   *
   * @param httpMethod  请求方式:POST|GET
   * @param requestUri  请求uri
   * @param requestBody 请求体 GET方法请求体需传""
   * @return 拼接好的请求头
   */
  public static Map<String, String> getHeader(String httpMethod, String requestUri,
      String requestBody) {
    
    HashMap<String, String> header = new HashMap<>(8);
//      请求随机数
    String headerNonce = String.valueOf(new Random().nextInt(999999));
    //  当前时间的UNIX时间戳
    String headerTimestamp = String.valueOf(System.currentTimeMillis() / 1000);
    String signature = null;
    try {
    
      signature = sign(secretId, secretKey, httpMethod, headerNonce, headerTimestamp, requestUri,
          requestBody);
    } catch (Exception e) {
    
      log.error("签名生成异常", e);
    }

    header.put("Content-Type", "application/json");
    header.put("X-TC-Key", secretId);
    header.put("X-TC-Timestamp", headerTimestamp);
    header.put("X-TC-Nonce", headerNonce);
    header.put("AppId", appId);
    header.put("X-TC-Version", "1.0");
    header.put("X-TC-Signature", signature);
    header.put("SdkId", sdkId);
    header.put("X-TC-Registered", "1");

    return header;
  }

三、封装HTTP请求方法

为了方便调用API,我封装了GET和POST方法

1、GET方法

/**
   * 发起get请求
   * @param uri 	请求uri 生成签名使用
   * @param address 请求路径
   * @return 		请求结果的JsonStr
   */
  public static String sendGet(String address,String uri) {
    
    //get请求,请求体为""
    Map<String, String> header = getHeader("GET",uri,"");
    String result = "";
    String logInfo = "";
    GetMethod getMethod = null;
    try {
    
      // 创建httpClient实例对象
      HttpClient httpClient = new HttpClient();
      // 设置httpClient连接主机服务器超时时间:15000毫秒
      httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000);
      // 创建GET请求方法实例对象
      getMethod = new GetMethod(address);
      // 设置post请求超时时间
      getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 60000);
      if (header != null) {
    
        for (Map.Entry<String, String> entry : header.entrySet()) {
    
          getMethod.addRequestHeader(entry.getKey(), entry.getValue());
        }
      }
      logInfo = "HTTP调用接口:" + address;
      httpClient.executeMethod(getMethod);
      result = getMethod.getResponseBodyAsString();
    } catch (Exception e) {
    
      log.info("HTTP调用接口出错:" + logInfo + e, e);
    } finally {
    
      if (null != getMethod) {
    
        getMethod.releaseConnection();
      }
    }
    return result;
  }

2、POST方法

 /**
   * 腾讯会议发送post请求  携带生产签名和公共请求头参数
   *
   * @param address     请求地址
   * @param uri         请求uri生产签名使用
   * @param requestBody 请求参数
   * @return 			请求响应结果
   */
  public static String sendPost(String address, String uri, String requestBody) {
    

    //生成公共请求头参数和签名
    HashMap<String, String> headerMap = getHeaderTest("POST", uri, requestBody);
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpPost httpPost = new HttpPost(address);
    String jsonStr = "";
    try {
    
      for (Entry<String, String> header : headerMap.entrySet()) {
    
        httpPost.setHeader(header.getKey(), header.getValue());
      }

      httpPost.setEntity(new StringEntity(requestBody));
      CloseableHttpResponse httpResponse = null;
      httpResponse = httpClient.execute(httpPost);
      HttpEntity httpEntity = httpResponse.getEntity();
      if (httpEntity != null) {
    
        jsonStr = EntityUtils.toString(httpEntity, "UTF-8");
      }
      log.info("腾讯会议httpPost请求相应信息{}", jsonStr);
    } catch (IOException e) {
    
      log.info("腾讯会议httpPost发送异常", e);
    } finally {
    
      httpPost.releaseConnection();
      try {
    
        httpClient.close();
      } catch (IOException e) {
    
        log.error("httpClient关闭异常", e);
      }
    }
    return jsonStr;
  }

有了公共请求头,我们就可以调用API了

四、调用REST APIs

REST APIs:https://cloud.tencent.com/document/product/1095/42414

1、创建会议

需要注意的是会议主题 subject,直接传入中文,会报API签名验证失败,这个是一个坑点,当初搞了我好久,一直没查到原因。

解决办法是取中文的Unicode

/**
   * 创建预约腾讯会议
   *
   * @param subject   主题
   * @param startTime 开始时间戳
   * @param duration  持续时间(单位分钟)
   * @param userId    企业注册时的用户名
   * @return 创建结果
   */
  public static Map<String, Object> creatMeeting(String subject, Long startTime,
      Integer duration, String userId) {
    
//    Unix时间戳,单位为秒
    startTime = startTime / 1000;
    String endTime = String.valueOf(startTime + (duration * 60));
    HashMap<String, Object> resultMap = new HashMap<>(8);
//    请求体,get方法请求体需传""
    String resultBody = "{" +
        //会议结束时间
        "\"end_time\": \"" + endTime + "\"" + "," +
        //会议开始时间戳(单位秒)。
        "\"start_time\": \"" + startTime + "\"," +
        //用户的终端设备类型 1:PC
        "\"instanceid\": " + "1" + "," +
        // 会议类型:0:预约会议 1:快速会议
        "\"type\": " + "0" + "," +
        //腾讯会议用户唯一标识 不能为1--9内的数字
        "\"userid\": \"" + userId + "\"," +
        //会议主题
        "\"subject\": \"" + getUnicode(subject) + "\"" +
        "}";

//    创建会议uri
    String uri = "/v1/meetings";
    String address = MEETING_DOMAIN_URL + uri;

    try {
    
      String jsonStr = requestPost(address, uri, resultBody);
      JSONObject jsonObject = JSONObject.parseObject(jsonStr);
      if (null == jsonObject.getJSONObject("error_info")) {
    
        JSONObject meetingInfo = jsonObject.getJSONArray("meeting_info_list").getJSONObject(0);
        resultMap.put("success", "true");
        resultMap.put("message", "创建预约会议成功");
        resultMap.put("meetingId", meetingInfo.getString("meeting_id"));
        resultMap.put("meetingCode", meetingInfo.getString("meeting_code"));
        resultMap.put("joinUrl", meetingInfo.getString("join_url"));
      } else {
    
        resultMap.put("success", "false");
        resultMap.put("message", "创建预约会议失败");
        resultMap.put("errorInfo", jsonObject.getJSONObject("error_info"));
      }
    } catch (Exception e) {
    
      resultMap.put("success", "false");
      resultMap.put("message", e.getMessage());
      log.error("创建预约会议发生异常", e);
    }
    return resultMap;
  }

  /**
   * 防止api请求中传入中文导致的报错问题
   * @param s 中文字符串
   * @return Unicode码
   */
public static String getUnicode(String s) {
    
    try {
    
      StringBuffer out = new StringBuffer("");
      byte[] bytes = s.getBytes("unicode");
      for (int i = 0; i < bytes.length - 1; i += 2) {
    
        out.append("\\u");
        String str = Integer.toHexString(bytes[i + 1] & 0xff);
        for (int j = str.length(); j < 2; j++) {
    
          out.append("0");
        }
        String str1 = Integer.toHexString(bytes[i] & 0xff);
        out.append(str1);
        out.append(str);
      }
      return out.toString();
    } catch (UnsupportedEncodingException e) {
    
      e.printStackTrace();
      return null;
    }
  }

2、查询会议

  /**
   * 根据meetingId查询腾讯会议
   *
   * @param meetingId 创建腾讯会议生成的meetingId
   * @param userId    创建人的id
   * @return 查询结果
   */
  public static Map<String, Object> queryMeetings(String meetingId, String userId) {
    
    HashMap<String, Object> resultMap = new HashMap<>(8);
    try {
    
      String uri = "/v1/meetings/" + meetingId + "?userid=" + userId + "&instanceid=1";

//    https://api.meeting.qq.com/v1/meetings/{meetingId}?userid={userid}&instanceid={instanceid}
//      String address = url + "/" + meetingId;
      String address = MEETING_DOMAIN_URL + uri;


      String jsonStr = sendGet(address, uri);
      JSONObject jsonObject = JSONObject.parseObject(jsonStr);

      resultMap.put("message", jsonObject.getString("error_info"));
    } catch (Exception e) {
    
      log.error("获取会议信息异常", e);
      resultMap.put("success", "false");
      resultMap.put("message", e.getMessage());
    }
    return resultMap;
  }

3、取消会议

/**
   * 取消预约的会议
   *
   * @param userId    预定人id
   * @param meetingId 会议id
   * @return 取消结果
   */
  public static Map<String, Object> cancelMeeting(String userId, String meetingId) {
    

    HashMap<String, Object> resultMap = new HashMap<>(8);
//    https://api.meeting.qq.com/v1/meetings/{meetingId}/cancel
    String address;
//    创建会议uri
    String uri = "/v1/meetings/" + meetingId + "/cancel";
    address = MEETING_DOMAIN_URL + uri;
    try {
    
//    请求体,get方法请求体需传""
      String resultBody = "{\n"
          + "     \"meetingId\" : \"" + meetingId + "\",\n"
          + "     \"userid\" : \"" + userId + "\",\n"
          + "     \"instanceid\" : 1,\n"
          + "     \"reason_code\" : 1\n"
          + "}";

      String jsonStr = requestPost(address, uri, resultBody);
      JSONObject jsonObject = JSONObject.parseObject(jsonStr);
      if (null == jsonObject.getJSONObject("error_info")) {
    
        resultMap.put("message", "取消会议成功");
        resultMap.put("success", "true");
      } else {
    
        resultMap.put("message", "取消会议失败");
        resultMap.put("errorInfo", jsonObject.getJSONObject("error_info"));
        resultMap.put("success", "false");

      }
    } catch (Exception e) {
    
      resultMap.put("success", "false");
      resultMap.put("message", e.getMessage());
      log.error("取消会议异常:", e);
    }
    return resultMap;
  }

到此我的文章就结束了,希望可以帮助到大家,少走一些弯路。

大家还有一些不懂的地方,可以到腾讯云提交工单(https://console.cloud.tencent.com/workorder/category),找腾讯售后寻求技术支持,客服还是很负责任的。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/pityKate/article/details/123823161

智能推荐

前端开发之vue-grid-layout的使用和实例-程序员宅基地

文章浏览阅读1.1w次,点赞7次,收藏34次。vue-grid-layout的使用、实例、遇到的问题和解决方案_vue-grid-layout

Power Apps-上传附件控件_powerapps点击按钮上传附件-程序员宅基地

文章浏览阅读218次。然后连接一个数据源,就会在下面自动产生一个添加附件的组件。把这个控件复制粘贴到页面里,就可以单独使用来上传了。插入一个“编辑”窗体。_powerapps点击按钮上传附件

C++ 面向对象(Object-Oriented)的特征 & 构造函数& 析构函数_"object(cnofd[\"ofdrender\"])十条"-程序员宅基地

文章浏览阅读264次。(1) Abstraction (抽象)(2) Polymorphism (多态)(3) Inheritance (继承)(4) Encapsulation (封装)_"object(cnofd[\"ofdrender\"])十条"

修改node_modules源码,并保存,使用patch-package打补丁,git提交代码后,所有人可以用到修改后的_修改 node_modules-程序员宅基地

文章浏览阅读133次。删除node_modules,重新npm install看是否成功。在 package.json 文件中的 scripts 中加入。修改你的第三方库的bug等。然后目录会多出一个目录文件。_修改 node_modules

【】kali--password:su的 Authentication failure问题,&sudo passwd root输入密码时Sorry, try again._password: su: authentication failure-程序员宅基地

文章浏览阅读883次。【代码】【】kali--password:su的 Authentication failure问题,&sudo passwd root输入密码时Sorry, try again._password: su: authentication failure

整理5个优秀的微信小程序开源项目_微信小程序开源模板-程序员宅基地

文章浏览阅读1w次,点赞13次,收藏97次。整理5个优秀的微信小程序开源项目。收集了微信小程序开发过程中会使用到的资料、问题以及第三方组件库。_微信小程序开源模板

随便推点

Centos7最简搭建NFS服务器_centos7 搭建nfs server-程序员宅基地

文章浏览阅读128次。Centos7最简搭建NFS服务器_centos7 搭建nfs server

Springboot整合Mybatis-Plus使用总结(mybatis 坑补充)_mybaitis-plus ruledataobjectattributemapper' and '-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏3次。前言mybatis在持久层框架中还是比较火的,一般项目都是基于ssm。虽然mybatis可以直接在xml中通过SQL语句操作数据库,很是灵活。但正其操作都要通过SQL语句进行,就必须写大量的xml文件,很是麻烦。mybatis-plus就很好的解决了这个问题。..._mybaitis-plus ruledataobjectattributemapper' and 'com.picc.rule.management.d

EECE 1080C / Programming for ECESummer 2022 Laboratory 4: Global Functions Practice_eece1080c-程序员宅基地

文章浏览阅读325次。EECE 1080C / Programming for ECESummer 2022Laboratory 4: Global Functions PracticePlagiarism will not be tolerated:Topics covered:function creation and call statements (emphasis on global functions)Objective:To practice program development b_eece1080c

洛谷p4777 【模板】扩展中国剩余定理-程序员宅基地

文章浏览阅读53次。被同机房早就1年前就学过的东西我现在才学,wtcl。设要求的数为\(x\)。设当前处理到第\(k\)个同余式,设\(M = LCM ^ {k - 1} _ {i - 1}\) ,前\(k - 1\)个的通解就是\(x + i * M\)。那么其实第\(k\)个来说,其实就是求一个\(y\)使得\(x + y * M ≡ a_k(mod b_k)\)转化一下就是\(y * M ...

android 退出应用没有走ondestory方法,[Android基础论]为何Activity退出之后,系统没有调用onDestroy方法?...-程序员宅基地

文章浏览阅读1.3k次。首先,问题是如何出现的?晚上复查代码,发现一个activity没有调用自己的ondestroy方法我表示非常的费解,于是我检查了下代码。发现再finish代码之后接了如下代码finish();System.exit(0);//这就是罪魁祸首为什么这样写会出现问题System.exit(0);////看一下函数的原型public static void exit (int code)//Added ..._android 手动杀死app,activity不执行ondestroy

SylixOS快问快答_select函数 导致堆栈溢出 sylixos-程序员宅基地

文章浏览阅读894次。Q: SylixOS 版权是什么形式, 是否分为<开发版税>和<运行时版税>.A: SylixOS 是开源并免费的操作系统, 支持 BSD/GPL 协议(GPL 版本暂未确定). 没有任何的运行时版税. 您可以用她来做任何 您喜欢做的项目. 也可以修改 SylixOS 的源代码, 不需要支付任何费用. 当然笔者希望您可以将使用 SylixOS 开发的项目 (不需要开源)或对 SylixOS 源码的修改及时告知笔者.需要指出: SylixOS 本身仅是笔者用来提升自己水平而开发的_select函数 导致堆栈溢出 sylixos

推荐文章

热门文章

相关标签