基于netty去实现websocket 单聊+群聊 前端使用layui模板引擎

本项目开发周期比较短 大部分都是围绕业务来去实现 很多需要完善地方这里只是出了一个简短的例子
ps:在此之前没有搞过即时通讯,这个项目也算是入门大佬勿喷
所使用的技术:
netty,rabbitMQ,redis,mongoDB,mysql
先上java依赖

		 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
               <!--引入 redis 依赖 -->
            <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.4.2</version>
        </dependency>

		  <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.42.Final</version>
        </dependency>
        
        <!-- spring-boot-starter-data-mongodb -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.2.3</version>
        </dependency>
	以上基本满足了  还有一些第三方推送和oss储存就不一一放出来了,不会用oss和推送的可以去看看博客
	下面开始详细介绍:
		1.定义前后端通用数据结构
package com.yj.im.project.nettyServer;

import com.yj.im.project.entity.ChatRecord;

import java.io.Serializable;

public class Message implements Serializable {
     

    /**
     * 服务端接受参数
     * type          消息类型     0.连接  1.发送消息  2.接收消息  3.客户端保持心跳  4.群聊  500.异常
     * chatRecord {
     *      * @param {Object} msgType 消息类型  0.文字 1.图片,2.视频,
     *      * @param {Object} userId 发送的id
     *      * @param {Object} recipientId 接收者id
     *      * @param {Object} msg 消息
     *      * @param {Object} msgid 对应的消息id  (读取是需要加入)
     * }
     */

    /**
     * 客户端
     * type   消息类型     0.连接  1.客户端向服务器发送消息(发消息)  2.服务端向客户端发送消息(接收消息)   4.群聊 3.客户端保持心跳 500.异常
     * 备注:即: 客户端发送的统一为 1
     * 服务端发送的统一为 2
     * 前后端心跳包发送统一为 3
     * 

* chatRecord { * * @param {Object} msgType 消息类型 0.文字 1.图片,2.视频, * * @param {Object} userId 发送的id * * @param {Object} recipientId 接收者id * * @param {Object} msg 消息(如果msgType为1,2此字段为文件路径) * * @param {Object} msgid 对应的消息id (读取是需要加入) * } */ private Integer type;//消息类型 private ChatRecord chatRecord;//聊天消息 private Object ext; //附加消息 public Integer getType() { return type; } public Message setType(Integer type) { this.type = type; return this; } public ChatRecord getChatRecord() { return chatRecord; } public Message setChatRecord(ChatRecord chatRecord) { this.chatRecord = chatRecord; return this; } public Object getExt() { return ext; } public Message setExt(Object ext) { this.ext = ext; return this; } }

package com.yj.im.project.entity;

import java.util.Date;
import java.io.Serializable;

/**
 * 聊天记录表(ChatRecord)实体类
 *
 * @author makejava
 * @since 2020-04-07 16:14:52
 */
public class ChatRecord implements Serializable {
     
    private static final long serialVersionUID = 489748139527994068L;
    /**
     * 记录表id
     */
    private Long id;

    private String mongoId;

    /**
     * 用户ID
     */
    private Long userId;
    /**
     * 接收者用户ID
     */
    private Long recipientId;

    /**
     * 用户在群里的昵称
     */
    private String nickName;

    /**
     * 头像
     */
    private String img;

    /**
     * 消息名称
     */
    private String msgName;

    /**
     * 是否已读(0.已读 1.未读)
     */
    private Integer hasRead;
    /**
     * 是否删除(0.已删除 1.未删除)
     */
    private Integer hasDelete;
    /**
     * 消息类型(0.文字 1.图片,3.视频)
     */
    private Integer msgType;

    /**
     * 区分用户消息还是系统消息
     */
    private Integer sysMsgType;

    /**
     * 消息内容(如果是图片则为文件路径)
     */
    private String message;
    /**
     * 状态(0逻辑删除、1数据有效)
     */
    private Integer status;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 修改时间
     */
    private Date updateTime;

    public String getMongoId() {
     
        return mongoId;
    }

    public ChatRecord setMongoId(String mongoId) {
     
        this.mongoId = mongoId;
        return this;
    }

    public String getImg() {
     
        return img;
    }

    public ChatRecord setImg(String img) {
     
        this.img = img;
        return this;
    }

    public String getMsgName() {
     
        return msgName;
    }

    public ChatRecord setMsgName(String msgName) {
     
        this.msgName = msgName;
        return this;
    }

    public String getNickName() {
     
        return nickName;
    }

    public ChatRecord setNickName(String nickName) {
     
        this.nickName = nickName;
        return this;

    }

    public Integer getSysMsgType() {
     
        return sysMsgType;
    }

    public ChatRecord setSysMsgType(Integer sysMsgType) {
     
        this.sysMsgType = sysMsgType;
        return this;
    }

    public Long getId() {
     
        return id;
    }

    public ChatRecord setId(Long id) {
     
        this.id = id;
        return this;
    }

    public Long getUserId() {
     
        return userId;
    }

    public ChatRecord setUserId(Long userId) {
     
        this.userId = userId;

        return this;

    }

    public Long getRecipientId() {
     
        return recipientId;
    }

    public ChatRecord setRecipientId(Long recipientId) {
     
        this.recipientId = recipientId;
        return this;

    }

    public Integer getHasRead() {
     
        return hasRead;
    }

    public ChatRecord setHasRead(Integer hasRead) {
     
        this.hasRead = hasRead;
        return this;

    }

    public Integer getHasDelete() {
     
        return hasDelete;
    }

    public ChatRecord setHasDelete(Integer hasDelete) {
     
        this.hasDelete = hasDelete;
        return this;

    }

    public Integer getMsgType() {
     
        return msgType;
    }

    public ChatRecord setMsgType(Integer msgType) {
     
        this.msgType = msgType;
        return this;

    }

    public String getMessage() {
     
        return message;
    }

    public ChatRecord setMessage(String message) {
     
        this.message = message;
        return this;

    }

    public Integer getStatus() {
     
        return status;
    }

    public ChatRecord setStatus(Integer status) {
     
        this.status = status;
        return this;

    }

    public Date getCreateTime() {
     
        return createTime;
    }

    public ChatRecord setCreateTime(Date createTime) {
     
        this.createTime = createTime;
        return this;

    }

    public Date getUpdateTime() {
     
        return updateTime;
    }

    public ChatRecord setUpdateTime(Date updateTime) {
     
        this.updateTime = updateTime;
        return this;

    }

}

2.后续跟上netty代码:

package com.yj.im.project.nettyServer;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;

public class WebSocketInitializer extends ChannelInitializer<SocketChannel> {
     

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
     
        ChannelPipeline pipeline = ch.pipeline();

        //-------------
        //用于支持 Http协议
        //-----------------

