本项目开发周期比较短 大部分都是围绕业务来去实现 很多需要完善地方这里只是出了一个简短的例子
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
});
}
};
.到这里大部分代码都在这里了,写的比较烂毕竟是前后端一个人搞。而且是第一次写即时通讯。