引入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", 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;@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