        //websocket基于 http协议,需要有 http 的编解码器
        pipeline.addLast(new HttpServerCodec())
                //对于大数据流的支持
                .addLast(new ChunkedWriteHandler())
                //添加对HTTP请求和响应的聚合器:只要使用Netty进行 http编码都需要使用到
                //对HttpMessage进行聚合,聚合成FullHttpRequest或者FullHttpResponse
                //在 netty 编程总都会使用到Handler
                .addLast(new HttpObjectAggregator(1024 * 64))
                .addLast(new WebSocketServerProtocolHandler("/ws"))
                //添加Netty空闲超时检查的支持
                //4:读空闲超时,8:写空闲超时,12: 读写空闲超时
                .addLast(new IdleStateHandler(4, 8, 12))
                .addLast(new HearBeatHandler())
                //添加自定有的 handler
                .addLast(new ChatHandler());


    }
}

package com.yj.im.project.nettyServer;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class WebSocketServer {
     


    private EventLoopGroup bossGroup;//主线程

    private EventLoopGroup workerGroup;//工作线程

    private ServerBootstrap server;//服务器

    private ChannelFuture future; //回调

    @PostConstruct
    public void start() {
     
        future = server.bind(8089);
        BaseNettyServer.nettyLog().warn("-----------------------------------------------------------------------------------------");
        BaseNettyServer.nettyLog().warn("---------------------------------netty server   开始启动---------------------------------");
        BaseNettyServer.nettyLog().warn("-----------------------------------------------------------------------------------------");
        BaseNettyServer.nettyLog().warn("-----------------------------------------------------------------------------------------");
        BaseNettyServer.nettyLog().warn("---------------------------------netty server - 启动成功---------------------------------");
        BaseNettyServer.nettyLog().warn("-----------------------------------------------------------------------------------------");

    }

    public WebSocketServer() {
     
        bossGroup = new NioEventLoopGroup();
        workerGroup = new NioEventLoopGroup();
        server = new ServerBootstrap();
        server.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new WebSocketInitializer());
    }
}

netty是需要两个线程来工作的具体的可以去百度看看这里不做介绍,然后定义端口加上@PostConstruct启动的时候执行一次就好了WebSocketServer()是当前的构造器,容器启动的时候会加载这个构造器。

3.这里就是自定义的消息处理handler,在这之前先把一些其他类粘出来

package com.yj.im.project.nettyServer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BaseNettyServer {
     

    private final static Logger log = LoggerFactory.getLogger(HearBeatHandler.class);

    public static Logger nettyLog(){
     
        return log;
    }

}

package com.yj.im.project.nettyServer;

import com.yj.im.project.entity.ChatFriend;
import io.netty.channel.Channel;

import java.util.HashMap;
import java.util.Map;

public class UserChannelMap {
     


    //保存用户id和通道的map对象
    private static Map<String, Channel> userChannelMap;

    static {
     
        userChannelMap = new HashMap<>();
    }

    /**
     * 建立用户和通道直接的关联
     *
     * @param userId
     * @param channel
     */
    public static void put(Long userId, Channel channel) {
     
        userChannelMap.put("im" + userId, channel);
    }

    public static Channel getChannel(Long userId) {
     
        return userChannelMap.get("im" + userId);
    }

    /**
     * 解除用户和通道直接的关系
     *
     * @param userid
     */
    public static void remove(String userid) {
     
        userChannelMap.remove(userid);
    }

    public static void removeByChannelId(String channelId) {
     
        for (String userId : userChannelMap.keySet()) {
     
            if (userChannelMap.get(userId).id().asLongText().equals(channelId)) {
     
                BaseNettyServer.nettyLog().info("客户端连接断开,取消用户" + userId + "与通道" + channelId + "的关联");
                ChatHandler.userInfoMap.remove(userId);
                userChannelMap.remove(userId);
                break;
            }

        }
    }

    // 打印所有的用户与通道的关联数据
    public static void print() {
     

//        for (String s : userChannelMap.keySet()) {
     
//            BaseNettyServer.nettyLog().info("用户id:" + s + " 通道:" + userChannelMap.get(s).id());
//        }
    }

    /**
     * 根据用户id,获取通道
     */
    public static Channel get(String userid) {
     
        return userChannelMap.get(userid);
    }
}

package com.yj.im.project.nettyServer;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

public class HearBeatHandler extends ChannelInboundHandlerAdapter {
     

    //触发用户事件
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
     

        if (evt instanceof IdleStateEvent) {
     
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            if (idleStateEvent.state() == IdleState.READER_IDLE) {
     //读空闲
                //检测到读空闲不做任何的操作
                BaseNettyServer.nettyLog().warn("读空闲事件触发......");
            } else if (idleStateEvent.state() == IdleState.WRITER_IDLE) {
     //写空闲
                //检测到写空闲不做任何的操作
                BaseNettyServer.nettyLog().warn("写空闲事件触发...");
            } else if (idleStateEvent.state() == IdleState.ALL_IDLE) {
     //读写空闲
                ctx.channel().close();
                BaseNettyServer.nettyLog().info("---------------------------------------------------------------------------------------------");
                BaseNettyServer.nettyLog().info("----------------------------------------------------------------------------");
                BaseNettyServer.nettyLog().warn("-----------------------------------------------资源关闭---------------------------------------");
                BaseNettyServer.nettyLog().info("----------------------------------------------------------------------------");
                BaseNettyServer.nettyLog().info("--------------------------------------------------------------------------------------------");
            }
        }
    }
}

这里是具体的处理,根据业务区处理就好了

package com.yj.im.project.nettyServer;

import com.alibaba.fastjson.JSON;
import com.yj.im.project.dao.ChatUserGroupDao;
import com.yj.im.project.dao.mongoDB.ChatFriendMsgNotReadDao;
import com.yj.im.project.dao.mongoDB.ChatGroupMsgNotReadyDao;
import com.yj.im.project.entity.ChatRecord;
import com.yj.im.project.entity.ChatSysRead;
import com.yj.im.project.entity.mongoEntity.ChatGroupMsgNotReady;
import com.yj.im.project.entity.mongoEntity.ChatMyFriendMsgNotRead;
import com.yj.im.project.entity.pojo.JwtUser;
import com.yj.im.project.service.ChatRecordService;
import com.yj.im.project.service.ChatSysReadService;
import com.yj.im.project.util.RSA.JwtUtils;
import com.yj.im.project.util.RedisOperator;
import com.yj.im.project.util.SpringUtil;
import com.yj.im.project.util.contants.RedisKeyConstants;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.apache.logging.log4j.core.util.UuidUtil;

import java.time.LocalDateTime;
import java.util.*;

