webSocket

简介

**WebSocket是一种在单个TCP连接上进行全双工通信的协议。**WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

数据库(参考)

# 消息主体
CREATE TABLE `v_message` (
  `message_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `title` varchar(300) DEFAULT NULL COMMENT '标题',
  `content` varchar(3000) DEFAULT NULL COMMENT '内容',
  `msg_type` tinyint(3) DEFAULT NULL COMMENT '消息类型(1公告、2通知、3私信)',
  `sender_id` bigint(20) DEFAULT NULL COMMENT '发送人ID',
  `notice_type` tinyint(3) DEFAULT NULL COMMENT '通知类型(发票、应收款、入库、其它...)',
  `create_time` datetime DEFAULT NULL,
  `deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除',
  `receiver_type` tinyint(3) DEFAULT NULL COMMENT '接收者类别(1个人/2全部/角色/部门/分组)',
  `group_id` bigint(20) DEFAULT NULL COMMENT '接收者组别ID(由 v_receiver_group 表维护)',
  PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='消息表';

# 接收消息
CREATE TABLE `v_msg_receive` (
  `msg_receive_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `message_id` bigint(20) DEFAULT NULL COMMENT '消息ID(由 v_message 表维护)',
  `receiver_id` bigint(20) DEFAULT NULL COMMENT '接收者ID',
  `state` tinyint(1) DEFAULT NULL COMMENT '状态(0未读/1已读)',
  `read_time` datetime DEFAULT NULL COMMENT '阅读时间',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`msg_receive_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='消息接收表';

Vue

	webSocketLink(){
          let _this = this;
          // 心跳检测
          var heatCheck = {
              timeout: 10000, // 心跳间隔时间
              num: 3,  //心跳均未响次数,超过应重连
              timeoutObj: null,
              serverTimeoutObj: null,
              reset: function(){
                  clearInterval(this.timeoutObj);
                  clearTimeout(this.serverTimeoutObj);
                  return this;
              },
              start: function(){
                  var self = this;
                  this.timeoutObj = setTimeout(function(){
                      _this.websocket.send("ping");
                      //console.log("ping");
                      self.serverTimeoutObj = setTimeout(function(){
                          console.log("心跳停止,断开websocket----");
                          _this.websocket.close(); 
                      }, 15000);
                  }, self.timeout);
              }
          }
          if ('WebSocket' in window) {
              _this.updateUrl("websocketServer");
              _this.websocket = new WebSocket(_this.wsUrl);
          }else {
              alert("此浏览器不支持消息通知功能,请更换或升级浏览器!!!");
          }
           /* else if ('MazWebSocket' in window) {
              _this.updateUrl("webSocketServer");
              _this.websocket = new MazWebSocket(_this.wsUrl);
          } else {
              _this.updateUrl("sockjs/webSocketServer");
              _this.websocket = new SockjS(_this.wsUrl);
          } */
          _this.websocket.onopen = function(){
              console.log("websocket连接成功");
              heatCheck.reset().start(); // 开启心跳检测
          }
          _this.websocket.onmessage = function (event) { // 接收后台发来的消息
              if (event.data === 'pong'){
                  heatCheck.reset().start();
                  console.log(event.data);
              }else{
                  console.log("收到websocket服务端消息:" + event.data);
                  _this.$notify.info({
                    title: '你有一则新消息',
                    message: event.data,
                    duration: 0,
                    offset: 25
                  });
                  _this.refreshData();
              }
              
          }
          _this.websocket.onerror = function(){
              console.log("websocket连接错误")
          }
          _this.websocket.onclose = function(){
              console.log("websocket尝试重连.....");
              _this.webSocketLink();
          }
      },

	// 请求路径
	updateUrl(urlPath){
          let _this = this;
          let BASE_URL = document.location.host + '/api/' + urlPath +  '/' + this.navData.username+  '/' + this.navData.userId;
          if (urlPath.indexOf('sockjs') != -1) {
              _this.wsUrl = 'http://' + BASE_URL;
          } else {
              if (window.location.protocol == 'http:') {
                  _this.wsUrl = 'ws://' + BASE_URL;
              } else {
                  _this.wsUrl = 'wss://' + BASE_URL;
              }
          }
      },

Java

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
@ServerEndpoint(value = "/websocketServer/{username}/{userId}")
public class Websocket {

    private static int onlineCount = 0;     // 当前连接数
    private static Map<Long, Websocket> clients = new ConcurrentHashMap<>();  // 已连接客户端
    private Session session;
    private Long userId;
    private String username;

    /**
     * 连接时执行
     * @param
     * @param username
     * @param session
     * @throws IOException
     */
    @OnOpen
    public void onOpen( @PathParam("username") String username,@PathParam("userId") Long userId, Session session) {
        this.userId = userId;
        this.username = username;
        this.session = session;

        addOnlineCount();
        clients.put(userId, this);
        System.out.println("新增连接用户:"+username);
        System.out.print("当前连接用户ID:");
        clients.forEach((Long key, Websocket websocket)-> System.out.print(key.toString()+'、'));
    }

    /**
     * 收到消息时执行
     * @param message
     * @throws IOException
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        if ("ping".equals(message)){
            session.getAsyncRemote().sendText("pong");
        } else {
            System.out.println(message);
        }
        //sendMessageAll(message);    // 新通知
        // 先解析收到的json数据
        // 根据类型决定发送对象
    }

    /**
     * 连接错误时执行
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }


    /**
     * 发送消息给指定用户
     * @param message
     * @param userIds
     * @throws IOException
     */
    public void sendMessageTo(String message, List<Long> userIds) throws IOException {
        // session.getBasicRemote().sendText(message);
        userIds.forEach(id ->{
            if(clients.get(id)!=null){
                clients.get(id).session.getAsyncRemote().sendText(message);
            }
        });
        /*
                for (Websocket item : clients.values()) {
                    if (item.username.equals(To)) {
                        item.session.getAsyncRemote().sendText(message);
                    }
                }
        */
    }

    /**
     * 发送消息给全部用户
     * @param message
     */
    public void sendMessageAll(String message) {
        for (Websocket item : clients.values()) {
            item.session.getAsyncRemote().sendText(message);
        }
    }

    /**
     * 关闭时执行
     * @throws IOException
     */
    @OnClose
    public void onClose() throws IOException {
        clients.remove(username);
        subOnlineCount();
    }

    public static synchronized int getOnlineCount(){
        return onlineCount;
    }

    public static  synchronized void addOnlineCount() {
        Websocket.onlineCount++;
        log.info("新增一条连接,当前存在连接数:"+onlineCount);
    }

    public static synchronized void subOnlineCount() {
        Websocket .onlineCount--;
        log.info("减少一条连接,当前存在连接数:"+onlineCount);
    }

    public static synchronized Map<Long, Websocket> getClients() {
        return clients;
    }
}

切面

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 发送消息通知的切面
 */
@Slf4j
@Aspect
@Component
public class MessageAspect {

    @Autowired
    private Websocket websocket;

    @Pointcut("execution(* com.threepiles.lantern.finance.service.impl.InvoiceServiceImpl.submitInvoice(..))")
    public void pointcutInvoice() {}

    // 发票审核后置通知
    @AfterReturning("pointcutInvoice()")
    public void doAfter(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        InvoiceAudit  invoiceAudit = null;
        if (args!=null){
            invoiceAudit = (InvoiceAudit)args[0];	// 切点方法的入参
        } else {
            return;
        }
       Message message = new Message();
       ......
       ......
       ......
        // websocket通知用户
        try {
            if (userIds!=null){
                websocket.sendMessageTo(message.getTitle(), userIds);
            }
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值