本篇博客主要用于博主巩固项目知识,代码都是测试可用的
使用到的技术:
pom.xml:
4.0.0
com.fengstyle
test-websocket
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.1.0.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-mongodb
org.springframework.boot
spring-boot-starter-websocket
org.springframework.boot
spring-boot-starter-test
test
org.mongodb
mongodb-driver-sync
3.9.1
junit
junit
4.12
test
org.projectlombok
lombok
1.18.4
org.apache.commons
commons-lang3
org.apache.maven.plugins
maven-compiler-plugin
3.2
1.8
1.8
UTF-8
创建pojo包
采用了lombok简化代码https://blog.csdn.net/GuiSu97/article/details/90697494中有提到使用
package com.fengstyle.im.pojo;
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 //get set方法
@AllArgsConstructor //全参构造
@NoArgsConstructor //空参构造
@Document(collection = "message") //标注在实体类上,类似于hibernate的entity注解,标明由mongo来维护该表
//@Builder //声明实体,表示可以进行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;
}
package com.fengstyle.im.pojo;
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;
}
构造用户数据
package com.fengstyle.im.pojo;
import java.util.HashMap;
import java.util.Map;
public class UserData {
public static final Map 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());
}
}
创建dao包,定义MessageDAO接口
package com.fengstyle.im.dao;
import com.fengstyle.im.pojo.Message;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.bson.types.ObjectId;
import java.util.List;
public interface MessageDAO {
/**
* 查询点对点聊天记录
*/
List findListByFromAndTo(Long fromId, Long toId, Integer page, Integer rows);
/**
* 根据id查询数据
*/
Message findMessageById(String id);
/**
* 更新消息状态
*/
UpdateResult updateMessageState(ObjectId id, Integer status);
/**
* 新增消息数据
* */
Message saveMessage(Message message);
/**
* * 根据消息id删除数据
* */
DeleteResult deleteMessage(String id);
}
编写实现类
package com.fengstyle.im.dao.impl;
import com.fengstyle.im.dao.MessageDAO;
import com.fengstyle.im.pojo.Message;
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;
@Override
public List findListByFromAndTo(Long fromId, Long toId, Integer page, Integer rows) {
Criteria fromList = Criteria.where("from.id").is(fromId).and("to.id").is(toId);
Criteria toList = Criteria.where("from.id").is(toId).and("to.id").is(fromId);
Criteria criteria = new Criteria().orOperator(fromList, toList);
PageRequest pageRequest = PageRequest.of(page - 1, rows, Sort.by(Sort.Direction.ASC,"send_date"));
Query query = new 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.setId(ObjectId.get());
message.setSendDate(new Date());
message.setStatus(1);
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);
}
}
编写配置文件application.properties
package com.fengstyle.im.dao;
import com.fengstyle.im.Application;
import com.fengstyle.im.pojo.Message;
import com.fengstyle.im.pojo.User;
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;
/**
* 测试类 dao单元测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
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("我叫张三,很高兴认识你")
.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 testQueryById(){
Message message =
this.messageDAO.findMessageById("5d146819b02705456c02cdda");
System.out.println(message);
}
@Test
public void testQueryList(){
List list = this.messageDAO.findListByFromAndTo(1001L, 1002L, 2,
1);
for (Message message : list) {
System.out.println(message);
}
}
}
执行testSave()
可以看到成功往远程MongoDB中插入了数据
执行 testQueryById() 控制台成功返回查询数据,说明代码可用
1.5.1发送消息流程
1.5.2接受消息流程
1.5.3具体实现如下
创建websocket包
package com.fengstyle.im.websocket;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fengstyle.im.dao.MessageDAO;
import com.fengstyle.im.pojo.Message;
import com.fengstyle.im.pojo.UserData;
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 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);
}
}
}
package com.fengstyle.im.websocket;
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 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) {
}
}
package com.fengstyle.im.websocket;
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;
/**
* webSocket配置类
*/
@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, "/websocket/{uid}")
.setAllowedOrigins("*")
.addInterceptors(this.messageHandshakeInterceptor);
}
}
在application.properties添加启动端口
这里测试使用的是谷歌的Simple WebSocket Client插件与websocket在线测试工具
ok,是没问题的
创建controller,service包
package com.fengstyle.im.controller;
import com.fengstyle.im.pojo.Message;
import com.fengstyle.im.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("message")
@CrossOrigin
public class MessageController {
@Autowired
private MessageService messageService;
/**
* 拉取消息列表
* *
@param fromId
* @param toId
* @param page
* @param rows
* @return
*/
@GetMapping
public List queryMessageList(@RequestParam("fromId") Long fromId, @RequestParam("toId") Long toId,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "rows", defaultValue = "10") Integer rows) {
return this.messageService.queryMessageList(fromId, toId, page, rows);
}
}
package com.fengstyle.im.service;
import com.fengstyle.im.dao.MessageDAO;
import com.fengstyle.im.pojo.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MessageService {
@Autowired
private MessageDAO messageDAO;
public List queryMessageList(Long fromId, Long toId, Integer page, Integer rows) {
List list = this.messageDAO.findListByFromAndTo(fromId, toId, page, rows);
for (Message message : list) {
//如过消息为未读
if(message.getStatus().intValue() == 1){
// 修改消息状态为已读
this.messageDAO.updateMessageState(message.getId(), 2);
}
}
return list;
}
}
测试:
。。。
。。。