public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
     

    private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    //用户建立连接后的用户信息
    public static Hashtable<String, JwtUser> userInfoMap = new Hashtable<>();

    //用户消息处理服务
    ChatRecordService chatRecordService = SpringUtil.getBean(ChatRecordService.class);

    //系统级消息处理服务
    ChatSysReadService chatSysReadService = SpringUtil.getBean(ChatSysReadService.class);

    //群消息处理MongoDB
    ChatGroupMsgNotReadyDao chatGroupMsgReadyDao = SpringUtil.getBean(ChatGroupMsgNotReadyDao.class);

    //好友消息是否未读MongoDB
    ChatFriendMsgNotReadDao chatFriendMsgNotReadDao = SpringUtil.getBean(ChatFriendMsgNotReadDao.class);

    //用户所在的群相关dao层
    ChatUserGroupDao chatUserGroupDao = SpringUtil.getBean(ChatUserGroupDao.class);

    //redis
    RedisOperator redisOperator = SpringUtil.getBean(RedisOperator.class);


    /**
     * @param channelHandlerContext
     * @param textWebSocketFrame
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
     


        String content = textWebSocketFrame.text();
        BaseNettyServer.nettyLog().info("接收到的数据: " + content);
        Message message = JSON.parseObject(content, Message.class);
        switch (message.getType()) {
     
            //处理客户端链接的消息
            case 0:   //表示连接
                //建立用户和通道之间的关系
                JwtUser userInfo = JwtUtils.getUserInfoByToken(message.getExt().toString());
                UserChannelMap.put(userInfo.getId(), channelHandlerContext.channel());
                BaseNettyServer.nettyLog().info("用户名:" + userInfo.getUsername() + "建立了连接!!!");
                UserChannelMap.print();
                userInfoMap.put("im" + userInfo.getId(), userInfo);
                message.setType(0);
                channelHandlerContext.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message.setChatRecord(message.getChatRecord().setUserId(userInfo.getId())))));
                break;

            case 1:  //表示发送消息
                //将消息保存到数据库


                Date data = new Date();
                JwtUser jwtUser = userInfoMap.get("im" + message.getChatRecord().getUserId());
                ChatRecord chatRecord = message.getChatRecord()
                        .setStatus(1)
                        .setHasRead(0)
                        .setHasDelete(0)
                        .setImg(jwtUser.getUserImg())
                        .setNickName(jwtUser.getUsername())
                        .setCreateTime(data)
                        .setUpdateTime(data);
                chatRecordService.insertToMq(chatRecord);
                //查看此好友是否在线,如果在线就将消息发送给此好友
                //1.根据好友id,查询此通道是否存在
                Channel channel = UserChannelMap.getChannel(chatRecord.getRecipientId());
                if (channel != null) {
     
                    //存在的话发消息给他
                    ChatMyFriendMsgNotRead cmgod = chatFriendMsgNotReadDao.queryOne(new ChatMyFriendMsgNotRead()
                            .setUserId(message.getChatRecord().getUserId())
                            .setFriendId(message.getChatRecord().getRecipientId()));
                    message.setType(2);
                    channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message.setChatRecord(message.getChatRecord().setMongoId(cmgod != null ? cmgod.getId() : "")))));
                } else {
     
                    String mongoId = UuidUtil.getTimeBasedUuid().toString();
                    //用户不在线 未读消息+1 并且储存当前过来的消息
                    Long userId = message.getChatRecord().getUserId();
                    ChatMyFriendMsgNotRead chatMyFriendMsgNotRead = chatFriendMsgNotReadDao.queryOne(new ChatMyFriendMsgNotRead()
                            .setUserId(userId)
                            .setFriendId(message.getChatRecord().getRecipientId()));
                    //MongoDB查出来
                    if (chatMyFriendMsgNotRead == null) {
     
                        //没有就新增一条,此时map里面可能没有好友的信息所以不存头像
                        chatFriendMsgNotReadDao.save(new ChatMyFriendMsgNotRead()
                                .setUserId(userId)
                                .setFriendId(message.getChatRecord().getRecipientId())
                                .setMsgNotReadyCount(1)
                                .setId(mongoId)
                                .setOutMsg(message.getChatRecord().getMessage())
                                .setOutTime(LocalDateTime.now()));
                    } else {
     
                        //有就修改
                        chatFriendMsgNotReadDao.updateFirst(chatMyFriendMsgNotRead, new ChatMyFriendMsgNotRead()
                                .setOutMsg(message.getChatRecord().getMessage())
                                .setMsgNotReadyCount(chatMyFriendMsgNotRead.getMsgNotReadyCount() + 1).setOutTime(LocalDateTime.now()));
                    }
                }
                break;
            case 2:  //接收消息
                //将消息设置为已读 (暂时不做  功能已有但是太消耗性能)
                if (message.getChatRecord().getSysMsgType() != null) {
     
                    //存在即为读取系统消息
                    chatSysReadService.updateByTypeAndIdToReadCount(new ChatSysRead()
                            .setReadType(message.getChatRecord().getSysMsgType())
                            .setUserId(message.getChatRecord().getUserId()));
                } else {
     
                    //确认用户好友消息
                    chatRecordService.updateByUserAndRecordId(new ChatRecord()
                            .setUserId(message.getChatRecord().getUserId())
                            .setRecipientId(message.getChatRecord().getRecipientId())
                            .setHasRead(0)
                            .setUpdateTime(new Date()));
                }
                break;
            case 3: //检测心跳
                //接收心跳信息
                BaseNettyServer.nettyLog().info("接收到心跳消息" + JSON.toJSONString(message));
                message.setExt("收到啦谢谢你 !!!");
                message.setType(3);
                channelHandlerContext.channel().writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message)));
                break;

            case 4://群消息发送
                Set<Long> users = new HashSet<Long>();//假装是redis取出来的群组ids
                users.add(1L);
                users.add(2L);
                users.add(3L);
                //然后向所在的群发送消息
                for (Long user : users) {
     
                    String nickName = redisOperator.get(RedisKeyConstants.IM_GROUP_GET_NICKNAME_BY_ID + message.getChatRecord().getRecipientId()).toString();
                    if (nickName == null) {
     
                        nickName = chatUserGroupDao.selectByGroupIdAndUserIdGetRemarks(message.getChatRecord().getRecipientId(), user);
                    }

                    Channel cs = UserChannelMap.getChannel(user);
                    if (cs != null) {
     
                        //当前用户在线
                        message.setType(4);
                        cs.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(message.getChatRecord().setNickName(nickName))));
                    } else {
     
                        //用户不在线 未读消息+1 并且储存当前过来的消息
                        ChatGroupMsgNotReady chatGroupMsgReady = chatGroupMsgReadyDao.queryOne(new ChatGroupMsgNotReady().setUserId(user).setGroupId(message.getChatRecord().getRecipientId()));
                        //MongoDB查出来
                        if (chatGroupMsgReady == null) {
     
                            //没有就新增一条
                            chatGroupMsgReadyDao.save(new ChatGroupMsgNotReady()
                                    .setUserId(user)
                                    .setMsgNotReadyCount(1)
                                    .setNickName(nickName)
                                    .setGroupId(message.getChatRecord().getRecipientId())
                                    .setId(UuidUtil.getTimeBasedUuid().toString())
                                    .setOutMsg(message.getChatRecord().getMessage())
                                    .setOutTime(LocalDateTime.now()));
                        } else {
     
                            //有就修改
                            chatGroupMsgReadyDao.updateFirst(chatGroupMsgReady, new ChatGroupMsgNotReady()
                                    .setOutMsg(message.getChatRecord().getMessage())
                                    .setMsgNotReadyCount(chatGroupMsgReady.getMsgNotReadyCount() + 1).setOutTime(LocalDateTime.now()));
                        }
                    }
                }
                //群消息
                //mq存消息
                chatRecordService.chatGroupInsertMq(message.getChatRecord());

                break;
            case 5:
                //确认群消息
                ChatGroupMsgNotReady query = new ChatGroupMsgNotReady().setUserId(message.getChatRecord().getUserId()).setGroupId(message.getChatRecord().getRecipientId());
                chatGroupMsgReadyDao.updateFirst(query, new ChatGroupMsgNotReady().setMsgNotReadyCount(0));
                break;
            default:
                //没有成功匹配视为异常操作
                channelHandlerContext.channel()
                        .writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(new Message()
                                .setType(500)
                                .setExt("未能匹配到相关操作"))));
                BaseNettyServer.nettyLog().error("未能匹配到相关操作");

        }

    }

    //当有新的客户端连接服务器之后,就会自动调用这个方法
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
     
        channels.add(ctx.channel());
    }

    //出现异常是关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
     
        //根据通道id取消用户和通道的关系
        UserChannelMap.removeByChannelId(ctx.channel().id().asLongText());
        ctx.channel().close();
        BaseNettyServer.nettyLog().warn("出现异常.....关闭通道!");
    }


    //当客户端关闭链接时关闭通道
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
     
        BaseNettyServer.nettyLog().info("关闭通道");
        UserChannelMap.removeByChannelId(ctx.channel().id().asLongText());
        ctx.channel().close();
        UserChannelMap.print();
    }

    public static void toAllUser(Message text) {
     
        for (Channel channel : channels) {
     
            channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(text)));
        }
    }

}

最后这个类是我自己的业务处理方式,你们可以根据自己的业务去写一些逻辑,到这里实际上这个后端代码已经可以正常执行了,本来想吧具体的rabbitmq+MongoDB的代码贴出来,考虑到每个公司的业务可能不一样所以就没贴出来了。

下面是前端代码,前端也是稍微简短的发出来吧。具体的可以联系我,可以发完整的项目。

<!DOCTYPE>
<html>
<head>
    <meta charset="utf-8">
    <title>Layui</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="referrer" content="no-referrer"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="icon" type="image/x-icon" href="#"/>
    <!--    layui核心css-->
    <link rel="stylesheet" href="../../assets/layui/css/layui.css">
    <!--    自定义css-->
    <link rel="stylesheet" href="../../assets/css/message/sub_message.css">
    <link rel="stylesheet" href="../../assets/css/message/chat.css">
    <link rel="stylesheet" href="../../assets/css/message/style.css">


    <!--    核心JS 和 配置Js-->
    <script src="../../assets/js/jquery-3.1.1.min.js"></script>
    <script src="../../assets/layui/layui.all.js"></script>
    <script src="../../assets/js-v/config.js"></script>

    <!--    当前页面的js-->
    <script src="../../assets/js/message/main.js"></script>
    <script src="../../assets/js/message/sub_mesaage.js"></script>
    <script src="../../assets/js/websocket/socket_main.js"></script>

</head>
<body id="main" style="background: #eeeeee;">
<div id="msgList">

    <!-- 头部 -->
    <div class="head-div">
        <br>
        <br>
        <p>消息</p>
    </div>

    <!--   消息体 -->
    <div id="body-div">
        <!--            没有消息的窗口 start    -->
        <div class="not-msg-div">
            <br/>
            <br/>
            <br/>
            <img src="../../assets/img/message/not-message/图层%201.png" alt="没有消息" width="100%"/>
            <p>暂无新消息</p>
        </div>
        <!--                没有消息的窗口 end-->

        <!--        拥有系统消息  start-->
        <div id="sys_msg_tmp"></div>
        <!--        拥有系统消息  end-->

        <!--        好友与群聊消息 start-->
        <div id="my_message"></div>
        <!--        好友与群聊消息 end-->

    </div>
</div>

<div id="sub_main" style="overflow: auto;">
    <!-- 头部 -->
    <div class="head-div" style="position: fixed">
        <img id="remove_sub_message" src="../../assets/img/message/sub_message/<@2x.png">
        <br>
        <br>
        <p>小团团</p>
    </div>

    <!--    聊天消息-->
    <div id="message_context" class="chatBox-content" style="overflow: auto;"></div>


    <div class="button_input">
        <div class="layui-row">
            <div class="layui-col-xs2">
                <div class="grid-demo grid-demo-bg1"><img style="margin-left: 14%;margin-top: 9%;"
                                                          src="../../assets/img/message/sub_message/语音_看图王@2x.png"
                                                          width="50%"></div>
            </div>
            <div class="layui-col-xs8">
                <div class="grid-demo" style="text-align: center"><input style="margin-left: -5%;margin-top: 2%;"
                                                                         type="text" class="post_message"></div>
            </div>
            <div class="layui-col-xs2">
                <div class="grid-demo"><img style="margin-left: 30%;margin-top: 9%"
                                            src="../../assets/img/message/sub_message/加_看图王@2x.png" width="50%"></div>
            </div>
        </div>
    </div>
</div>
</body>
<script id="page_template_id" type="text/html">
    {
     {
     # layui.each(d,function(index, item){
      }}
    <div class="clearfloat">
        <div class="author-name">
            <small class="chat-date">{
     {
     # index%2===0?layui.util.toDateString(item.createTime, 'MM-dd
                HH:mm'):null}}</small>
        </div>
        {
     {
     # if(JSON.parse(sessionStorage.getItem("myUserId"))==item.userId){
      var myImg =
        JSON.parse(sessionStorage.getItem("myHeadImg")); }}
        <div class="right">
            <div class="chat-message">
                {
     {
     item.message}}
            </div>
            <div class="chat-avatars"><img src="{
     {myImg}}" alt="头像"/></div>
            {
     {
     # }else{
      var youImg = JSON.parse(sessionStorage.getItem("youHeadImg")); }}
            <div class="left">

                <div class="chat-avatars"><img src="{
     {youImg}}" alt="头像"/></div>
                <div class="chat-message">
                    {
     {
     item.message}}
                </div>
                {
     {
     # }; }}

            </div>
        </div>
        {
     {
     # }); }}
    </div>

</script>
</html>




css文件

1.chat.css


.chatContainer, .chatContainer div, .chatContainer ul, .chatContainer li, .chatContainer p {
     
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}

/* 设置滚动条的样式 */
::-webkit-scrollbar {
     
    width: 5px;
}

