前言:
现在姐妹们对“用html制作微信聊天”大致比较关注,咱们都想要分析一些“用html制作微信聊天”的相关文章。那么小编在网上收集了一些关于“用html制作微信聊天””的相关文章,希望小伙伴们能喜欢,姐妹们快快来了解一下吧!1.背景
基于项目需求,最近需要实现一个简单的聊天功能。日常生活中,大家对于聊天也习以为常,微信、QQ等软件也经常用到,其实我们也可以引入一些第三方的sdk包等去实现,也可以利用WebSocket通信协议去手动实现简单的聊天。本文主要讲述下WebSocket实现的具体步骤及实现的效果图。
2.方案选型及优缺点介绍
方案一 利用http接口手动实现三个接口:sengMsg(消息发送)、receiveMsg(消息接收)、getHistoryMsg(获取历史消息) ,然后前端发送消息时调用sendMsg接口,将数据写入数据库以便获取历史消息使用,接收消息时前端声明一个定时器,每一秒钟去刷新消息接收接口,来获取消息内容显示到聊天框中,最后,如果用户需要翻看历史消息,调用getHistoryMsg接口即可。优点 后端实现简单,且能将聊天消息持久化到数据库永久保存,可以根据聊天室id随时获取消息内容缺点 由于频繁调用接口,服务器和api接口压力比较大,高并发情况下服务器可能会宕机,而且不进行消息发送时,由于定时器的使用,前端频繁请求会造成空跑,显然不太合理方案二 利用已有的WebSocket服务实现聊天功能优点 不用额外自己实现接口,直接按照WebSocket定义的规则直接套用即可缺点 消息没有持久化,如果服务宕机,可能无法查看历史消息
3.服务搭建及实现
3.1 引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId></dependency>3.2 声明socket配置类
@Configurationpublic class WebSocketConfig { //注入一个ServerEndpointExporter @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); }}3.3 声明聊天Controller
/** * 聊天控制器 * @ServerEndpoint("/chat/{userId}")中的userId是前端创建会话窗口时当前用户的id,即消息发送者的id */@ServerEndpoint("/chat/{userId}")@Componentpublic class ChatWebSocketController { private final Logger logger = Logger.getLogger(ChatWebSocketController.class); //onlineCount:在线连接数 private static AtomicInteger onlineCount = new AtomicInteger(0); //webSocketSet:用来存放每个客户端对应的MyWebSocket对象。 public static List<ChatWebSocketController> webSocketSet = new ArrayList<>(); //存放所有连接人信息 public static List<String> userList = new ArrayList<>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; //用户ID public String userId = ""; /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(Session session, @PathParam("userId") String userId) { this.session = session; this.userId = userId; this.userList.add(userId) ; //加入set中 webSocketSet.add(this); //在线数加1 onlineCount.incrementAndGet(); logger.info("有新连接加入!" + userId + "当前在线用户数为" + onlineCount.get()); JSONObject msg = new JSONObject(); try { msg.put("msg", "连接成功"); msg.put("status", "SUCCESS"); msg.put("userId", userId); sendMessage(JSON.toJSONString(msg)); } catch (Exception e) { logger.debug("IO异常"); } } /** * 连接关闭调用的方法 */ @OnClose public void onClose(@PathParam("userId") String userId ) { //从set中删除 webSocketSet.remove(this); onlineCount.decrementAndGet(); // 在线数减1 logger.info("用户"+ userId +"退出聊天!当前在线用户数为" + onlineCount.get()); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, @PathParam("userId") String userId ) { //客户端输入的消息message要经过处理后封装成新的message,后端拿到新的消息后进行数据解析,然后判断是群发还是单发,并调用对应的方法 logger.info("来自客户端" + userId + "的消息:" + message); try { MyMessage myMessage = JSON.parseObject(message, MyMessage.class); String messageContent = myMessage.getMessage();//messageContent:真正的消息内容 String messageType = myMessage.getMessageType(); if("1".equals(messageType)){ //单聊 String recUser = myMessage.getUserId();//recUser:消息接收者 sendInfo(messageContent,recUser,userId);//messageContent:输入框实际内容 recUser:消息接收者 userId 消息发送者 }else{ //群聊 sendGroupInfo(messageContent,userId);//messageContent:输入框实际内容 userId 消息发送者 } } catch (Exception e) { logger.error("解析失败:{}", e); } } /** * 发生错误时调用的方法 * * @OnError **/ @OnError public void onError(Throwable error) { logger.debug("Websocket 发生错误"); error.printStackTrace(); } public synchronized void sendMessage(String message) { this.session.getAsyncRemote().sendText(message); } /** * 单聊 * message : 消息内容,输入的实际内容,不是拼接后的内容 * recUser : 消息接收者 * sendUser : 消息发送者 */ public void sendInfo( String message , String recUser,String sendUser) { JSONObject msgObject = new JSONObject();//msgObject 包含发送者信息的消息 for (ChatWebSocketController item : webSocketSet) { if (StringUtil.equals(item.userId, recUser)) { logger.info("给用户" + recUser + "传递消息:" + message); //拼接返回的消息,除了输入的实际内容,还要包含发送者信息 msgObject.put("message",message); msgObject.put("sendUser",sendUser); item.sendMessage(JSON.toJSONString(msgObject)); } } } /** * 群聊 * message : 消息内容,输入的实际内容,不是拼接后的内容 * sendUser : 消息发送者 */ public void sendGroupInfo(String message,String sendUser) { JSONObject msgObject = new JSONObject();//msgObject 包含发送者信息的消息 if (StringUtil.isNotEmpty(webSocketSet)) { for (ChatWebSocketController item : webSocketSet) { if(!StringUtil.equals(item.userId, sendUser)) { //排除给发送者自身回送消息,如果不是自己就回送 logger.info("回送消息:" + message); //拼接返回的消息,除了输入的实际内容,还要包含发送者信息 msgObject.put("message",message); msgObject.put("sendUser",sendUser); item.sendMessage(JSON.toJSONString(msgObject)); } } } } /** * Map/Set的key为自定义对象时,必须重写hashCode和equals。 * 关于hashCode和equals的处理,遵循如下规则: * 1)只要重写equals,就必须重写hashCode。 * 2)因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。 * 3)如果自定义对象做为Map的键,那么必须重写hashCode和equals。 * * @param o * @return */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ChatWebSocketController that = (ChatWebSocketController) o; return Objects.equals(session, that.session); } @Override public int hashCode() { return Objects.hash(session); }}3.4 声明Controller中的MyMessage实体类
public class MyMessage implements Serializable { private static final long serialVersionUID = 1L; private String userId; private String message;//消息内容 private String messageType;//消息类型 1 代表单聊 2 代表群聊 public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getMessageType() { return messageType; } public void setMessageType(String messageType) { this.messageType = messageType; }}3.5 声明Controller中的StringUtil工具类
public final class StringUtil { /** * 对象为空 * * @param object * @return */ public static boolean isEmpty(Object object) { if (object == null) { return true; } if (object instanceof String && "".equals(((String) object).trim())) { return true; } if (object instanceof List && ((List) object).size() == 0) { return true; } if (object instanceof Map && ((Map) object).isEmpty()) { return true; } if (object instanceof CharSequence && ((CharSequence) object).length() == 0) { return true; } if (object instanceof Arrays && (Array.getLength(object) == 0)) { return true; } return false; } /** * 对象不为空 * * @param object * @return */ public static boolean isNotEmpty(Object object) { return !isEmpty(object); } /** * 查询字符串中某个字符首次出现的位置 从1计数 * * @param string 字符串 * @param c * @return */ public static int strFirstIndex(String c, String string) { Matcher matcher = Pattern.compile(c).matcher(string); if (matcher.find()) { return matcher.start() + 1; } else { return -1; } } /** * 两个对象是否相等 * * @param obj1 * @param obj2 * @return */ public static boolean equals(Object obj1, Object obj2) { if (obj1 instanceof String && obj2 instanceof String) { obj1 = ((String) obj1).replace("\\*", ""); obj2 = ((String) obj2).replaceAll("\\*", ""); if (obj1.equals(obj2) || obj1 == obj2) { return true; } } if (obj1.equals(obj2) || obj1 == obj2) { return true; } return false; } /** * 根据字节截取内容 * * @param bytes 自定义字节数组 * @param content 需要截取的内容 * @return */ public static String[] separatorByBytes(double[] bytes, String content) { String[] contentArray = new String[bytes.length]; double[] array = new double[bytes.length + 1]; array[0] = 0; //复制数组 System.arraycopy(bytes, 0, array, 1, bytes.length); for (int i = 0; i < bytes.length; i++) { content = content.substring((int) (array[i] * 2)); contentArray[i] = content; } String[] strings = new String[bytes.length]; for (int i = 0; i < contentArray.length; i++) { strings[i] = contentArray[i].substring(0, (int) (bytes[i] * 2)); } return strings; } /** * 获取指定字符串出现的次数 * * @param srcText 源字符串 * @param findText 要查找的字符串 * @return */ public static int appearNumber(String srcText, String findText) { int count = 0; Pattern p = Pattern.compile(findText); Matcher m = p.matcher(srcText); while (m.find()) { count++; } return count; } /** * 将字符串str每隔2个分割存入数组 * * @param str * @return */ public static String[] setStr(String str) { int m = str.length() / 2; if (m * 2 < str.length()) { m++; } String[] strings = new String[m]; int j = 0; for (int i = 0; i < str.length(); i++) { if (i % 2 == 0) { //每隔两个 strings[j] = "" + str.charAt(i); } else { strings[j] = strings[j] + str.charAt(i); j++; } } return strings; } /** * 定义一个StringBuffer,利用StringBuffer类中的reverse()方法直接倒序输出 * 倒叙字符串 * * @param s */ public static String reverseString2(String s) { if (s.length() > 0) { StringBuffer buffer = new StringBuffer(s); return buffer.reverse().toString(); } else { return ""; } } /** * 截取字符串中的所有日期时间 * * @param str * @return */ public static List<String> dateTimeSubAll(String str) { try { List<String> dateTimeStrList = new ArrayList<>(); String regex = "[0-9]{4}[-][0-9]{1,2}[-][0-9]{1,2}[ ][0-9]{1,2}[:][0-9]{1,2}[:][0-9]{1,2}"; Pattern pattern = compile(regex); Matcher matcher = pattern.matcher(str); while (matcher.find()) { String group = matcher.group(); dateTimeStrList.add(group); } return dateTimeStrList; } catch (Exception e) { e.getMessage(); return null; } } /** * 截取字符串中的所有日期 * * @param str * @return */ public static List<String> dateSubAll(String str) { try { List<String> dateStrList = new ArrayList<>(); Pattern pattern = compile("[0-9]{4}[-][0-9]{1,2}[-][0-9]{1,2}"); Matcher matcher = pattern.matcher(str); while (matcher.find()) { String group = matcher.group(); dateStrList.add(group); } return dateStrList; } catch (Exception e) { e.getMessage(); return null; } } /** * 获取随机字符串 * * @param length * @return */ public static String getRandomString(int length) { String base = "abcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); }}3.6 后台声明测试的html页面
<!DOCTYPE HTML><html><head> <title>WebSocket Chat Demo</title></head><body> <input id="inputContent" type="text" style="width:600px;"/> <button onclick="send()">Send</button> <button onclick="closeConnection()">Close</button> <div id="msg"></div></body><script type="text/javascript"> var websocket = null; //声明自己搭建的websocket服务 if ('WebSocket' in window) { var random = parseInt(Math.random() * 1000000) + ""; websocket = new WebSocket("ws://localhost:8005/chat/"+ random); } else { alert('Not support websocket') } //连接发生错误的回调方法 websocket.onerror = function() { setMessageInnerHTML("error"); }; //连接成功建立的回调方法 websocket.onopen = function(event) { //setMessageInnerHTML("open"); } //接收到消息的回调方法 websocket.onmessage = function(event) { setMessageInnerHTML(event.data); } //连接关闭的回调方法 websocket.onclose = function() { setMessageInnerHTML("close"); } //监听窗口关闭事件,当窗口关闭时关闭对应websocket连接 window.onbeforeunload = function() { websocket.close(); } //将消息回显在页面上 function setMessageInnerHTML(innerHTML) { document.getElementById('msg').innerHTML += innerHTML + '<br/>'; } //关闭连接 function closeConnection() { websocket.close(); } //发送消息 function send() { var msg = document.getElementById('inputContent').value; websocket.send(msg); }</script></html>
该类对应的路径如下:
4.启动服务并测试
页面输入ip+端口建立websocket连接并发送一条消息,测试结果如图:
注意:
注意
1.正常情况下,输入框中只输入要发送的实际聊天内容即可,比如“在吗老公,急事”,但是为了更容易测试,页面中输入的是拼接后的json消息体,接收者用户id,以及消息类型,实际开发中数据格式让前端处理即可,前端根据输入的内容拼接成如输入框图所示的数据格式即可2.messageType来区分单聊还是群聊,但是此处的群聊是建立连接的所有websocket服务,没有区分组概念,如果区分的话,后台接口请求路径中要添加上roomId参数,然后建立连接时将进入该聊天室的用户放入一个map集合中,群聊发送消息时,根据不同的roomId,只给该组的用户推送群聊消息即可
标签: #用html制作微信聊天