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.测试








  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值