最近公司需要做一个腾讯会议API的对接,经过查看官方文档和腾讯相关技术人员周旋~~以及不断踩坑,终于在我的不懈努力下,成功将API接口调通了
腾讯会议API接口文档:https://cloud.tencent.com/document/product/1095/42407
公共参数是用于标识用户和接口鉴权目的的参数,如非必要,在每个接口单独的接口文档中不再对这些参数进行说明,但每次请求均需要携带这些参数,才能正常发起请求。
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. 通过企业管理后台添加或批量导入企业用户。 |
可参考腾讯会议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鉴权方式需要在每次请求时加入公共请求头参数,以及通过签名算法生成的签名也需携带入请求头,借此,我封装了生成请求头的方法。
其中secretId
,secretKey
,appId
,sdkId
是创建企业自建应用生成的,参考腾讯会议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;
}
为了方便调用API,我封装了GET和POST方法
/**
* 发起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;
}
/**
* 腾讯会议发送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:https://cloud.tencent.com/document/product/1095/42414
需要注意的是会议主题 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;
}
}
/**
* 根据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;
}
/**
* 取消预约的会议
*
* @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),找腾讯售后寻求技术支持,客服还是很负责任的。
文章浏览阅读1.1w次,点赞7次,收藏34次。vue-grid-layout的使用、实例、遇到的问题和解决方案_vue-grid-layout
文章浏览阅读218次。然后连接一个数据源,就会在下面自动产生一个添加附件的组件。把这个控件复制粘贴到页面里,就可以单独使用来上传了。插入一个“编辑”窗体。_powerapps点击按钮上传附件
文章浏览阅读264次。(1) Abstraction (抽象)(2) Polymorphism (多态)(3) Inheritance (继承)(4) Encapsulation (封装)_"object(cnofd[\"ofdrender\"])十条"
文章浏览阅读133次。删除node_modules,重新npm install看是否成功。在 package.json 文件中的 scripts 中加入。修改你的第三方库的bug等。然后目录会多出一个目录文件。_修改 node_modules
文章浏览阅读883次。【代码】【】kali--password:su的 Authentication failure问题,&sudo passwd root输入密码时Sorry, try again._password: su: authentication failure
文章浏览阅读1w次,点赞13次,收藏97次。整理5个优秀的微信小程序开源项目。收集了微信小程序开发过程中会使用到的资料、问题以及第三方组件库。_微信小程序开源模板
文章浏览阅读128次。Centos7最简搭建NFS服务器_centos7 搭建nfs server
文章浏览阅读1.2k次,点赞2次,收藏3次。前言mybatis在持久层框架中还是比较火的,一般项目都是基于ssm。虽然mybatis可以直接在xml中通过SQL语句操作数据库,很是灵活。但正其操作都要通过SQL语句进行,就必须写大量的xml文件,很是麻烦。mybatis-plus就很好的解决了这个问题。..._mybaitis-plus ruledataobjectattributemapper' and 'com.picc.rule.management.d
文章浏览阅读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
文章浏览阅读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 ...
文章浏览阅读1.3k次。首先,问题是如何出现的?晚上复查代码,发现一个activity没有调用自己的ondestroy方法我表示非常的费解,于是我检查了下代码。发现再finish代码之后接了如下代码finish();System.exit(0);//这就是罪魁祸首为什么这样写会出现问题System.exit(0);////看一下函数的原型public static void exit (int code)//Added ..._android 手动杀死app,activity不执行ondestroy
文章浏览阅读894次。Q: SylixOS 版权是什么形式, 是否分为<开发版税>和<运行时版税>.A: SylixOS 是开源并免费的操作系统, 支持 BSD/GPL 协议(GPL 版本暂未确定). 没有任何的运行时版税. 您可以用她来做任何 您喜欢做的项目. 也可以修改 SylixOS 的源代码, 不需要支付任何费用. 当然笔者希望您可以将使用 SylixOS 开发的项目 (不需要开源)或对 SylixOS 源码的修改及时告知笔者.需要指出: SylixOS 本身仅是笔者用来提升自己水平而开发的_select函数 导致堆栈溢出 sylixos