Spring -websocket实现简易在线聊天
Posted 咩咩文
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring -websocket实现简易在线聊天相关的知识,希望对你有一定的参考价值。
引入spring-websocket包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>$websocket.version</version>
</dependency>
</dependencies>
1.创建聊天记录信实体类MessageLog
package com.bjhy.ven.domain;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 聊天记录信息表
* @author xiaowen
*
*/
@Entity
@Table(name = "t_message")
public class MessageLog
//id
@Id
private String id;
//发送的消息
private String msg;
// 发送者
@ManyToOne
@JoinColumn(name = "fromId")
private SysUser from;
//接收者
@ManyToOne
@JoinColumn(name = "toId")
private SysUser to;
//发送时间
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private Date createDate;
/**
* @return the id
*/
public String getId()
return id;
/**
* @param id the id to set
*/
public void setId(String id)
this.id = id;
/**
* @return the msg
*/
public String getMsg()
return msg;
/**
* @param msg the msg to set
*/
public void setMsg(String msg)
this.msg = msg;
/**
* @return the from
*/
public SysUser getFrom()
return from;
/**
* @param from the from to set
*/
public void setFrom(SysUser from)
this.from = from;
/**
* @return the to
*/
public SysUser getTo()
return to;
/**
* @param to the to to set
*/
public void setTo(SysUser to)
this.to = to;
/**
* @return the createDate
*/
public Date getCreateDate()
return createDate;
/**
* @param createDate the createDate to set
*/
public void setCreateDate(Date createDate)
this.createDate = createDate;
2.创建消息服务接口MessageService
package com.bjhy.ven.service;
import com.bjhy.ven.biz.commons.service.BizCommonService;
import com.bjhy.ven.domain.MessageLog;
public interface MessageService extends BizCommonService<MessageLog, String>
/**
* 保存message
*/
public void saveAndSendMessage(String from,String to,String message);
BizCommonService是公共的业务处理接口
package com.bjhy.ven.biz.commons.service;
import java.io.Serializable;
import java.util.List;
import org.springframework.data.domain.Sort;
import com.bjhy.platform.commons.pager.Condition;
import com.bjhy.platform.commons.pager.PageBean;
/**
*
* 公用业务接口
* @author wbw
*
* @param <T> 实体对象
* @param <PK> 实体对象id
*/
public interface BizCommonService<T,PK extends Serializable>
/**
* 根据对象的ID查询对象信息
* @param id
* 对象ID
* @return
* 返回查询的对象
*/
public T findById(PK id);
/**
* 查询所有数据
*/
public List<T> findAll(Sort sort);
/**
* 根据条件查询数据
* @param conditions 条件对象
*/
public List<T> findByCondition(Condition... conditions);
/**
* 保存一个对象
* @param entity
* 保存的对象
*/
public PK save(T entity);
/**
* 修改一个对象
* @param entity
* 修改的对象
*/
public void update(T entity);
/**
* 保存或者修改一个对象
* @param entity
* 保存或者修改的对象
* @return
* 返回保存或者修改的对象的ID
*/
public PK saveOrUpdate(T entity);
/**
* 根据ID删除一个或者多个对象
* @param ids
* 删除的对象ID数组
* @throws Exception
*/
@SuppressWarnings("unchecked")
public void deleteById(PK... ids);
/**
* 分页查询
* @param pageBean 分页对象
*/
void pageQuery(PageBean pageBean);
3.实现消息服务接口MessageServiceImpl
package com.bjhy.ven.service.impl;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bjhy.platform.persist.dao.CommonRepository;
import com.bjhy.ven.biz.commons.service.impl.AbstractBizCommonService;
import com.bjhy.ven.dao.MesasgeRepository;
import com.bjhy.ven.domain.MessageLog;
import com.bjhy.ven.domain.SysUser;
import com.bjhy.ven.service.MessageService;
import com.bjhy.ven.service.SysUserService;
@Service
@Transactional
public class MessageServiceImpl extends AbstractBizCommonService<MessageLog, String> implements MessageService
// 基础查询ql
private static final String BASE_FIELD_QUERY = "select m.id,m.msg,m.createDate,fromer.userName,toer.userName from MessageLog m left join m.from fromer left join m.to toer where 1=1 ";
@Autowired
private MesasgeRepository mesasgeRepository;
@Autowired
private SysUserService userService;
@Autowired
private SimpMessagingTemplate template;
@Override
protected CommonRepository<MessageLog, String> getRepository()
return mesasgeRepository;
@Override
protected String getPageQl()
return BASE_FIELD_QUERY;
@Override
public void saveAndSendMessage(String from, String to, String message)
String convertMessage ="from:'"+from+"',to:'"+to+"',message:'"+message+"'";
template.convertAndSend("/topic/"+to,convertMessage);
//判断非空
SysUser userFrom = userService.findByUserName(from);
SysUser userTo = userService.findByUserName(from);
MessageLog msg = new MessageLog();
msg.setFrom(userFrom);
msg.setTo(userTo);
msg.setMsg(message);
msg.setCreateDate(new Date());
this.save(msg);
AbstractBizCommonService抽象的公共业务实现接口
package com.bjhy.ven.biz.commons.service.impl;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import com.bjhy.platform.commons.exception.PlatformException;
import com.bjhy.platform.commons.pager.Condition;
import com.bjhy.platform.commons.pager.Order;
import com.bjhy.platform.commons.pager.PageBean;
import com.bjhy.platform.persist.dao.CommonRepository;
import com.bjhy.platform.util.BeanUtils;
import com.bjhy.ven.biz.commons.service.BizCommonService;
/**
* 公用业务接口抽象实现类
* @author wbw
*/
@Component
@Transactional
public abstract class AbstractBizCommonService<T,PK extends Serializable> implements BizCommonService<T, PK>
@SuppressWarnings("unchecked")
protected Class<T> entityClass = (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
/**
* 返回实现repository接口的实例对象
*/
protected abstract CommonRepository<T, PK> getRepository();
/**
* 返回分页ql语句
*/
protected abstract String getPageQl();
@Override
public T findById(PK id)
return getRepository().findOne(id);
@Override
public List<T> findAll(Sort sort)
return getRepository().findAll(sort);
@SuppressWarnings("unchecked")
@Override
public List<T> findByCondition(Condition... conditions)
return (List<T>)getRepository().doList(getPageQl(), Arrays.asList(conditions), new ArrayList<Order>(), false);
@SuppressWarnings("unchecked")
@Override
public PK save(T entity)
PK id = null;
try
//判断是否有创建时间字段
try
Field createDateField = entity.getClass().getDeclaredField("createDate");
if(createDateField != null)
createDateField.setAccessible(true);
createDateField.set(entity, new Date());
catch (NoSuchFieldException e)
getRepository().store(entity);
Field field = entity.getClass().getDeclaredField("id");
field.setAccessible(true);
id = (PK)field.get(entity);
catch (Exception e)
e.printStackTrace();
throw new PlatformException(e.getMessage());
return id;
@SuppressWarnings("unchecked")
@Override
public void update(T entity)
try
Field field = entity.getClass().getDeclaredField("id");
field.setAccessible(true);
PK id = (PK)field.get(entity);
T sourceEntity = getRepository().findOne(id);
BeanUtils.copyNotNullProperties(entity, sourceEntity);
getRepository().update(sourceEntity);
catch (Exception e)
e.printStackTrace();
throw new RuntimeException(e);
@SuppressWarnings("unchecked")
@Override
public PK saveOrUpdate(T entity)
try
Field field = entity.getClass().getDeclaredField("id");
field.setAccessible(true);
PK id = (PK)field.get(entity);
if(StringUtils.isEmpty(id))
return save(entity);
else
update(entity);
return id;
catch (Exception e)
e.printStackTrace();
throw new PlatformException(e.getMessage());
@SuppressWarnings("unchecked")
@Override
public void deleteById(PK... ids)
for (PK pk : ids)
getRepository().delete(pk);
@Override
public void pageQuery(PageBean pageBean)
getRepository().doPager(pageBean, getPageQl());
4.创建消息dao,MesasgeRepository 这里的orm框架使用的是Spring Data JPA
package com.bjhy.ven.dao;
import com.bjhy.platform.persist.dao.CommonRepository;
import com.bjhy.ven.domain.MessageLog;
public interface MesasgeRepository extends CommonRepository<MessageLog, String>
CommonRepository是自定义的repository方法接口
package com.bjhy.platform.persist.dao;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import com.bjhy.platform.commons.pager.Condition;
import com.bjhy.platform.commons.pager.Order;
import com.bjhy.platform.commons.pager.PageBean;
/**
*
* 自定义repository的方法接口
*/
@NoRepositoryBean
public interface CommonRepository<T, ID extends Serializable> extends JpaRepository<T, ID>
/**
* 保存对象<br/>
* 注意:如果对象id是字符串,并且没有赋值,该方法将自动设置为uuid值
* @param item
* 持久对象,或者对象集合
* @throws Exception
*/
public void store(Object... item);
/**
* 更新对象数据
*
* @param item
* 持久对象,或者对象集合
* @throws Exception
*/
public void update(Object... item);
/**
* 执行ql语句
* @param qlString 基于jpa标准的ql语句
* @param values ql中的?参数值,单个参数值或者多个参数值
* @return 返回执行后受影响的数据个数
*/
public int executeUpdate(String qlString, Object... values);
/**
* 执行ql语句
* @param qlString 基于jpa标准的ql语句
* @param params key表示ql中参数变量名,value表示该参数变量值
* @return 返回执行后受影响的数据个数
*/
public int executeUpdate(String qlString, Map<String, Object> params);
/**
* 执行ql语句,可以是更新或者删除操作
* @param qlString 基于jpa标准的ql语句
* @param values ql中的?参数值
* @return 返回执行后受影响的数据个数
* @throws Exception
*/
public int executeUpdate(String qlString, List<Object> values);
/**
* 结合提供的分页信息,获取指定条件下的数据对象
* @param pageBean 分页信息
* @param qlString 基于jpa标准的ql语句
*/
public void doPager(PageBean pageBean, String qlString);
/**
* 结合提供的分页信息,获取指定条件下的数据对象
* @param pageBean 分页信息
* @param qlString 基于jpa标准的ql语句
* @param cacheable 是否启用缓存查询
*/
public void doPager(PageBean pageBean,String qlString,boolean cacheable);
/**
* 结合提供的分页信息,获取指定条件下的数据对象
* @param pageBean 分页信息
* @param qlString 基于jpa标准的ql语句
* @param params key表示ql中参数变量名,value表示该参数变量值
*/
public void doPager(PageBean pageBean, String qlString,
Map<String, Object> params);
/**
* 结合提供的分页信息,获取指定条件下的数据对象
* @param pageBean 分页信息
* @param qlString 基于jpa标准的ql语句
* @param values ql中的?参数值
*/
public void doPager(PageBean pageBean, String qlString, List<Object> values);
/**
* 结合提供的分页信息,获取指定条件下的数据对象
* @param pageBean 分页信息
* @param qlString 基于jpa标准的ql语句
* @param values ql中的?参数值
*/
public void doPager(PageBean pageBean, String qlString, Object... values);
/**
* 批量删除数据对象
* @param entityClass
* @param primaryKeyValues
* @return
*/
public int batchDeleteByQl(Class<?> entityClass,Object...primaryKeyValues);
/**
* 批量删除数据对象
* @param entityClass
* @param pKeyVals 主键是字符串形式的
* @return
* @throws Exception
*/
public int batchDeleteByQl(Class<?> entityClass,String...pKeyVals);
/**
* @param qlString 查询hql语句
* @param values hql参数值
* @param conditions 查询条件
* @param orders 排序条件
* @return
*/
public List<?> doList(String qlString, List<Object> values, List<Condition> conditions, List<Order> orders, boolean sqlable);
public List<?> doList(String qlString, List<Condition> conditions, List<Order> orders, boolean sqlable);
此处省略该接口的实现类CommonRepositoryImpl
5.消息服务前端控制器MessageController
package com.bjhy.ven.controller.messager;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.bjhy.ven.domain.SysUser;
import com.bjhy.ven.service.MessageService;
/**
* webSocket 发送接收消息controller
* @author xiaowen
*
*/
import com.bjhy.ven.service.SysUserService;
@Controller
@RequestMapping("/message")
public class MessageController
@Autowired
private MessageService messageService;
@Autowired
private SysUserService sysUserService;
private static final String MESSAGE_INDEX="message/message";
private static final String STAUTS="ok";
/**
* 请求首页
* @return
*/
@RequestMapping("/index")
public String index()
return MESSAGE_INDEX;
/**
* 发送消息
* @param from 发送消息者
* @param to 接受消息者
* @param message 消息
* @return
*/
@RequestMapping("/sendMessage")
public @ResponseBody String recieveMessage(String from,String to,String message)
messageService.saveAndSendMessage(from, to, message);
return STAUTS;
/**
* 获取在线用户
* @return
*/
@RequestMapping("/onlineUsers")
public @ResponseBody List<SysUser> onlineUsers(HttpSession session)
String sessionUserName = (String) session.getAttribute("username");
return sysUserService.findUserByUserName(sessionUserName);
6.创建聊天页面message.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>聊天</title>
$static_context$appJs$jquery$toast$bootstrap$socketJS
<script type="text/javascript" src="$request.contextPath/js/message/message.js"></script>
</head>
<body>
<input type="hidden" value="$username" id="username" />
<input type="hidden" id="to"/>
<div style="margin-bottom:20px"></div>
<div class="container">
<div class="row">
<!-- 在线用户列表 -->
<div class="col-md-3">
<div class="list-group">
<ul class="list-group" id="online">
<li class="list-group-item active">在线用户</li>
</ul>
</div>
</div>
<!-- 聊天窗体-->
<div class="col-md-9" id="chatwindows">
</div>
</div>
<!-- 发送消息文本 -->
<div class="row" style="display:none" id="send_text">
<div class="col-md-2"></div>
<div class="col-md-1"></div>
<div class="col-md-9" >
<textarea id="messgeContext" class="form-control" rows="3" placeholder="按下回车键发送消息...."></textarea>
</div>
</div>
<!-- 发送按钮 -->
<div class="row" style="display:none" id="execute_send">
<div class="col-md-2"></div>
<div class="col-md-4">
</div>
<div class="col-md-5" style="margin-top:5px;">
<button type="button" class="btn btn-danger btn_lg" id="btn_close" >关闭</button>
<button type="button" class="btn btn-danger btn_lg" id="btn_send" >发送</button>
</div>
</div>
</div>
</body>
</html>
特别注意此处需要用到socket.js、stomp.js
页面的呈现效果
6.创建message.js处理消息动作
var messageArr = ;
$(function()
mseeage.init();
bindEvent.init();
);
var bindEvent =
//初始化
init :function()
this.sendEvent();
,
//渲染视图
viewEvent: function()
$(".list-group-item").not(":eq(0)").click(function()
var name = $(this).attr("value");
$(".panel-primary").hide();
$("#"+name).show();
$("#to").val(name);
messageArr[name] = null;
$(".list-group-item[value='" + name + "']").find("span").html("");
$("#send_text").show();
$("#execute_send").show();
);
,
//发送消息
sendEvent : function()
//发送
$('#btn_send').click(function()
mseeage.snedMessage();
);
//关闭
$('#btn_close').click(function()
$(".panel-primary").remove();
$("#send_text").hide();
$("#execute_send").hide();
);
//enter事件
$(window).keydown(function(event)
if(event.keyCode ==13)
mseeage.snedMessage();
);
var mseeage =
init : function()
this.initOnlineUsers();
this.initSocket();
,
//初始化在线用户
initOnlineUsers : function()
$.ajax(
url:contextPath + "/message/onlineUsers",
success : function(data)
var $html =""
var $content = "";
for (var i = 0; i < data.length; i++)
var userName = data[i].userName;
var alias = data[i].alias;
$html +=" <li class='list-group-item' value='"+alias+"'>"+userName+"<span class='badge'></span></li>"
$content +="<div class='panel panel-primary' style='height:400px;display:none' id='"+alias+"'>" +
" <div class='panel-heading'>聊天栏--<span>"+userName+"</span></div>" +
"<div id='tolk' class='panel-body'></div></div>";
$('#chatwindows').append($content);
$("#online").not("li:eq(0)").append($html);
//绑定点击事件
bindEvent.viewEvent();
)
,
//初始化
initSocket : function()
var userName = $("#username").val();
var socket = new SockJS(contextPath + "/" + wsEndPoint);
var stompClient = Stomp.over(socket);
stompClient.connect(, function(frame)
stompClient.subscribe('/topic/'+userName, function(greeting)
var result=eval("("+greeting.body+")");
console.log(result);
var msg_="<br>"+result.from+"说:"+result.message;
$("#"+result.from).find("div:eq(1)").append(msg_);
if($('#to').val() != result.from)
if(messageArr[result.from])
messageArr[result.from]++;
else
messageArr[result.from] = 1;
$(".list-group-item[value='" + result.from + "']").find("span").html(messageArr[result.from]);
);
);
,
//发送消息
snedMessage : function()
var messge = $("#messgeContext").val();
var from = $("#username").val();
var to = $("#to").val();
if(messge.trim().length>0 && $("#to").val())
var data =
from : from,
to :to,
message : messge
$.ajax(
url:contextPath + "/message/sendMessage",
data: data,
success : function(data)
$("#messgeContext").val("");
)
else
toastr.warning("请输入消息!");
;
关于websocket配置
1.注册socket.js、stomp.js资源,实现ServletContextAware接口,在页面上通过$socketJs就可以引用js
package com.bjhy.platform.websocket.client;
import javax.servlet.ServletContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ServletContextAware;
@Component
public class RegistrySocketJS implements ServletContextAware
public final static String GLOBAL_ENDPOINT = "platform-ws";
private final static String SOCKET_JS = "socketJS";
@Override
public void setServletContext(ServletContext servletContext)
String contextPath = servletContext.getContextPath();
servletContext.setAttribute(SOCKET_JS,buildSocketJSResource(contextPath));
private String buildSocketJSResource(String contextPath)
String socketGloablJS = "<script type=\\"text/javascript\\" >var wsEndPoint = '" + GLOBAL_ENDPOINT + "';</script>";
String socketJS = "<script type=\\"text/javascript\\" src=\\"" + contextPath + "/websocket/js/sockjs.min.js\\"></script>";
String stompJS = "<script type=\\"text/javascript\\" src=\\"" + contextPath + "/websocket/js/stomp.min.js\\"></script>";
return socketGloablJS + socketJS + stompJS;
2.配置websocket消息中间件
package com.bjhy.platform.websocket.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
@Override
public void registerStompEndpoints(StompEndpointRegistry registry)
registry.addEndpoint("/platform-ws").withSockJS();
@Override
public void configureMessageBroker(MessageBrokerRegistry registry)
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic", "/quene");
这个可以单独抽离成一个子模块paltform-websocket如下图:
7.测试
以上是关于Spring -websocket实现简易在线聊天的主要内容,如果未能解决你的问题,请参考以下文章
Spring全家桶笔记:Spring+Spring Boot+Spring Cloud+Spring MVC
学习笔记——Spring简介;Spring搭建步骤;Spring的特性;Spring中getBean三种方式;Spring中的标签
Spring框架--Spring事务管理和Spring事务传播行为