springboot集成websocket

pom.xml

<!-- websocket -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-websocket</artifactId>
 </dependency>

WebSocketConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    /**
     * 	注入ServerEndpointExporter,
     * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

Application

解决spring bean无法注入问题,如:在websocket中使用redis

@Slf4j
@SpringBootApplication
@EnableScheduling
@EnableAspectJAutoProxy
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        ConfigurableApplicationContext ctx = app.run(args);
        
        /**解决spring bean无法注入问题*/
        WebSocket.setApplicationContext(ctx);
        /**解决spring bean无法注入问题*/
    }
}

WebSocket.java

结合 springboot集成redis,redis常用工具类及redis队列使用
如果不需要删除redis相关代码就行

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.linus.config.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
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.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Description: websocker服务类
 * @date: 2024/7/26
 */
@Slf4j
@Component
@ServerEndpoint("/websocket/{userId}") //此注解相当于设置访问URL
public class WebSocket {
    private Session session;
    private String userId;

    //线程安全的map,用来保存每个客户端对应的WebSocket对象
    private static ConcurrentHashMap<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();
    //缓存消息的redis key
    final String WEBSOCKET_CACHE_MESSAGE_REDIS_KEY = "WEBSOCKET_CACHE_MESSAGE_REDIS_KEY";
    
    /*************解决spring bean无法注入问题**************/
    private static ApplicationContext applicationContext;

    public static void setApplicationContext(ApplicationContext applicationContext) {
        WebSocket.applicationContext = applicationContext;
    }
    /*************解决spring bean无法注入问题**************/
    