/* 滚动槽 */
::-webkit-scrollbar-track {
     
    border-radius: 10px;
}

/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
     
    border-radius: 5px;
    background: #fefffd;
    -webkit-box-shadow: #f2f1ff;
}

::-webkit-scrollbar-thumb:window-inactive {
     
    background: rgba(175, 190, 255, 0.4);
}

.btn-default-styles:focus {
     
    outline: none;
}

.btn-default-styles:hover {
     
    background: #c5c5c5;
    animation: anniu 1s infinite;
}

.btn-default-styles:active {
     
    box-shadow: 0 2px 3px rgba(0, 0, 0, .2) inset;
}

.chatBox-content {
     
    width: 100%;
}

.chatBox-content-demo {
     
    width: 100%;
    /*overflow-y: auto;*/
    height: 80%;
    margin-top: 21%;
}

.clearfloat:after {
     
    display: block;
    clear: both;
    content: "";
    visibility: hidden;
    height: 0
}

.clearfloat {
     
    zoom: 1;
    margin: 10px 10px;
}


.author-name {
     
    text-align: center;
    margin: 15px 0 5px 0;
    color: #888;
}

.clearfloat .chat-message {
     
    max-width: 252px;
    /*text-align: left;*/
    padding: 8px 12px;
    border-radius: 6px;
    word-wrap: break-word;
    display: inline-block;
    position: relative;
}


