华为云会议对接,华为云会议事件消息订阅

       最近做了一个对接华为云视频会议接口,订阅华为云会议事件消息的功能。做之前在网上搜索了一番,居然发现没有一个这方面的资料。决定整理一下分享出来,留给有缘人

       具体的需求是,我们的app上集成了华为云会议sdk,在用户开启聊天的时候没有收到相关消息通知,解决思路是,当用户在app上调华为云会议sdk接口发起通话成功之后,调用服务端接口进行会议事件消息的订阅,由我们服务端与华为云会议服务端进行socket通信,通过订阅华为云会议事件消息给用户发送相关通知

      这里只列出 服务端与华为云会议服务端交互,订阅华为云会议事件消息的相关代码不涉及相关业务逻辑

1、获取accessToken 接口(在调用华为云会议的相关接口之前需要通过appid与appKey 进行鉴权获取accessToken)  - http接口

参考:执行App ID鉴权_华为云会议 Meeting

String nonce = UUID.randomUUID().toString();
Map<String, String> headerMap = new HashMap<>(2);
//30分钟过期
long expireTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() + 30 * 60 * 1000;
String data = apiAuthConfig.getAppId() + ":" + ":" + expireTime + ":" + nonce;
String encode = HmacSha256.encode(data, apiAuthConfig.getAppKey());
headerMap.put("Authorization", "HMAC-SHA256 signature=" + encode);
headerMap.put("Content-Type", "application/json; charset=UTF-8");
Map<String, Object> bodyMap = new HashMap<>(8);
bodyMap.put("appId", apiAuthConfig.getAppId());
bodyMap.put("clientType", 72);
bodyMap.put("expireTime", expireTime);
bodyMap.put("nonce", nonce);
String result = CreateSslClientDefault.doPost(apiAuthConfig.getDomain() + GET_API_ACCESS_TOKEN_URL, Jsons.toJson(bodyMap), headerMap);

2、获取会控token接口 - http接口

参考:获取会控Token_华为云会议 Meeting

Map<String, String> headerMap = new HashMap<>(2);
headerMap.put("X-Password", conferencePassword);
headerMap.put("X-Login-Type", "1");
Map<String, Object> queryMap = new HashMap<>(1);
queryMap.put("conferenceID", conferenceId);
String url = apiAuthConfig.getDomain() + GET_CONTROL_CONFERENCES_TOKEN_URL;
String result = CreateSslClientDefault.doGet(url, queryMap, headerMap);
log.info("getControlConferencesToken result : {}", result);

3、获取 websocket建联token

参考:获取WebSocket建链Token_华为云会议 Meeting

Map<String, String> headerMap = new HashMap<>(2);
headerMap.put("X-Conference-Authorization", controlToken);
Map<String, Object> queryMap = new HashMap<>(1);
queryMap.put("conferenceID", confID);
String url = apiAuthConfig.getDomain() + GET_WS_CONFERENCES_TOKEN_URL;
String result = CreateSslClientDefault.doGet(url, queryMap, headerMap);

4、websocket消息事件订阅

参考:信息订阅_华为云会议 Meeting

