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

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

具体的需求是,我们的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

java 复制代码
public class MeetingWebSocketClient implements Runnable {

    private MeetingHandlerContext context;

    public MeetingWebSocketClient(MeetingHandlerContext meetingHandlerContext){

        this.context = meetingHandlerContext;
    }

    @Override
    public 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消息事件处理

java 复制代码
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;
    }

    @Override
    public 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();


    }

    @Override
    public 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;
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // 链接异常
        log.error("web socket transport error : {}", exception.getMessage());

    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        log.info("web socket connection closed");
        // 当连服务端关闭连接时,中断心跳请求
        if(heartbeat != null){
            heartbeat.cancel(true);
        }

        // 通知所有的handler当前链接已断开
        handler.setConnClose();
    }

    @Override
    public 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);
    }
}
java 复制代码
// 会议邀请消息推送
@Slf4j
public class AttendeesNotifyHandler extends WebSocketNotifyHandler<WebSocketMessage> {


    @Override
    public 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));
        
       
    }
}
java 复制代码
// 会议状态信息推送
@Slf4j
public class ConfDynamicInfoNotifyHandler extends WebSocketNotifyHandler<WebSocketMessage> {

  

    @Override
    public 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));

        
    }
}
java 复制代码
// 会议邀请结果消息推送
@Slf4j
public class InviteResultNotifyHandler extends WebSocketNotifyHandler<WebSocketMessage> {

   

    @Override
    public 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));
       
    }
}
java 复制代码
// 在线人数消息推送
@Slf4j
public class ParticipantsNotifyHandler extends WebSocketNotifyHandler<WebSocketMessage> {

  

    @Override
    public 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));
        
    }
}
java 复制代码
// 消息事件处理器构造类

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;
        }
    }
}
java 复制代码
// websocket 返回的消息体,T类型由具体事件类型确定
@Data
public class MeetingWebSocketMessage<T> {

    /**
     * 消息类型
     */
    String action;

    /**
     * 消息随机序列号
     */
    String sequence;

    /**
     * 消息id
     */
    String msgID;

    /**
     * 消息体
     */
    T data;

}
相关推荐
IT民工金鱼哥2 天前
【华为云-云驻共创】UCS跨云多活容灾:让业务高可用不再是难题
华为云
知孤云出岫3 天前
华为云计算知识总结——及案例分享
华为·华为云·云计算
嘟嘟Listing3 天前
华为云安装docker
docker·容器·华为云
博客园团队4 天前
又给会员送福利,100台一年华为云2核2G3M云服务器
华为云
三掌柜6664 天前
鸿蒙原生应用开发及部署:首选华为云,开启HarmonyOS NEXT App新纪元
华为·华为云·harmonyos·鸿蒙·1024程序员节
shiming88795 天前
GaussDB Ustore存储引擎解读
华为云·gaussdb ustore
wangye114226 天前
共创一带一路经济体,土耳其海外媒体软文发稿 - 媒体宣发报道
大数据·人工智能·华为云·游戏引擎·产品运营
华为云开源7 天前
华为云开源项目Sermant正式成为CNCF官方项目
微服务·云原生·开源·华为云
DS小龙哥7 天前
基于STM32+华为云IOT设计的大棚育苗管理系统
stm32·物联网·华为云