.clearfloat .left .chat-message {
     
    background: #FFFFFF;
    min-height: 18px;
}

.clearfloat .left .chat-message:before {
     
    position: absolute;
    content: "";
    top: 8px;
    left: -6px;
    border-top: 10px solid transparent;
    border-bottom: 10px solid transparent;
    border-right: 10px solid #fff;
}

.clearfloat .right {
     
    text-align: right;
}

.clearfloat .right .chat-message {
     
    background: #178FFE;
    color: #fff;
    text-align: left;
    min-height: 18px;
    margin-right: 9px;
}

.clearfloat .right .chat-message:before {
     
    position: absolute;
    content: "";
    top: 8px;
    right: -6px;
    border-top: 10px solid transparent;
    border-bottom: 10px solid transparent;
    border-left: 10px solid #178FFE;
}

.clearfloat .chat-avatars {
     
    display: inline-block;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: #eee;
    vertical-align: top;
    /*margin-right: -13%;*/
    overflow: hidden;
}

.clearfloat .chat-avatars > img {
     
    width: 30px;
    height: 30px;
}

.clearfloat .left .chat-avatars {
     
    margin-right: 10px;
}

.clearfloat .right .chat-avatars {
     
    /*margin-left: 70%;*/
    /*float: left;*/
}

.chat-message img {
     
    width: 220px;
    height: auto;
}

/*.right img {
     */
/*    margin-left: -7%;*/
/*    position: absolute;*/
/*}*/

2.style.css

* {
     
    margin: 0px;
    padding: 0px;

}

/*顶部的蓝色头部*/
.head-div {
     
    width: 100%;
    height: 4.5rem;
    background-image: url("../../img/message/not-message/矩形 [email protected]");
}
/*顶部的蓝色头部*/
.head-div img{
     
    width: 3%;
    position: absolute;
    margin-top: 7%;
    margin-left: 5%;
}

/*消息大icon的css*/
.head-div p {
     
    width: 82px;
    height: 17px;
    font-size: 17px;
    font-family: Microsoft YaHei;
    font-weight: bold;
    color: rgba(255, 255, 255, 1);
    line-height: 14px;
    /*text-Align: center;*/
    margin: 0rem auto;
}

/*暂无新消息的css*/
.not-msg-div p {
     
    width: 80px;
    height: 13px;
    font-size: 14px;
    font-family: Microsoft YaHei;
    font-weight: 200;
    color: rgba(124, 124, 124, 1);
    line-height: 14px;
    margin: 0rem auto;
}

/*聊天消息的css*/
.message {
     
    width: auto;
    height: 59px;
    background: rgba(255, 255, 255, 1);
}

.top-msg-name {
     
    width: 45px;
    height: 10px;
    font-size: 11px;
    font-family: Microsoft YaHei;
    font-weight: 200;
    color: rgba(144, 144, 144, 1);
    line-height: 14px;
}

.sub-message {
     
    width: 155px;
    height: 12px;
    font-size: 11px;
    font-family: Microsoft YaHei;
    font-weight: 200;
    color: rgba(175, 175, 175, 1);
    line-height: 14px;
}

.layui-col-md10 {
     
    float: left;
    position: absolute;
    margin-left: 15%;
}

.layui-col-md12 {
     
    float: left;
    position: absolute;
    margin-left: 15%;
    margin-top: 5.5%;
}
.layui-badge{
     
    margin-left: -3%;
    margin-top: -1.5%;
    position: absolute;
    border-radius: 15px;
}
.time_span {
     
    position: absolute;
    float: right;
    margin-left: 81%;
    margin-top: -14%;
    width: 65.5px;
    height: 9px;
    font-size: 11px;
    font-family: Microsoft YaHei;
    font-weight: 200;
    color: rgba(175,175,175,1);
    line-height: 14px;
}

3.sub_message.css

* {
     
    margin: 0px;
    padding: 0px;

}

/*顶部的蓝色头部*/
.head-div {
     
    width: 100%;
    height: 4.5rem;
    background-image: url("../../img/message/sub_message/虚化背景@2x.png");
}

/*消息大icon的css*/
.head-div p {
     
    width: 48.5px;
    height: 16px;
    font-size: 16px;
    font-family: Microsoft YaHei;
    font-weight: bold;
    color: rgba(255, 255, 255, 1);
    line-height: 14px;
    /*text-Align: center;*/
    margin: 0rem auto;
}

.button_input {
     
    width: 100%;
    height: 3rem;
    background: #F1F1F1;
    box-shadow: 0px -3px 15px 0px rgba(187, 187, 187, 0.29);
    position: fixed;
    bottom: 0px;
}

input:-webkit-autofill,select:-webkit-autofill {
     
    -webkit-box-shadow: 0 0 0px 1000px white  inset !important;
}

input{
     
    outline-color: invert ;
    outline-style: none ;
    outline-width: 0px ;
    border: none ;
    border-style: none ;
    text-shadow: none ;
    -webkit-appearance: none ;
    -webkit-user-select: text ;
    outline-color: transparent ;
    box-shadow: none;
    padding-left: 5%;
}

.post_message {
     
    width:272px;
    height:37px;
    background: rgba(249, 249, 249,1);
    border-radius:10px;
}

js文件
1.socketsocket_main.js

var websocket

var lockReconnect = false;  //避免ws重复连接var lockReconnect = false;  //避免ws重复连接

var socketUserId = null;


