前言
在原項目中,對於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協議單片機通信,異步存儲聊天數據
如果本文對你有所幫助,歡迎關注個人技術公衆號