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协议单片机通信,异步存储聊天数据


如果本文对你有所帮助,欢迎关注个人技术公众号
图片描述


Java猫说
现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。

现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。

1.3k 声望
929 粉丝
0 条评论
推荐阅读
突击大厂面试的Netty源码知识(下)
ButeBuf 1.ByteBuffer的缺点     长度固定,容量不能动态扩展、收缩,遇到大的POJO会出现索引越界异常     只有一个标识指针position,需要手动调用flip和rewind 2.实现策略     基于JDK ByteBuffer,增加额外的...

Java猫说阅读 1k

刨根问底 Redis, 面试过程真好使
充满寒气的互联网如何在面试中脱颖而出,平时积累很重要,八股文更不能少!下面带来的这篇 Redis 问答希望能够在你的 offer 上增添一把🔥。

菜农曰17阅读 973

封面图
PHP转Go实践:xjson解析神器「开源工具集」
我和劲仔都是PHP转Go,身边越来越多做PHP的朋友也逐渐在用Go进行重构,重构过程中,会发现php的json解析操作(系列化与反序列化)是真的香,弱类型语言的各种隐式类型转换,很大程度的减低了程序的复杂度。

王中阳Go10阅读 2k评论 3

封面图
万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide5阅读 847

封面图
计算机网络连环炮40问
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

程序员大彬8阅读 1.1k

与RabbitMQ有关的一些知识
工作中用过一段时间的Kafka,不过主要还是RabbitMQ用的多一些。今天主要来讲讲与RabbitMQ相关的一些知识。一些基本概念,以及实际使用场景及一些注意事项。

lpe2348阅读 1.9k

封面图
Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...

王中阳Go5阅读 2.3k评论 2

封面图

现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。

1.3k 声望
929 粉丝
宣传栏