function socketInti(wsUrl) {
     
    lockReconnect = true;
    websocket = new WebSocket(wsUrl);
    onOpen();
    //检查浏览器是否支持WebSocket
    if (window.WebSocket) {
     
        console.log('This browser supports WebSocket');
    } else {
     
        console.log('This browser does not supports WebSocket');
        return;
    }

    sessionStorage.setItem("where_is_my", JSON.stringify(0));//0代表我在聊天框外面 1.代表进去了

    websocket.onopen = function (evt) {
     
        websocket.send(getMessage(0, 0, null, $("#user").val(), null, null, null, tokenValue));
        // setInterval(function () {
     
        //     onOpen()
        // }, 1500);
    };
    websocket.onclose = function (evt) {
     
        onClose(evt)
    };
    websocket.onmessage = function (evt) {
     
        onMessage(evt)
    };
    websocket.onerror = function (evt) {
     
        onError(evt)
    };


    function onOpen() {
     
        console.log("建立了连接开始心跳:");
        heartCheck.reset().start();
    }

    function onMessage(evt) {
     
        heartCheck.reset().start();
        var resultMsg = JSON.parse(evt.data);
        let addFlag = JSON.parse(sessionStorage.getItem("where_is_my"));
        switch (resultMsg.type) {
     
            case WS_OPEN_TYPE:
                socketUserId = resultMsg.chatRecord.userId;
                sessionStorage.setItem("myUserId", JSON.stringify(socketUserId));//储存当前的用户id
                break;
            case WS_CONFIRM_FRIEND_TYPE://朋友给我发信息
                if (addFlag == 0) {
      //
                    //我在消息列表,修改消息未读数量 和未读信息,还有更新时间,并且聊天记录增加
                    let list = JSON.parse(localStorage.getItem("myFriendAndGroupMsg"));
                    let msg = resultMsg.chatRecord.message;
                    let id = resultMsg.chatRecord.mongoId;
                    let whoId = resultMsg.chatRecord.userId;
                    let whoName = resultMsg.chatRecord.nickName;
                    let whoImg = resultMsg.chatRecord.img;
                    let time = resultMsg.chatRecord.updateTime;
                    var msgData = [{
     
                        outTime: time,
                        outMsg: msg,
                        nickName: whoName,
                        userId: whoId,
                        id: id,
                        img: whoImg,
                        subMsgType: 2,
                        msgNotReadyCount: 1
                    }];
                    if (list != null && list != []) {
     
                        let flag = true;
                        for (let i = 0; i < list.length; i++) {
     
                            //当前已有的消息列表
                            if (list[i].userId === whoId) {
     
                                //如果消息已有的消息列表匹配到了消息
                                let msgId = MESSAGE_FRIEND_ID + whoId;
                                msgAuto(msgId, whoImg, whoName, msg, time);

                                //全部都替换完毕后 将修改一下数据存起来
                                list[i].outTime = time;
                                list[i].outMsg = msg;
                                list[i].nickName = whoName;
                                list[i].img = whoImg;
                                flag = false;
                                break;
                            }
                        }
                        if (flag) {
     
                            list.push({
     
                                outTime: time,
                                outMsg: msg,
                                nickName: whoName,
                                userId: whoId,
                                id: id,
                                img: whoImg,
                                subMsgType: 2,
                                msgNotReadyCount: 1
                            });

                            // 匹配不到消息就新增一条,并且把缓存的数据也新增一条
                            localStorage.setItem("myFriendAndGroupMsg", JSON.stringify(list));
                            $(".not-msg-div").hide();
                            $("#main").css("background-color", "#eeeeee");
                            $.get("../../assets/tmp/message/friend_message.lay", function (tpl) {
     
                                renderTemplate(tpl, "my_message", list);
                            });
                        }

                    } else {
     
                        //没有任何数据 增加列表
                        localStorage.setItem("myFriendAndGroupMsg", JSON.stringify(msgData));
                        $(".not-msg-div").hide();
                        $("#main").css("background-color", "#eeeeee");
                        $.get("../../assets/tmp/message/friend_message.lay", function (tpl) {
     
                            renderTemplate(tpl, "my_message", msgData);
                        });
                    }

                } else {
     
                    //当前在消息里面
                    console.log("消息里面渲染开始");
                    layui.use(['form', 'laytpl'], function () {
     
                        let c = [{
     
                            userId: resultMsg.chatRecord.userId,
                            msgType: resultMsg.chatRecord.msgType,
                            message: resultMsg.chatRecord.message,
                            createTime: resultMsg.chatRecord.createTime
                        }];
                        console.log(c);
                        var laytpl = layui.laytpl;
                        laytpl($("#page_template_id").html()).render(c, function (html) {
     
                            $("#chatBox-content-demo").append(html);
                        });
                        layui.form.render();// 渲染
                        try {
     
                            document.getElementById("chatBox-content-demo").scrollTop = document.getElementById("chatBox-content-demo").scrollHeight
                        } catch (e) {
     
                            //
                        }
                    });


                }
                //本地储存消息
                let msgKey = SUB_FRIEND_MESSAGE + resultMsg.chatRecord.userId;
                console.log(msgKey);
                let locatMsg = localStorage.getItem(msgKey);
                if (locatMsg != null) {
     
                    locatMsg = Array.from(JSON.parse(locatMsg));
                    let d = {
     
                        userId: resultMsg.chatRecord.userId,
                        msgType: resultMsg.chatRecord.msgType,
                        message: resultMsg.chatRecord.message,
                        createTime: resultMsg.chatRecord.createTime
                    };
                    if (locatMsg != null) {
     
                        locatMsg.push(d)
                    } else {
     
                        locatMsg = d;
                    }
                    localStorage.setItem(msgKey, JSON.stringify(locatMsg));
                }

                break;
            case WS_CONFIRM_GROUP_TYPE:
                break;
            case WS_SEND_FRIEND_TYPE:
                break;
            case WS_SEND_GROUP_TYPE:
                break;
            case WS_JUMP_TYPE:
                console.log("心跳检测回复啦");
                break;
        }


    }

    function writeToScreen(message) {
     
        layer.msg(message, {
     icon: 6});
    }


    function onClose(evt) {
     
        reconnect()
    }

    function onError(evt) {
     
        reconnect()
    }

// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function () {
     
        websocket.close();
    }

    /**
     *
     * @param msgId 具体那条消息
     * @param whoImg 消息头像
     * @param whoName 消息名字
     * @param msg  具体消息
     * @param time 更新时间
     */
    function msgAuto(msgId, whoImg, whoName, msg, time) {
     
        let count = null;
        let num = 0;
        let $1 = $(document.getElementById(msgId).getElementsByClassName("layui-badge"));
        $($1[0]).show();
        $(document.getElementById(msgId).getElementsByClassName("layui-badge")[0]).removeAttr("style");
        $(document.getElementById(msgId).getElementsByClassName("sub-message")[0]).show();
        $(document.getElementById(msgId).getElementsByClassName("sub-message")[0]).removeAttr("style");
        count = $(document.getElementById(msgId).getElementsByClassName("layui-badge")[0]).text();
        $(document.getElementById(msgId).getElementsByClassName("layui-badge")[0]).text(count == null || count === '' ? ++num : ++count);
        $(document.getElementById(msgId).getElementsByClassName("sub-message")[0]).text("[" + (count == null || count === '' ? num : count) + "条]")
        //消息处理,时间处理

        $(document.getElementById(msgId).getElementsByClassName("sub-message")[1]).text(msg);

        let nowImg = $(document.getElementById(msgId).getElementsByClassName("head_img")).attr("src");
        if (nowImg === whoImg) {
     
            //相同啥也不做
        } else {
     
            //不相同替换
            $(document.getElementById(msgId).getElementsByClassName("head_img")).attr("src", whoImg);
        }

        $(document.getElementById(msgId).getElementsByClassName("time_span")).text(layui.util.toDateString(time, 'MM-dd HH:mm'));


        // form.render();
    }


}

function reconnect() {
     
    setTimeout(function () {
          //没连接上会一直重连,设置延迟避免请求过多
        if(localStorage.getItem("token")!=null||sessionStorage.getItem("token")!=null){
     
            socketInti(wsUrl);
        }
    }, 2000);
}


//心跳检测
var heartCheck = {
     
    timeout: 8500,
    timeoutObj: null,
    serverTimeoutObj: null,
    reset: function () {
     
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        return this;
    },
    start: function () {
     
        var self = this;
        this.timeoutObj = setTimeout(function () {
     
            //这里发送一个心跳,后端收到后,返回一个心跳消息,
            //onmessage拿到返回的心跳就说明连接正常
            var str = socketUserId + "心跳了❥❥❥❥❥❥❥❥❥❥❥❥❥❥❥❥";
            console.log('建立心跳');
            websocket.send(getMessage(WS_JUMP_TYPE, null, 0, socketUserId, null, str, null, null));
            self.serverTimeoutObj = setTimeout(function () {
     //如果超过一定时间还没重置,说明后端主动断开了
                websocket.close();     //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
            }, self.timeout)
        }, this.timeout)
    }
}


