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@Transactionalpublic 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@Transactionalpublic 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的方法接口 */@NoRepositoryBeanpublic 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",   successfunction(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,    successfunction(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;@Componentpublic 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@EnableWebSocketMessageBrokerpublic 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.测试








           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值