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事务传播行为

Spring框架--Spring事务管理和Spring事务传播行为

Spring框架--Spring JDBC

Spring DAO vs Spring ORM vs Spring JDBC