/**
 * 获取发送出去的消息体
 * @param {
     Object} type 消息类型     0.连接  1.发送消息  2.接收消息  3.客户端保持心跳 4.群消息
 * @param {
     Object} msgType 消息类型  0.文字 1.图片,3.视频,
 * @param {
     Object} userId 发送的id
 * @param {
     Object} recipientId 接收者id
 * @param {
     Object} msg 消息
 * @param {
     Object} msgId 消息id 传null
 * @param {
     Object} ext 扩展字段 传null
 */
function getMessage(type, msgType, sysMsgType, userId, recipientId, msg, msgId, ext) {
     
    return JSON.stringify({
     
        type: type,
        chatRecord: {
     
            id: msgId,
            userId: userId,
            msgType: msgType,
            recipientId: recipientId,
            sysMsgType: sysMsgType,
            message: msg,
        },
        ext: ext
    });
}

main.js

var initSysFlag = sessionStorage.getItem("initSysFlag"); //系统消息数据初始化开关
var initUserFlag = sessionStorage.getItem("initUserFlag"); //用户消息数据初始化开关

$(document).ready(function () {
     
    $("#sub_main").toggle(10);
    // $("#main").toggle(10);
    init();//初始化聊天信息
    socketInti(wsUrl);//初始化连接
    if (localStorage.getItem("goFriend") != null) {
     
        sub_message(JSON.parse(localStorage.getItem("goFriend")).userFriendId, 1);
    }

    $("#remove_sub_message").on("click", function () {
     
        $("#sub_main").toggle(10);
        $("#msgList").toggle(10);
        sessionStorage.setItem("where_is_my", JSON.stringify(0));
        if(localStorage.getItem("goFriend")!=null){
     
            localStorage.removeItem("goFriend");
            javascript:history.go(-1);
            javascript:history.go(-1)
        }
    });
});

function init() {
     
    //默认是没有消息的,打开没有消息的页面
    $(".not-msg-div").show();
    //系统消息 start
    if (initSysFlag == null) {
     
        $.ajax({
     
            url: httpurl + "/api/queryByIdCountList",
            beforeSend:
                function (request) {
     
                    request.setRequestHeader(tokenKey, tokenValue)
                },
            type: "post",
            dataType: "json",
            success: function (results) {
     
                if(results.result==null){
     
                    return;
                }
                if (results.result.sys!=null && results.result.sys.length > 0) {
     
                    $(".not-msg-div").hide();
                    $("#main").css("background-color", "#eeeeee");
                    for (let i = 0; i < results.result.sys.length; i++) {
     
                        if (results.result.sys[i].sysMsgType == 0 || results.result.sys[i].sysMsgType == 1) {
     
                            if (results.result.sys[i].count > 0) {
     
                                localStorage.setItem("sysMsg", JSON.stringify(results.result.sys));
                            }
                        }
                    }
                    $.get("../../assets/tmp/message/sys_message.lay", function (tpl) {
     
                        renderTemplate(tpl, "sys_msg_tmp", JSON.parse(localStorage.getItem("sysMsg")));
                    });
                    sessionStorage.setItem("initSysFlag", 0);
                } else {
     
                    $("#main").removeAttr("style", "");
                }
            }
        });
    } else {
     
        let data = JSON.parse(localStorage.getItem("sysMsg"));
        if (data != null) {
     
            $(".not-msg-div").hide();
            $("#main").css("background-color", "#eeeeee");
            $.get("../../assets/tmp/message/sys_message.lay", function (tpl) {
     
                renderTemplate(tpl, "sys_msg_tmp", data);
            });
        } else {
     
            $("#main").removeAttr("style", "");
        }
    }
    //系统消息 end

    //好友,群消息 start
    if (initUserFlag == null) {
     

        $.ajax({
     
            url: httpurl + "/api/queryByUserIdGetMsgList",
            dataType: "json",
            type: "post",
            beforeSend:
                function (request) {
     
                    request.setRequestHeader(tokenKey, tokenValue)
                },
            success: function (results) {
     
                if(results==null){
     
                    return;
                }

                if (results.result.length > 0) {
     
                    $(".not-msg-div").hide();
                    $("#main").css("background-color", "#eeeeee");
                    let json = [];
                    for (let i = 0; i < results.result.length; i++) {
     
                        if (results.result[i].msgNotReadyCount > 0) {
     
                            json.push(results.result[i]);
                        }
                    }
                    localStorage.setItem("myFriendAndGroupMsg", JSON.stringify(json));
                    $.get("../../assets/tmp/message/friend_message.lay", function (tpl) {
     
                        renderTemplate(tpl, "my_message", JSON.parse(localStorage.getItem("myFriendAndGroupMsg")));
                    });
                    sessionStorage.setItem("initUserFlag", 0);
                } else {
     
                    $("#main").removeAttr("style", "");
                }
            }
        });
    } else {
     
        let data = JSON.parse(localStorage.getItem("myFriendAndGroupMsg"));
        if (data != null) {
     
            $(".not-msg-div").hide();
            $("#main").css("background-color", "#eeeeee");
            $.get("../../assets/tmp/message/friend_message.lay", function (tpl) {
     
                renderTemplate(tpl, "my_message", data);
            });
        } else {
     
            $("#main").removeAttr("style", "");
        }
    }


}

/**
 * @param id MongoDB的id
 * @param userId 数据库的userId
 * @param type
 * @param c
 */
function clickMsg(id, type, c, userId) {
     
    //消息被点击了,进入消息body的同时取消红点
    $("#sub_main").toggle(10);
    $("#msgList").toggle(10);
    let json = null;
    switch (type) {
     
        case 0:
            json = JSON.parse(localStorage.getItem("sysMsg"));
            break;
        case 1:
            json = JSON.parse(localStorage.getItem("sysMsg"));
            break;
        case 2:
            //好友消息
            sub_message(userId, 0);
            sessionStorage.setItem("where_is_my", JSON.stringify(1));
            json = JSON.parse(localStorage.getItem("myFriendAndGroupMsg"));
            break;
        case 3:
            //群聊消息
            json = JSON.parse(localStorage.getItem("myFriendAndGroupMsg"));
            break;
    }

    //消息已读处理 1.将客户端的读取状态修改 然后服务端修改数据库
    let count;
    if (json != null) {
     
        for (let i = 0; i < json.length; i++) {
     
            if (type == 0 || type == 1) {
     
                if (json[i].id == id) {
     
                    count = json[i].count;
                    json[i].count = 0;
                    localStorage.setItem("sysMsg", JSON.stringify(json));
                }
            } else {
     
                //好友消息
                json[i].msgNotReadyCount = 0;
                localStorage.setItem("myFriendAndGroupMsg", JSON.stringify(json))
            }
            $(c.getElementsByClassName("layui-badge")[0]).text(0);
            $(c.getElementsByClassName("sub-message")[0]).text('[' + 0 + '条]');
            $(c.getElementsByClassName("layui-badge")[0]).hide();
            $(c.getElementsByClassName("layui-badge")[0]).css('position', 'absolute');
            $(c.getElementsByClassName("sub-message")[0]).hide();
            $(c.getElementsByClassName("sub-message")[0]).css('position', 'absolute');

            //并且修改数据库的已读状态
        }


        if (id != null) {
     
            $.ajax({
     
                url: httpurl + "/api/updateSysMsgType",
                type: "post",
                beforeSend:
                    function (request) {
     
                        request.setRequestHeader(tokenKey, tokenValue)
                    },
                dataType: "json",
                data: {
     id: id, readCount: count, type: type},
                success: function (result) {
     
                    //
                }
            })
        }

    }
}