public class MeetingWebSocketClient implements Runnable {private MeetingHandlerContext context;public MeetingWebSocketClient(MeetingHandlerContext meetingHandlerContext){this.context = meetingHandlerContext;}@Overridepublic void run() {try{log.info("HwMeetingWebSocketClient start, confID : {}", context.getConfID());WebSocketClient client = new StandardWebSocketClient();// 添加自定义头部信息,如有需要WebSocketHttpHeaders headers = new WebSocketHttpHeaders();String url = context.getWsURL() + String.format(HwMeetingConstant.GET_CONTROL_INCREMENT_CONN_URL + "?confID=%s&tmpToken=%s", context.getConfID(), context.getWsToken());// 连接到WebSocket服务器ListenableFuture<WebSocketSession> future = client.doHandshake(new MeetingWebSocketHandler(context), url, headers);// 发送要订阅的消息sendSubscribeMessage(future.get());}catch (Exception e){e.printStackTrace();}}private void sendSubscribeMessage(WebSocketSession session) throws IOException {// 发送订阅消息String[] subscribeTypes = new String[]{/* ConfBasicInfoNotify,*/ConfDynamicInfoNotify,ParticipantsNotify,AttendeesNotify,InviteResultNotify,};Map<String, Object> data = new HashMap();data.put("subscribeType", subscribeTypes);data.put("confToken", context.getClToken());MeetingWebSocketMessage webSocketMessage = new MeetingWebSocketMessage();webSocketMessage.setAction("Subscribe");webSocketMessage.setSequence(UUID.randomUUID().toString());webSocketMessage.setData(Jsons.toJson(data));TextMessage subscribeMessage = new TextMessage(Jsons.toJson(webSocketMessage));session.sendMessage(subscribeMessage);}
}

5、websocket消息事件处理

public class MeetingWebSocketHandler implements WebSocketHandler {static int cpuNum = Runtime.getRuntime().availableProcessors();// 所有的 socket 链接的心跳都用同一个线程池去处理public static final ScheduledExecutorService HEART = Executors.newScheduledThreadPool(cpuNum + 1, (r) ->{Thread thread = new Thread(r);thread.setName("thread-heart");return thread;});private WebSocketNotifyHandler handler;private MeetingHandlerContext context;private ScheduledFuture<?> heartbeat;public MeetingWebSocketHandler(MeetingHandlerContext context){this.context = context;}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {log.info("web socket connection open");// 加载所有的事件处理器handler = new WebSocketNotifyHandler.Builder().addHandler(new AttendeesNotifyHandler(context)).addHandler(new ParticipantsNotifyHandler(context)).addHandler(new InviteResultNotifyHandler(context)).addHandler(new ConfDynamicInfoNotifyHandler(context)).get();// 启动发送心跳的任务(发送心跳消息 一分钟发送一次)startHeartBeatTask(session);// 通知所有的handler当前链接已建立handler.setConnOpen();}@Overridepublic void handleMessage(WebSocketSession session, final WebSocketMessage<?> message) throws Exception {MeetingWebSocketMessage event = JSON.parseObject(message.getPayload().toString(), MeetingWebSocketMessage.class);// 接受到 心跳结果消息、订阅结果消息 直接忽略if(event.getAction().equals(HeartBeat) || event.getAction().equals(Subscribe)){return;}String messageIdKey = RedisKeyHelper.getConferencesMessageIdKey(event.getMsgID());// 如果是重复消息则不处理if(duplicateMessageCheck(event, messageIdKey)){return;}// 执行消息处理器doHandler(handler, message);}private boolean duplicateMessageCheck(MeetingWebSocketMessage event, String redisCacheKey) {if(event == null || StringUtils.isBlank(event.getMsgID())){return true;}if(StringUtils.isNotBlank(context.getRedisContent().opsForValue().get(redisCacheKey))){return true;}return false;}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {// 链接异常log.error("web socket transport error : {}", exception.getMessage());}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {log.info("web socket connection closed");// 当连服务端关闭连接时,中断心跳请求if(heartbeat != null){heartbeat.cancel(true);}// 通知所有的handler当前链接已断开handler.setConnClose();}@Overridepublic boolean supportsPartialMessages() {return false;}private void doHandler(WebSocketNotifyHandler handler, WebSocketMessage message){handler.doHandler(message);if(handler.next == null){return;}doHandler(handler.next, message);}private void startHeartBeatTask(WebSocketSession session){// 发送心跳消息 一分钟发送一次heartbeat = HEART.scheduleWithFixedDelay(() ->{log.info("confID : {}, HeartBeat...", context.getConfID());if(session.isOpen()){MeetingWebSocketMessage heartBeatMessageData = new MeetingWebSocketMessage();heartBeatMessageData.setAction("HeartBeat");heartBeatMessageData.setSequence(UUID.randomUUID().toString());TextMessage  heartMessage = new TextMessage(Jsons.toJson(heartBeatMessageData));try {session.sendMessage(heartMessage);} catch (Exception e) {log.error("HeartBeat error : {}", e.getMessage());}}}, 1, 1, TimeUnit.MINUTES);}
}
// 会议邀请消息推送
@Slf4j
public class AttendeesNotifyHandler extends WebSocketNotifyHandler<WebSocketMessage> {@Overridepublic void doHandler(final WebSocketMessage socketMessage) {MeetingWebSocketMessage event = JSON.parseObject(socketMessage.getPayload().toString(), MeetingWebSocketMessage.class);if (!event.getAction().equals(AttendeesNotify)) {return;}AttendeesNotifyMessage message = JSON.parseObject(socketMessage.getPayload().toString(), AttendeesNotifyMessage.class);if(message == null || message.getData() == null || message.getData().size() <= 0){return;}log.info("AttendeesNotifyHandler msg: {}", Jsons.toJson(message));}
}
// 会议状态信息推送
@Slf4j
public class ConfDynamicInfoNotifyHandler extends WebSocketNotifyHandler<WebSocketMessage> {@Overridepublic void doHandler(final WebSocketMessage socketMessage) {MeetingWebSocketMessage event = JSON.parseObject(socketMessage.getPayload().toString(), MeetingWebSocketMessage.class);if (!event.getAction().equals(ConfDynamicInfoNotify)) {return;}MeetingStatusNotifyMessage message = JSON.parseObject(socketMessage.getPayload().toString(), MeetingStatusNotifyMessage.class);log.info("ConfDynamicInfoNotifyHandler msg: {}", Jsons.toJson(message));}
}
// 会议邀请结果消息推送
@Slf4j
public class InviteResultNotifyHandler extends WebSocketNotifyHandler<WebSocketMessage> {@Overridepublic void doHandler(final WebSocketMessage socketMessage) {MeetingWebSocketMessage event = JSON.parseObject(socketMessage.getPayload().toString(), MeetingWebSocketMessage.class);if (!event.getAction().equals(InviteResultNotify)) {return;}InviteResultNotifyMessage message = JSON.parseObject(socketMessage.getPayload().toString(), InviteResultNotifyMessage.class);if(message == null || message.getData() == null || message.getData().size() <= 0){return;}log.info("InviteResultNotifyHandler msg: {}", Jsons.toJson(message));}
}
// 在线人数消息推送
@Slf4j
public class ParticipantsNotifyHandler extends WebSocketNotifyHandler<WebSocketMessage> {@Overridepublic void doHandler(final WebSocketMessage socketMessage) {MeetingWebSocketMessage event = JSON.parseObject(socketMessage.getPayload().toString(), MeetingWebSocketMessage.class);if (!event.getAction().equals(ParticipantsNotify)) {return;}ParticipantsNotifyMessage message = JSON.parseObject(socketMessage.getPayload().toString(), ParticipantsNotifyMessage.class);if(message == null || message.getData() == null || message.getData().size() <= 0){return;}log.info("ParticipantsNotifyHandler msg: {}", Jsons.toJson(message));}
}
// 消息事件处理器构造类public abstract class WebSocketNotifyHandler<T> {private static AtomicBoolean SOCKET_STATUS = new AtomicBoolean(false);protected WebSocketNotifyHandler<T> next;private void next(WebSocketNotifyHandler handler){this.next = handler;}public abstract void doHandler(final T data);public Boolean getConnStatus(){return SOCKET_STATUS.get();}public void setConnOpen(){SOCKET_STATUS.compareAndSet(false, true);}public void setConnClose(){SOCKET_STATUS.compareAndSet(true, false);}public static class Builder<T>{private WebSocketNotifyHandler<T> head;private WebSocketNotifyHandler<T> tail;public Builder<T> addHandler(WebSocketNotifyHandler<T> handler){if(this.head == null){this.head = this.tail = handler;return this;}this.tail.next(handler);this.tail = handler;return this;}public WebSocketNotifyHandler<T> get(){return this.head;}}
}
// websocket 返回的消息体,T类型由具体事件类型确定
@Data
public class MeetingWebSocketMessage<T> {/*** 消息类型*/String action;/*** 消息随机序列号*/String sequence;/*** 消息id*/String msgID;/*** 消息体*/T data;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/844306.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【CTF-Web】XXE学习笔记(附ctfshow例题)

XXE 文章目录 XXE0x01 前置知识汇总XMLDTD &#xff08;Document Type Definition&#xff09; 0x02 XXE0x03 XXE危害0x04 攻击方式1. 通过File协议读取文件Web373(有回显)Web374(无回显) Web375Web376Web377Web378 0x01 前置知识汇总 XML 可扩展标记语言&#xff08;eXtensi…

Android音频焦点

什么是音频焦点&#xff1f; 音频焦点是 API 8 中引入的一个概念。它用于传达这样一个事实&#xff1a;用户一次只能专注于一个音频流&#xff0c;例如收听音乐或播客&#xff0c;但不能同时关注两者。在某些情况下&#xff0c;多个音频流可以同时播放&#xff0c;但只有一个是…

2021职称继续教育--实行高水平对外开放,积极参与全球经济治理体系改革,开拓合作共赢新局面

单选题&#xff08;共7题&#xff0c;每题5分&#xff09; 1、根据本讲&#xff0c;我国目前已有&#xff08;&#xff09;个省份设立了自贸区。 C、21 2、根据本讲&#xff0c;“一带一路”的官方翻译为&#xff08;&#xff09;。 A、The Belt and Road Initiative 3、根据…

故障诊断 | 基于KAN故障诊断模型

效果一览 文章概述 故障诊断 | 基于 KAN故障诊断模型。KAN是一种全新的神经网络架构&#xff0c;它与传统的MLP架构不同&#xff0c;能够用更少的参数量在Science领域取得惊人的表现&#xff0c;并且具备可解释性&#xff0c;有望成为深度学习模型发展的一个重要方向。运用KAN&…

从0开始学web之信息收集

web1~源代码 web1:where is flag?直接右键源代码找到。 web2~源代码 无法查看源代码确实右键不了&#xff0c;F12用不了&#xff0c; 但是还可以在URL前加上view-source: web3~HTTP响应 web3:where is flag?右键源代码没有&#xff0c;那就看看HTTP 头&#xff0c;F12抓…

数据大屏方案 : 实现数据可视化的关键一环_光点科技

在数字时代的浪潮中&#xff0c;数据已经成为企业决策和操作的重要基础。因此&#xff0c;“数据大屏方案”逐渐成为业界关注的焦点。这类方案通过将复杂的数据集合以直观的形式展现出来&#xff0c;帮助决策者快速把握信息&#xff0c;做出更加明智的决策。 数据大屏的定义及作…

什么是银行虚拟户?

虚拟银行账户是一种不同于传统银行账户的银行服务形式。又称银行云账户&#xff0c;主要是一个结算账户&#xff0c;可以作为企业开立多个不同名称子账户的主账户。 这些子账户可以用于各种用途&#xff0c;包括企业核算、佣金支付等。&#xff0c;同时保证转账和报送的同音性…

机器学习-9-python中的pipeline以及sklearn中的pipeline

参考探秘Python的Pipeline魔法 参考sklearn之pipeline:pipeline函数/make_pipeline函数的简介及其区别联系、使用技巧、案例应用之详细攻略 参考Python函数式编程之pipeline——很酷有没有 1 什么是Pipeline? pipeline 管道借鉴于Unix Shell的管道操作——把若干个命令串起…

nginx 配置 gzip压缩及去除 html 缓存

server{listen 80;server_name test.exmaple.cn;gzip on; # 是否开启gzip# gzip_buffers 32 4K; # 缓冲(压缩在内存中缓冲几块? 每块多大?)gzip_comp_level 6; # 推荐6 压缩级别(级别越高,压的越小,越浪费CPU计算资源)gzip_min_length 1k; # 开始压缩的最小长度(再小就…

Java-数组内存解析

文章目录 1.内存的主要结构&#xff1a;栈、堆2.一维数组的内存解析3.二维数组的内存解析 1.内存的主要结构&#xff1a;栈、堆 2.一维数组的内存解析 举例1&#xff1a;基本使用 举例2&#xff1a;两个变量指向一个数组 3.二维数组的内存解析 举例1&#xff1a; 举例2&am…

java生产制造执行系统MES源码:系统环境:Java EE 8、Servlet 3.0、Apache Maven 3 2;

MES系统技术选型 系统环境&#xff1a;Java EE 8、Servlet 3.0、Apache Maven 3 2&#xff1b; 主框架&#xff1a;Spring Boot 2.2.x、Spring Framework 5.2.x、Spring Security 5.2.x 3 持久层&#xff1a;Apache MyBatis 3.5.x、Hibernate Validation 6.0.x、Alibaba Dru…

Ai绘画怎么正确使用关键词?

在AI绘画的过程中&#xff0c;关键词&#xff08;提示词&#xff09;是非常重要的组成部分&#xff0c;下面我以AI绘画常用的Stable Diffusion为例&#xff0c;来介绍下AI绘画怎么使用提示词吧&#xff01; 一、提示词是什么 提示词&#xff08;Prompt&#xff09;就是我们对…

SOLIDWORKS 2024:零件亮点的升级与突破

随着科技的不断发展&#xff0c;工程设计软件也在持续进步&#xff0c;以更好地满足工程师和设计师的需求。SOLIDWORKS&#xff0c;作为一款广泛使用的三维CAD软件&#xff0c;一直在不断地推出新版本&#xff0c;以提供更强大、更便捷的功能。今天&#xff0c;我们将深入探讨S…

查找list集合中,持续时间>=ContinueTime的数据集合,保存在新的list中

在给定的包含时间戳的list中&#xff0c;查找连续continueNum次的且时间间隔为needDiff的集合。 eg&#xff1a;相邻两个数据的时间戳间隔为1分钟&#xff0c;且超过30分钟有数据 /**** param list 包含时间戳&#xff08;10位&#xff09;的list* param continueNum 至少持续…

初步研究Pose_300W_LP datasets.py

mat文件参数解读 Color_para&#xff1a;颜色参数&#xff0c;用于描述图像的颜色属性&#xff0c;比如图像的亮度、对比度等信息。 亮度属性、对比度属性、饱和度属性&#xff08;颜色越鲜艳&#xff09;、色调属性&#xff08;色调越偏向蓝色&#xff09;、色温属性&#xf…

Leecode热题100---二分查找---34:在排序数组中查找元素的第一个和最后一个的位置

题目&#xff1a; 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 方法1、枚举 思路&#xff1a;通过正向枚举和反向枚举找到对应…

【EI会议】第二届计算机、物联网与智慧城市国际会议

第二届计算机、物联网与智慧城市国际会议 快速通道 投稿链接&#xff1a;loading 截稿时间&#xff1a;9月15日 检索&#xff1a;EI检索 一、会议信息 大会官网&#xff1a;www.ciotsc.org 会议地点&#xff1a;湖南株洲 会议时间&#xff1a;2023年11月15日-17日 二、征稿主…

Go语言redis框架 — go-redis

https://zhuanlan.zhihu.com/p/645669818 一、简述 1. API友好&#xff0c;命令名称和参数与Redis原生命令一致&#xff0c;使用简单方便。 2. 支持完整的Redis命令集&#xff0c;覆盖了字符串、哈希、列表、集合、有序集合、HyperLogLog等数据结构。 3. 支持连接池&#x…

MySQL中SQL表设计的注意事项

效率工具 推荐一个程序员的常用工具网站&#xff0c;效率加倍嘎嘎好用&#xff1a;程序员常用工具 云服务器 云服务器限时免费领&#xff1a;轻量服务器2核4G腾讯云&#xff1a;2核2G4M云服务器新老同享99元/年&#xff0c;续费同价阿里云&#xff1a;2核2G3M的ECS服务器只需99…