    /**
     * 连接成功
     * @OnOpen注解:websocket 连接成功后,触发该注解修饰的方法
     * @param session
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId) {
        try {
            this.session = session;
            this.userId = userId;
            if (webSocketMap.containsKey(userId)) {
                webSocketMap.remove(userId);
                webSocketMap.put(userId, this);
            } else {
                webSocketMap.put(userId, this);
            }
            log.info("【websocket消息】有新的连接,总数为:" + webSocketMap.size());
            //取出消息列表
            RedisUtil redisUtil = applicationContext.getBean(RedisUtil.class);
            while (redisUtil.hasKey(WEBSOCKET_CACHE_MESSAGE_REDIS_KEY+":"+userId)) {
                String message = (String) redisUtil.lLeftPop(WEBSOCKET_CACHE_MESSAGE_REDIS_KEY + ":" + userId);
                if (StrUtil.isEmpty(message)) {
                    log.info("用户" + userId + "没有缓存的消息");
                } else {
                    //todo 发送消息-发送后清除缓存的消息
                    sendInfoByUserId(userId, message);
                    log.info("用户" + userId + "缓存的消息发送成功");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 接收消息
     * @OnMessage注解:客户端发送消息时,触发该注解声明的方法
     * @param message
     * @return
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("【websocket消息】收到客户端消息userId:" + userId + ",接收信息:" + message);
        if (webSocketMap.containsKey(userId)) {
            try {
                JSONObject obj = new JSONObject();
                obj.put("cmd","heart");
                obj.put("msgTxt", "心跳");
                sendInfoByUserId(this.userId,obj.toJSONString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 连接关闭
     * @OnClose注解:websocket断开连接后,触发该注解修饰的方法
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        try {
            if (webSocketMap.containsKey(userId)) {
                webSocketMap.remove(userId);
            }
            log.info("【websocket消息】连接断开,总数为:" + webSocketMap.size());
            // userStatusMessage();
        } catch (Exception e) {
        }
    }
    /**
     * 连接异常
     * @OnError注解:当建立的连接出现异常后,触发该注解修饰的方法
     * @param session
     * @param throwable
     */
    @OnError
    public void onError(Session session,Throwable throwable) {
        log.info("【websocket消息】连接异常:" + throwable.getMessage());
    }
    /**
     * 服务器给指定WebSocket客户端发送信息
     * @param userId
     * @param message
     */
    public void sendInfoByUserId(String userId, String message) {
        System.out.println("后端发送前端web数据userId:" + userId + "发送消息:" + message);
        try {
            if (webSocketMap.containsKey(userId)) {
                webSocketMap.get(userId).session.getBasicRemote().sendText(message);
            } else {
                log.info("用户" + userId + "没有连接");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 服务器给指定WebSocket客户端发送信息,如果未连接,缓存到redis
     * @param userId
     * @param message
     */
    public void sendInfoByUserId_cache(String userId, String message) {
        System.out.println("后端发送前端web数据userId:" + userId + "发送消息:" + message);
        try {
            if (webSocketMap.containsKey(userId)) {
                webSocketMap.get(userId).session.getBasicRemote().sendText(message);
            } else {
                log.info("用户" + userId + "没有连接");
                //把新消息缓存到redis列表
                RedisUtil redisUtil = applicationContext.getBean(RedisUtil.class);
                redisUtil.rSet(WEBSOCKET_CACHE_MESSAGE_REDIS_KEY+":"+userId,message);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 服务器给全部连接客户端发送信息
     * @param message
     */
    public void sendInfoAll(String message) {
        try {
            webSocketMap.values().forEach(ws -> ws.session.getAsyncRemote().sendText(message));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 此为单点消息(多人)
     * @param userIds
     * @param message
     */
    public void sendInfoByUserIds(String[] userIds, String message) {
        for (String userId : userIds) {
            sendInfoByUserId(userId, message);
        }
    }
    /**
     * 此为单点消息(多人),如果未连接缓存消息到redis
     * @param userIds
     * @param message
     */
    public void sendInfoByUserIds_cache(String[] userIds, String message) {
        for (String userId : userIds) {
            sendInfoByUserId_cache(userId, message);
        }
    }
    /**
     * 获取全部连接用户的id
     * @return
     */
    public Set<String> getAllUserIds() {
        return webSocketMap.keySet();
    }

}

socket.js

uniapp
class Socket {
    constructor(options) {
        this.socketUrl = 'ws://117.72.37.178:24908/websocket/';
        this.socketStart = false;
        this.heartbeatTimer = null;
        this.user = null;
        this.initSocketListeners();
    }
    initSocketListeners() {
        uni.onSocketOpen(() => {
            this.socketStart = true;
            console.log('WebSocket连接已打开!');
            this.startHeartbeat();
        });
        uni.onSocketMessage(res => {
            const data = JSON.parse(res.data);
            this.acceptMessage && this.acceptMessage(data);
        });
        uni.onSocketClose(() => {
            console.log('WebSocket 已关闭!');
            this.socketStart = false;
            this.reconnect();
        });
        uni.onSocketError(() => {
            console.log('WebSocket连接打开失败,请检查!');
            this.socketStart = false;
            this.reconnect();
        });
    }
    init(callback) {
		this.user = uni.getStorageSync('user')
        if (this.socketUrl && this.user.id) {
            if (!this.socketStart) {
                const url = `${this.socketUrl}${this.user.id}_app`;
                console.log("启动 socketUrl 连接地址:", url);
                uni.connectSocket({ url, method: 'GET' });
                callback && callback();
            } else {
                console.log('webSocket已经启动了');
            }
        } else {
            console.log('config/baseUrl socketUrl为空');
        }
    }
    send(data) {
        return new Promise((resolve, reject) => {
            uni.sendSocketMessage({
                data: JSON.stringify(data),
                success: resolve,
                fail: reject
            });
        });
    }
    startHeartbeat() {
        this.heartbeatTimer = setInterval(() => {
            this.send({ type: "心跳", userUid: this.user.id })
                .catch(() => {
                    console.log('心跳发送失败');
                    this.reconnect();
                });
        }, 10000);
    }
    stopHeartbeat() {
        clearInterval(this.heartbeatTimer);
        this.heartbeatTimer = null;
    }
    reconnect() {
        this.stopHeartbeat();
        setTimeout(() => {
            this.init();
        }, 5000);
    }
    closeSocket() {
        uni.closeSocket();
		this.user = null;
        this.socketStart = false;
        this.stopHeartbeat();
    }
}

const mySocket = new Socket();
export default mySocket;
vue
export const WebsocketMixin = {
  mounted() {
    this.initWebSocket();
  },
  destroyed: function () {
    // 离开页面生命周期函数
    this.websocketOnclose();
  },
  methods:{
    initWebSocket: function () {
      console.log("------------WebSocket连接成功");
      // WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
      var userId = store.getters.userInfo.id;
      if(!this.socketUrl.startsWith('/')){
        this.socketUrl = '/' + this.socketUrl
      }
      if(!this.socketUrl.endsWith('/')){
        this.socketUrl = this.socketUrl + '/'
      }
      let url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://") + this.socketUrl + userId;

      this.websock = new WebSocket(url);
      this.websock.onopen = this.websocketOnopen;
      this.websock.onerror = this.websocketOnerror;
      this.websock.onmessage = this.websocketOnmessage;
      this.websock.onclose = this.websocketOnclose;
    },
    websocketOnopen: function () {
      console.log("WebSocket连接成功");
    },
    websocketOnerror: function (e) {
      console.log("WebSocket连接发生错误");
      this.reconnect();
    },
    websocketOnclose: function (e) {
      this.reconnect();
    },
    websocketSend(text) {
      // 数据发送
      try {
        this.websock.send(text);
      } catch (err) {
        console.log("send failed (" + err.code + ")");
      }
    },
    reconnect() {
      var that = this;
      if(that.lockReconnect) return;
      that.lockReconnect = true;
      //没连接上会一直重连,设置延迟避免请求过多
      setTimeout(function () {
        console.info("尝试重连...");
        that.initWebSocket();
        that.lockReconnect = false;
      }, 5000);
    },
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值