-
使用技术:
-
Spring WebSocket
-
Spring-data-MongoDB
-
-
pom.xml
-
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-sync</artifactId> <version>3.9.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.4</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> </dependencies> <build> <plugins> <!-- java编译插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
-
-
编写Message对象
-
import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor @Document(collection = "message") // 指定表的名称 @Builder public class Message { @Id private ObjectId id; private String msg; /** * 消息状态,1-未读,2-已读 */ @Indexed private Integer status; @Field("send_date") @Indexed private Date sendDate; @Field("read_date") private Date readDate; @Indexed private User from; @Indexed private User to; }
-
User对象
-
import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor @Builder public class User { private Long id; private String username; }
-
-
构造数据:
-
import java.util.HashMap; import java.util.Map; public class UserData { public static final Map<Long,User> USER_MAP = new HashMap<>(); static { USER_MAP.put(1001L, User.builder().id(1001L).username("zhangsan").build()); USER_MAP.put(1002L, User.builder().id(1002L).username("lisi").build()); USER_MAP.put(1003L, User.builder().id(1003L).username("wangwu").build()); USER_MAP.put(1004L, User.builder().id(1004L).username("zhaoliu").build()); USER_MAP.put(1005L, User.builder().id(1005L).username("sunqi").build()); } }
-
-
编写MessageDAO
-
这只是快速做的做出来,没有去抽象接口.前面有抽象MongoDB的播客
-
import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.UpdateResult; import org.bson.types.ObjectId; import java.util.List; public interface MessageDAO { /** * 查询点对点聊天记录 * * @param fromId * @param toId * @param page * @param rows * @return */ List<Message> findListByFromAndTo(Long fromId, Long toId, Integer page, Integer rows); /** * 根据id查询数据 * * @param id * @return */ Message findMessageById(String id); /** * 更新消息状态 * * @param id * @param status * @return */ UpdateResult updateMessageState(ObjectId id, Integer status); /** * 新增消息 * * @param message * @return */ Message saveMessage(Message message); /** * 根据消息id删除数据 * * @param id * @return */ DeleteResult deleteMessage(String id); }
-
-
编写实现类:
-
import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.UpdateResult; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; @Component public class MessageDAOImpl implements MessageDAO { @Autowired private MongoTemplate mongoTemplate; /** * 实现:查询点对点消息记录 * @param fromId * @param toId * @param page * @param rows * @return */ @Override public List<Message> findListByFromAndTo(Long fromId, Long toId, Integer page, Integer rows) { // 用户A发送给用户B的条件 Criteria criteriaFrom = new Criteria().andOperator( Criteria.where("from.id").is(fromId), Criteria.where("to.id").is(toId) ); // 用户B发送给用户A的条件 Criteria criteriaTo = new Criteria().andOperator( Criteria.where("from.id").is(toId), Criteria.where("to.id").is(fromId) ); Criteria criteria = new Criteria().orOperator(criteriaFrom, criteriaTo); PageRequest pageRequest = PageRequest.of(page - 1, rows, Sort.by(Sort.Direction.ASC, "sendDate")); // 设置查询条件,分页 Query query = Query.query(criteria).with(pageRequest); // System.out.println(query); return this.mongoTemplate.find(query, Message.class); } @Override public Message findMessageById(String id) { return this.mongoTemplate.findById(new ObjectId(id), Message.class); } @Override public UpdateResult updateMessageState(ObjectId id, Integer status) { Query query = Query.query(Criteria.where("id").is(id)); Update update = Update.update("status", status); if (status.intValue() == 1) { update.set("send_date", new Date()); } else if (status.intValue() == 2) { update.set("read_date", new Date()); } return this.mongoTemplate.updateFirst(query, update, Message.class); } @Override public Message saveMessage(Message message) { // 写入发送时间 message.setSendDate(new Date()); message.setStatus(1); message.setId(ObjectId.get()); return this.mongoTemplate.save(message); } @Override public DeleteResult deleteMessage(String id) { Query query = Query.query(Criteria.where("id").is(id)); return this.mongoTemplate.remove(query, Message.class); } }
-
-
测试案例:
-
import org.bson.types.ObjectId; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Date; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class TestMessageDAO { @Autowired private MessageDAO messageDAO; @Test public void testSave() { Message message = Message.builder() .id(ObjectId.get()) .msg("你好") .sendDate(new Date()) .status(1) .from(new User(1001L, "zhangsan")) .to(new User(1002L, "lisi")) .build(); this.messageDAO.saveMessage(message); message = Message.builder() .id(ObjectId.get()) .msg("你也好") .sendDate(new Date()) .status(1) .to(new User(1001L, "zhangsan")) .from(new User(1002L, "lisi")) .build(); this.messageDAO.saveMessage(message); message = Message.builder() .id(ObjectId.get()) .msg("我在学习开发IM") .sendDate(new Date()) .status(1) .from(new User(1001L, "zhangsan")) .to(new User(1002L, "lisi")) .build(); this.messageDAO.saveMessage(message); message = Message.builder() .id(ObjectId.get()) .msg("那很好啊!") .sendDate(new Date()) .status(1) .to(new User(1001L, "zhangsan")) .from(new User(1002L, "lisi")).build(); this.messageDAO.saveMessage(message); System.out.println("ok"); } @Test public void testQueryList(){ List<Message> list = this.messageDAO.findListByFromAndTo(1001L, 1002L, 1, 10); for (Message message : list) { System.out.println(message); } } }
-
-
编写websocket
-
发送消息流程
-
接收消息流程
-
实现MessageHandler
-
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.util.HashMap; import java.util.Map; @Component public class MessageHandler extends TextWebSocketHandler { @Autowired private MessageDAO messageDAO; private static final ObjectMapper MAPPER = new ObjectMapper(); private static final Map<Long, WebSocketSession> SESSIONS = new HashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { Long uid = (Long) session.getAttributes().get("uid"); // 将当前用户的session放置到map中,后面会使用相应的session通信 SESSIONS.put(uid, session); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage textMessage) throws Exception { Long uid = (Long) session.getAttributes().get("uid"); JsonNode jsonNode = MAPPER.readTree(textMessage.getPayload()); Long toId = jsonNode.get("toId").asLong(); String msg = jsonNode.get("msg").asText(); Message message = Message.builder() .from(UserData.USER_MAP.get(uid)) .to(UserData.USER_MAP.get(toId)) .msg(msg) .build(); // 将消息保存到MongoDB message = this.messageDAO.saveMessage(message); // 判断to用户是否在线 WebSocketSession toSession = SESSIONS.get(toId); if (toSession != null && toSession.isOpen()) { //TODO 具体格式需要和前端对接 toSession.sendMessage(new TextMessage(MAPPER.writeValueAsString(message))); // 更新消息状态为已读 this.messageDAO.updateMessageState(message.getId(), 2); } } }
-
-
import org.apache.commons.lang3.StringUtils; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import java.util.Map; @Component public class MessageHandshakeInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { String path = request.getURI().getPath(); String[] ss = StringUtils.split(path, '/'); if(ss.length != 2){ return false; } if(!StringUtils.isNumeric(ss[1])){ //是否为数字 return false; } attributes.put("uid", Long.valueOf(ss[1])); return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } }
-
-
WebSocketConfig
-
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Autowired private MessageHandler messageHandler; @Autowired private MessageHandshakeInterceptor messageHandshakeInterceptor; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(this.messageHandler, "/ws/{uid}") .setAllowedOrigins("*") .addInterceptors(this.messageHandshakeInterceptor); } }
-
-
编写启动类
-
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ImApplication { public static void main(String[] args) { SpringApplication.run(ImApplication.class, args); } }
具体的mongodb配置,WebSocket启懂, 拦截器操作可以去看一下
-
https://mp.csdn.net/postedit/100059086 WebSocket
-
配置文件:
-
# Spring boot application server.port = 18081 spring.data.mongodb.uri=mongodb://172.0.0.1:27017/testdb
-
-
-
这样就可以通过开两个测试工具然后互相聊天了. 数据会写到MongoDB中.
-
问:前面的实现中,将Session对象放到全局的Map中,当连接变得非常多时,这将成为了系统瓶颈,因为不能进行分布式部署。
-
序列化到redis中, 但是session是不支持序列化的.
-
答:可以在socket服务器 后面加上一个消息系统. 分布式通过订阅.消息系统,然后消息系统去找session所在的socket服务器.
-
-
WebSocket 和 MongoDB 搭建简单的聊天系统
最新推荐文章于 2024-05-15 11:14:26 发布