API調用Netty長鏈接執行發送消息(在線數、用戶列表)

前言

在原項目中,對於WebSocket的長連接,聊天系統並沒有開放接口出來給第三方的系統調用,只有我們系統內部的人員才知道,確切的說系統內部也沒有實際的查詢接口,那麼我們今天就來實現這個功能。

在Netty下的Websocket長連接中,以API形式獲取在線用戶數,與在線用戶列表,並針對某個用戶已API調用的形式進行數據發送,而不需要所謂的前端頁面去創建websocket連接。

實踐流程

存放Channel的容器

首先,我們需要一個類似ChannelGroup的連接池來存放我們的連接實例,這裏我直接在原來本地模擬的一個LikeRedisTemplate中新建了一個ConcurrentHashMap,用於存放對應的用戶名——連接實例的鍵值對。

方便後期API調用時可以通過這個LikeRedisTemplate中的這個Map進行獲取、刪除及相關信息。

/**存放鏈接池實例*/
private Map<Object,Object> ChannelRedisMap = new ConcurrentHashMap<>();

/**
 * 存儲對應的用戶名與Netty鏈接實例
 * @param name 登錄用戶名
 * @param channel Netty鏈接實例
 */
public void saveChannel(Object name,Object channel){
    ChannelRedisMap.put(name,channel);
}

/**
 * 獲取存儲池中的鏈接實例
 * @param name 登錄用戶名
 * @return {@link io.netty.channel.Channel 鏈接實例}
 */
public Object getChannel(Object name){
    return ChannelRedisMap.get(name);
}

/**
 * 刪除存儲池實例
 * @param name 登錄用戶名
 */
public void deleteChannel(Object name){
    ChannelRedisMap.remove(name);
}
    
/**
 * 獲取儲存池鏈接數
 * @return 在線數
 */
public Integer getSize(){
    return ChannelRedisMap.size();
}

/**
 * 返回在線用戶列表信息
 * @return 用戶名列表
 */
public Object getOnline() {
    List<Object> result = new ArrayList<>();
    for (Object key:ChannelRedisMap.keySet()){
        result.add(key);
    }
    return result;
}

Handler中執行存儲操作

有了容器,我們就需要在對應的位置進行連接實例的鍵值對存儲,我目前選擇了在聊天消息傳輸過程中進行存儲,暫時還沒有抽象出來。

並在連接斷開時也要做相關的處理。

//用戶登錄判斷
if (redisTemplate.check(incoming.id(),rName)){
    //臨時存儲聊天數據
    cacheTemplate.save(rName,rMsg);
    //存儲隨機鏈接ID與對應登錄用戶名
    redisTemplate.save(incoming.id(),rName);
    //存儲登錄用戶名與鏈接實例,方便API調用鏈接實例
    redisTemplate.saveChannel(rName,incoming);
}else{
    incoming.writeAndFlush(new TextWebSocketFrame("存在二次登陸,系統已爲你自動斷開本次鏈接"));
    channels.remove(ctx.channel());
    ctx.close();
    return;
}

@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    //刪除存儲池對應實例
    String name = (String) redisTemplate.getName(ctx.channel().id());
    redisTemplate.deleteChannel(name);
    //刪除默認存儲對應關係
    redisTemplate.delete(ctx.channel().id());
    channels.remove(ctx.channel());
}

發送方法

我直接在SendUtil中寫一個系統發送的方法,輸出也是轉爲TextWebSocketFrame

/**
 * 想指定鏈接發送數據
 * @param msg 消息
 * @param channel 指定鏈接
 * @return {@link String}
 */
public static String sendTest(String msg,Channel channel) {
    try {
        channel.writeAndFlush(new TextWebSocketFrame( "[系統API]" + msg));
        return "success";
    }catch (Exception e){
        e.printStackTrace();
        return "error";
    }
}

定義API

這個就簡單一些了,定義一個統一返回的Bean,還有API的返回工具類,然後寫對應的API接口方法。

@RestController
@RequestMapping("/back")
public class NCBackController {

    @Autowired
    private LikeRedisTemplate redisTemplate;

    /**
     * 獲取在線用戶數
     * @return {@link ResultVo}
     */
    @GetMapping("/size")
    public ResultVo getSize(){
        return ResultVOUtil.success(redisTemplate.getSize());
    }

    /**
     * 獲取在線用戶列表
     * @return {@link ResultVo}
     */
    @GetMapping("/online")
    public ResultVo getOnline(){
        return ResultVOUtil.success(redisTemplate.getOnline());
    }

    /**
     * API調用向在線用戶發送消息
     * @param name 用戶名
     * @param msg 消息
     * @return {@link ResultVo}
     */
    @PostMapping("/send")
    public ResultVo send(@RequestParam String name,@RequestParam String msg){
        Channel channel = (Channel) redisTemplate.getChannel(name);
        if (channel == null){
            return ResultVOUtil.error(555,"當前用戶連接已斷開");
        }
        String result = SendUtil.sendTest(msg,channel);
        return ResultVOUtil.success(result);
    }

}

效果

我在項目中添加Swagger方便查看與簡單測試API,引入對應pom,在啓動類加一個註解即可。

啓動項目後登陸界面,發送了一個基本消息。
圖片描述

Swagger這邊的頁面打開後,測試幾個API,都是成功的。

圖片描述
圖片描述
圖片描述
圖片描述

好了,結尾還是成功的,不過作爲一個好產品是不能僅僅這樣的,後續我們繼續完善。

本項目是本人近期GitHub的核心發展項目,有興趣的朋友可以去了解下

GitHub

項目名:InChat
項目地址:https://github.com/UncleCatMy...
項目介紹:基於Netty4與SpringBoot,聊天室WebSocket(文字圖片)、Iot物聯網-MQTT協議、TCP/IP協議單片機通信,異步存儲聊天數據


如果本文對你有所幫助,歡迎關注個人技術公衆號
圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章