/**
 * 模板的渲染方法
 * @param tmp 需要渲染的模板
 * @param resultContentId 模板渲染后显示在页面的内容的容器id
 * @param data json对象
 */
function renderTemplate(tmp, resultContentId, data) {
     
    layui.use(['form', 'laytpl'], function () {
     
        var laytpl = layui.laytpl;
        laytpl(tmp).render(data, function (html) {
     
            $("#" + resultContentId).html(html);
        });
    });
    layui.form.render();// 渲染
    try {
     
        document.getElementById("chatBox-content-demo").scrollTop = document.getElementById("chatBox-content-demo").scrollHeight
    } catch (e) {
     
        //
    }
};

//2020-04-17T18:36:37.948
function convertDateFromString(dateString) {
     
    if (dateString) {
     
        var sdate = dateString.split('T')[0].split("-")
        var sTime = dateString.split('T')[1].split(":")
        var date = new Date(sdate[0], sdate[1] - 1, sdate[2], sTime[0], sTime[1], sTime[2]);
        return date.Format("yyyy-MM-dd hh:mm:ss.S");
    }
}

Date.prototype.Format = function (fmt) {
     
    var o = {
     
        "M+": this.getMonth() + 1, //月份
        "d+": this.getDate(), //日
        "h+": this.getHours(), //小时
        "m+": this.getMinutes(), //分
        "s+": this.getSeconds(), //秒
        "q+": Math.floor((this.getMonth() + 3) / 3), //季度
        "S": '' //毫秒
    };
    if (/(y+)/.test(fmt))
        fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    for (var k in o) {
     
        if (new RegExp("(" + k + ")").test(fmt)) {
     
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
        }
    }
    return fmt;
}

sub_message.js

function sub_message(userId, type) {
     
    console.log("从别的地方进来了" + userId);
    if (type == 1) {
     
        //新增聊天
        $("#sub_main").toggle(10);
        $("#msgList").toggle(10);
    }
    sessionStorage.setItem("where_is_my", JSON.stringify(1));//代表进了聊天室
    sessionStorage.setItem("youUserId", userId); //对方id
    sessionStorage.setItem("myHeadImg", JSON.stringify("../../assets/img/message/has-message/系统消息_看图王.png"));
    sessionStorage.setItem("youHeadImg", JSON.stringify("../../assets/img/message/has-message/订单消息.png"));

    let youMsg = JSON.parse(sessionStorage.getItem("youUserId"));
    let msgKey = SUB_FRIEND_MESSAGE + youMsg;
    let locatMsg = JSON.parse(localStorage.getItem(msgKey));
    console.log("里面的KEy" + msgKey);
    if (JSON.parse(sessionStorage.getItem("sub_message_flag" + msgKey)) == null) {
     
        $.ajax({
     
            url: httpurl + "/api/queryAllById",
            type: "post",
            data: {
     recipientId: youMsg},
            dataType: "json",
            beforeSend:
                function (request) {
     
                    request.setRequestHeader(tokenKey, tokenValue)
                },
            success: function (results) {
     
                console.log("开始家在数据");
                sessionStorage.setItem("sub_message_flag" + msgKey, JSON.stringify(0));
                if (locatMsg == null) {
     
                    console.log("加载ajax");
                    //本地没有数据,直接映射,并且做本地缓存
                    localStorage.setItem(msgKey, JSON.stringify(results.result));
                    $.get("../../assets/tmp/message/sub_friend_message.lay", function (tpl) {
     
                        renderTemplate(tpl, "message_context", results.result);
                    });
                } else {
     
                    console.log("第2次else");
                    //本地有数据,吧数据拼接到本地数据后面
                    locatMsg = Array.from(locatMsg);
                    locatMsg.push(results.result);
                    $.get("../../assets/tmp/message/sub_friend_message.lay", function (tpl) {
     
                        renderTemplate(tpl, "message_context", locatMsg);
                    });
                }
            }
        })

    } else {
     
        console.log("第三次else");
        //已经加载过数据了,再次进来直接使用本地缓存
        $.get("../../assets/tmp/message/sub_friend_message.lay", function (tpl) {
     
            renderTemplate(tpl, "message_context", locatMsg);
        });
    }

    $(document).keyup(function (event) {
     
        if (event.keyCode == 13) {
     
            var textContent = $(".post_message").val().replace(/[\n\r]/g, '
'
); if (textContent != "") { let msgKey = SUB_FRIEND_MESSAGE + youMsg; let locatMsg = Array.from(JSON.parse(localStorage.getItem(msgKey))); let userId = JSON.parse(sessionStorage.getItem("myUserId")); let youId = JSON.parse(sessionStorage.getItem("youUserId")); console.log("我要发短信息了userId" + userId + "发给谁:" + youId); $(".post_message").val(""); websocket.send(getMessage(1, 0, null, userId, youId, textContent, null, null)); layui.use(['form', 'laytpl'], function () { let c = [{ userId: userId, msgType: 0, message: textContent, createTime: new Date() }]; locatMsg.push({ userId: userId, msgType: 0, message: textContent, createTime: new Date() }); localStorage.setItem(msgKey, JSON.stringify(locatMsg)); var laytpl = layui.laytpl; laytpl($("#page_template_id").html()).render(c, function (html) { $("#chatBox-content-demo").append(html); }); layui.form.render();// 渲染 try { document.getElementById("chatBox-content-demo").scrollTop = document.getElementById("chatBox-content-demo").scrollHeight } catch (e) { // } }); } } }); /** * 获取发送出去的消息体 * @param { Object} type 消息类型 0.连接 1.发送消息 2.接收消息 3.客户端保持心跳 4.群消息 * @param { Object} msgType 消息类型 0.文字 1.图片,3.视频, * @param { Object} userId 发送的id * @param { Object} recipientId 接收者id * @param { Object} msg 消息 * @param { Object} msgId 消息id 传null * @param { Object} ext 扩展字段 传null */ function getMessage(type, msgType, sysMsgType, userId, recipientId, msg, msgId, ext) { return JSON.stringify({ type: type, chatRecord: { id: msgId, userId: userId, msgType: msgType, recipientId: recipientId, sysMsgType: sysMsgType, message: msg, }, ext: ext }); } };

.到这里大部分代码都在这里了,写的比较烂毕竟是前后端一个人搞。而且是第一次写即时通讯。

你可能感兴趣的:(java,javascript,mongodb,websocket,html)