网站定制哪家正规,网站建设 文档下载,ios移动网站开发,印尼做网站的教学 中文目录 一、认识WebSocket
二、HTML实现聊天
三、微信小程序实现聊天 一、认识WebSocket
1.首先博主在初学Java时自我感觉走了很多弯路#xff0c;因为以前见识短#xff0c;在接触聊天功能时根本就没能想到有WebSocket这个聊天框架#xff0c;就只能用底层的UDP或TCP实现聊…目录 一、认识WebSocket
二、HTML实现聊天
三、微信小程序实现聊天 一、认识WebSocket
1.首先博主在初学Java时自我感觉走了很多弯路因为以前见识短在接触聊天功能时根本就没能想到有WebSocket这个聊天框架就只能用底层的UDP或TCP实现聊天功能及其繁琐。
1.在入门Java后的朋友学到网络编程会知道UDP和TCP两个知识点没错WebSocket是一种在单个TCP连接上进行全双工通信的协议。基于TCP协议的一个框架TCP知识点比较多具体咱们就不多说了直接实践怎么使用吧。
二、HTML实现聊天
首先我先贴出完整代码然后解释
1.html代码这里我就不单独写js文件了这个html实现的是一对一聊天还有一对多多对多群聊
script srchttps://code.jquery.com/jquery-3.6.4.min.js/script
script srchttps://cdn.jsdelivr.net/sockjs/1.1.4/sockjs.min.js/script
script srchttps://cdn.bootcdn.net/ajax/libs/stomp.js/2.3.3/stomp.min.js/scriptscriptvar stompClient null;function connect() {var content $(#message).val();var sender tbb;var socket new SockJS(http://127.0.0.1:8080/chat);stompClient Stomp.over(socket);stompClient.connect({}, function (frame) {console.log(Connected: frame);stompClient.subscribe(/user/tbb/message, function (message) {showMessage(JSON.parse(message.body));});});}function disconnect() {if (stompClient ! null) {stompClient.disconnect();}console.log(Disconnected);}function sendMessage() {var content $(#message).val();var sender $(#sender).val();stompClient.send(/app/chat/send/to/user, {}, JSON.stringify({content: content,sender: sender,unionId: sender,}));}function showMessage(message) {$(#chat).append(div message.sender : message.content /div);}
/scriptdivinput typetext idsender placeholderEnter your nameinput typetext idmessage placeholderType a message...button onclicksendMessage()Send/buttonbutton onclickconnect()Connect/buttonbutton onclickdisconnect()Disconnect/button
/divdiv idchat123/div 2.SpringBoot完整代码
1WebSocketConfig.java配置文件关键文件
package com.example.mengchuangyuan.common.chat.config;import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;Configuration
EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker(/user,/agent,/topic); // 定义消息代理客户端订阅的地址前缀config.setApplicationDestinationPrefixes(/app); // 定义客户端发送消息的地址前缀config.setUserDestinationPrefix(/user);}Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint(/chat).setAllowedOriginPatterns(*).withSockJS(); // 定义WebSocket端点客户端连接的地址}
}
2ChatController.java控制层
package com.example.mengchuangyuan.common.chat.controller;import com.example.mengchuangyuan.common.chat.entry.ChatMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.stereotype.Controller;Slf4j
Controller
public class ChatController {Autowiredprivate SimpMessagingTemplate simpMessagingTemplate;/*** 发消息群发* param chatMessage 获取用户消息* return 返回要回复的消息*///第一种方式MessageMapping(/chat/send) // 定义消息映射路径SendTo(/agent/public) // 发送消息到指定的代理路径public ChatMessage sendMessage(ChatMessage chatMessage) {log.info(chatMessage.toString());return chatMessage;}//方法二
// MessageMapping(/agent/send)
// public void getAgentInfo (Payload ChatMessage chatMessage) {
// System.out.println(发送群发消息);
// // 使用api进行推送
// simpMessagingTemplate.convertAndSend(/agent/public2, chatMessage);
// }/*** 发送给自己* param chatMessage* return*/MessageMapping(/agent/send/user)
// 这里的路径必须还是以广播的前缀为前缀否则无法接收SendToUser(/agent/info)public ChatMessage sendUserMessage(ChatMessage chatMessage) {log.info(chatMessage.toString());return chatMessage;}/*** 发送给指定用户* param chatMessage* return*/MessageMapping(/chat/send/to/user)
// 这里的路径必须还是以广播的前缀为前缀否则无法接收public void sendToUserMessage(ChatMessage chatMessage) {log.info(发送给指定用户:chatMessage.toString());simpMessagingTemplate.convertAndSendToUser(chatMessage.getUnionId(),/message,chatMessage);}
}
package com.example.mengchuangyuan.common.chat.entry;import lombok.Data;Data
public class ChatMessage {private String unionId;private String content;private String sender;
}以上就是完整代码
3.接下来我来简单解释一下因为一对一聊天比其他相对绕一点所以博主就解释它就好了且看下面四段被截取的代码 function connect() {var content $(#message).val();//发送的消息内容var sender tbb;//接收的人var socket new SockJS(http://127.0.0.1:8080/chat);//连接后端socket的地址stompClient Stomp.over(socket);stompClient.connect({}, function (frame) {console.log(Connected: frame);//这里我写死了接收来自tbb这个人的消息/user/tbb/message可以改成/user/sender/messagestompClient.subscribe(/user/tbb/message, function (message) { showMessage(JSON.parse(message.body));});});}上面的代码可以称之为连接服务器并且实时监听tbb给后端发来的消息
package com.example.mengchuangyuan.common.chat.config;import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;Configuration
EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {config.enableSimpleBroker(/user,/agent,/topic); // 定义消息代理客户端订阅的地址前缀config.setApplicationDestinationPrefixes(/app); // 定义客户端发送消息的地址前缀config.setUserDestinationPrefix(/user);}Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint(/chat).setAllowedOriginPatterns(*).withSockJS(); // 定义WebSocket端点客户端连接的地址}
}
上面两段是对应的在html的http://127.0.0.1:8080/chat中chat对应上面Java代码的chat必须一致要不然连不上/user/tbb/message路由中user对应上面Java代码的user必须一致要不然发消息时对方收不到消息博主以前踩过这个坑而这个tbb则是接收发消息的人的消息
function sendMessage() { //发消息给对方var content $(#message).val();var sender $(#sender).val();stompClient.send(/app/chat/send/to/user, {}, JSON.stringify({ //发消息的后端的路由content: content,//消息内容sender: sender,//消息姓名unionId: sender,//消息id这里我把消息姓名也作为id}));}/*** 发送给指定用户* param chatMessage* return*/MessageMapping(/chat/send/to/user)
// 这里的路径必须还是以广播的前缀为前缀否则无法接收public void sendToUserMessage(ChatMessage chatMessage) {log.info(发送给指定用户:chatMessage.toString());//chatMessage.getUnionId()是发送给某人的id/message对应四段中第一段的/user/tbb/message中的message
simpMessagingTemplate.convertAndSendToUser(chatMessage.getUnionId(),/message,chatMessage);} 最后两段是对应的用于给对方发消息。
三、微信小程序实现聊天
首先这是博主自己摸索了很久出来的一套小程序聊天体系。
1.聊天数据结构及框架
涉及到了redis缓存因此需要下载redis的依赖包
聊天数据结构如下自我感觉存在一定缺陷懒得改进了
整体储存结构
聊天界面结构{openid1openid2:{linkType:[info1,info2]}}
如图聊天界面 聊天列表结构{linkType:[openid1openid2,openid3openid4]}
如图聊天列表
info的结构{mid:,type:,linkType:,formUser:fromUser,toUser:toUser,message:,date:,nowDate:} fromUser和toUser的结构{openid:,phone:,name:,headImg:} openid1和openid2为fromUser和toUser的openidlinkType属于哪个板块聊天(比如相亲聊天板块或者外卖或商城聊天板块)
type作用于获取redis缓存的聊天记录和聊天心跳检测(备注因为获取历史聊天记录和心跳检测是以聊天方式向后端发起请求因此我用type的聊天要区分是用户发起聊天还是其他请求。由前端自动发动聊天请求获取历史聊天记录由前端发起聊天请求检测心跳检测心跳的目的是为了确保聊天过程不掉线)
date聊天的时间段(备注可以设置5分钟显示一个时间段聊天的时间比如微信隔5分钟后再发一条信息上面会显示时间这里我设置的date就充当这个角色)
nowDate每一句话的时间主要用于计算当前时间是否与上一句聊天记录的时间是否间隔5分钟如果间隔5分钟那么上面的date记录该时间如果间隔不到5分钟则date设置为空。 整体结构上两个人的openid连接作为获取他们之间所有功能板块历史聊天记录的内容的键(key)。以linkType作为键(key)获取某个功能板块的所有历史聊天记录这里聊天记录用集合来储存保证了聊天记录顺序。
2.Java SpringBoot代码
1MiniWebSocketConfig.java文件
package com.example.mengchuangyuan.common.chat.mini.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;Configuration
public class MiniWebSocketConfig {Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}2MiniWebSocketController.java文件
package com.example.mengchuangyuan.common.chat.mini.controller;import com.example.mengchuangyuan.common.redis.tool.SufferVariable;
import com.example.mengchuangyuan.common.tool.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.util.*;Slf4j
RestController
RequestMapping(/mini/socket)
public class MiniWebSocketController {Value(${upload.messageChatUrl})private String messageChatUrl;Value(${upload.messageChatPath})private String messageChatPath;private final ListMapString,Object mapList new ArrayList();GetMapping(/get/openid/history)private Result getOpenidHistory(RequestParam(openid) String openid,RequestParam(linkType) String linkType){
// log.info(openid);ListString openids (ListString) SufferVariable.openidKeys.get(linkType);
// if (keyMapnull){
// return Result.error();
// }
// ListString openids (ListString) keyMap.get(openid);
// log.info(所以openidString.valueOf(openids));if (openidsnull){return Result.error();}mapList.clear();for (String openid1: openids) {if (openid1.contains(openid)) {
// if (SufferVariable.messageMap.get(openid1)!null){mapList.add((MapString, Object) SufferVariable.messageMap.get(openid1));
// log.info(聊天记录缓存openid);
// log.info(String.valueOf(SufferVariable.messageMap.get(openid1)));// }}}return Result.success(mapList);}//上传聊天图片PostMapping(/img/upload)public Result imgUpDown(RequestParam(file) MultipartFile file,RequestParam(filename) String filename) throws IOException {System.out.println(filename);File file1 new File(messageChatPath,filename);if(!file1.exists()) {if(!file1.mkdirs()){ //创建目录return Result.error();}}//获取文件名String fileName file.getOriginalFilename();//获取文件后缀名。也可以在这里添加判断语句规定特定格式的图片才能上传否则拒绝保存。String suffixName fileName.substring(fileName.lastIndexOf(.));//为了避免发生图片替换这里使用了文件名重新生成fileName UUID.randomUUID()suffixName;file.transferTo(new File(file1,fileName));return Result.success(messageChatUrlfilename/fileName);}
}3WebSocketEndPoint.java文件
package com.example.mengchuangyuan.common.chat.mini.mapper;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.mengchuangyuan.common.redis.mapper.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;//对外公布的一个后端站点
//ws://localhost:8080/websocket/用户id
ServerEndpoint(value /websocket/{userId})
Component
Slf4j
public class WebSocketEndPoint {//与某个客户端的连接会话需要他来给客户端发送数据private Session session;Autowiredprivate SessionPool sessionPool;
// Autowired
// private RedisUtils redisUtils;private static WebSocketEndPoint webSocketEndPoint;//初始化 ②PostConstructpublic void init() {webSocketEndPoint this;webSocketEndPoint.sessionPool this.sessionPool;}//连接建立成功调用的方法OnOpenpublic void onOpen(Session session, PathParam(userId) String userId) {//把会话加入连接池中//userId通过用户传入session是系统自动产生SessionPool.sessions.put(userId, session);//TODO 可以添加日志操作}//关闭会话的时候OnClosepublic void onClose(Session session) throws IOException {webSocketEndPoint.sessionPool.close(session.getId());session.close();}//接收客户端的消息后调用的方法在这里可以进行各种业务逻辑的操作OnMessagepublic void onMessage(String message, Session session) {System.out.println(message);
// log.info(redisUtils:redisUtils);//心跳检测if (message.equalsIgnoreCase(ping)) {try {MapString, Object params new HashMap();params.put(type, pong);session.getBasicRemote().sendText(JSON.toJSONString(params));} catch (Exception e) {e.printStackTrace();}return;}//将Json字符串转为键值对
// HashMap params JSON.parseObject(message, HashMap.class);JSONObject params JSON.parseObject(message);webSocketEndPoint.sessionPool.sendMessage(params);//这里的业务逻辑仅仅是把收到的消息返回给前端
// SessionPool.sendMessage(message);}
}4SessionPool.java文件
package com.example.mengchuangyuan.common.chat.mini.mapper;import com.alibaba.fastjson.JSON;
import com.example.mengchuangyuan.common.redis.mapper.RedisUtils;
import com.example.mengchuangyuan.common.redis.tool.SufferVariable;
import com.example.mengchuangyuan.common.tool.DateYMDms;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;import javax.websocket.Session;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;Component
Slf4j
public class SessionPool {Autowiredprivate RedisUtils redisUtils;//key-value : userId - 会话系统创建public static MapString, Session sessions new ConcurrentHashMap();//避免多线程问题public void close(String sessionId) {//sessionId是在session中添加了一个标识准确定位某条sessionfor (String userId : SessionPool.sessions.keySet()) {Session session SessionPool.sessions.get(userId);if (session.getId().equals(sessionId)) {sessions.remove(userId);break;}}}public void sendMessage(String userId, String message) {sessions.get(userId).getAsyncRemote().sendText(message);}//消息的群发业务逻辑的群发public void sendMessage(String message) {
// redisUtils.cacheValue(chatMessage,String.valueOf(SufferVariable.messageMap));for (String sessionId : SessionPool.sessions.keySet()) {SessionPool.sessions.get(sessionId).getAsyncRemote().sendText(message);}}//点对点的消息推送public void sendMessage(MapString, Object params) {log.info(消息内容String.valueOf(params));long mid System.currentTimeMillis();MapString,Object formUser (MapString, Object) params.get(formUser);MapString,Object toUser (MapString, Object) params.get(toUser);String userId formUser.get(openid).toString();String toUserId toUser.get(openid).toString();
// String msg params.get(message).toString();String type params.get(type).toString();String linkType params.get(linkType).toString();//获取用户sessionSession session sessions.get(toUserId);MapString,Object keyMap;ListString setOpenid;MapString,Object map;ListObject list;String uid userIdtoUserId;params.put(mid,mid);if (SufferVariable.messageMap.get(userIdtoUserId)nullSufferVariable.messageMap.get(toUserIduserId)null){
// messageMap.put(userIdtoUserId,userIdtoUserId);map new HashMap();list new ArrayList();map.put(linkType,list);SufferVariable.messageMap.put(uid,map);// list.add(params);}else {if (SufferVariable.messageMap.get(userIdtoUserId)!null){uid userIdtoUserId;}else {uid toUserIduserId;}map (MapString, Object) SufferVariable.messageMap.get(uid);list (ListObject) map.get(linkType);}//获取历史记录if (type.equalsIgnoreCase(history)){if (sessions.get(userId) ! null) {MapString, Object map2 new HashMap();
// map (MapString, Object) SufferVariable.messageMap.get(uid);map2.put(type, isHistory);map2.put(message,list);sessions.get(userId).getAsyncRemote().sendText(JSON.toJSONString(map2));}return;}String nowDate DateYMDms.getUtilDate();if (list.size()!0) {MapString, Object lastMap (MapString, Object) list.get(list.size() - 1);String date DateYMDms.getYMDms(5, lastMap.get(nowDate).toString());params.put(date, date);}else {params.put(date, nowDate);}
// if (SufferVariable.openidKeys.get(linkType)null){
// keyMap new HashMap();
// }else {
// keyMap (MapString, Object) SufferVariable.openidKeys.get(linkType);
// }
// if (keyMap.get(userId)null){
// setOpenid new ArrayList();
// }else {
// log.info(String.valueOf(SufferVariable.openidKeys));
// setOpenid (ListString) keyMap.get(userId);
// }if (SufferVariable.openidKeys.get(linkType) null){setOpenid new ArrayList();}else {setOpenid (ListString) SufferVariable.openidKeys.get(linkType);}setOpenid.add(uid);SetString set new LinkedHashSet(setOpenid);setOpenid.clear();setOpenid.addAll(set);
// keyMap.put(userId,setOpenid);SufferVariable.openidKeys.put(linkType,setOpenid);params.put(nowDate,nowDate);list.add(params);map.put(linkType,list);SufferVariable.messageMap.put(uid,map);redisUtils.cacheValue(chatMessage,SufferVariable.messageMap);redisUtils.cacheValue(chatOpenidKeys,SufferVariable.openidKeys);if (session ! null) {log.info(uidString.valueOf(SufferVariable.messageMap.get(uid)));session.getAsyncRemote().sendText(JSON.toJSONString(params));}
// log.info(String.valueOf(messageMap));
// params.remove(formUserId);//session不为空的情况下进行点对点推送}
}
以上小程序相关代码存在一些缺陷并且未完整
需要小程序及SpringBoot完整代码的朋友可以私信博主
好了本次分享就到此结束。