(声明:我这是给我自己看的, 代码实在是懒得整理了, 全都是复制出来的, 如果大家看不懂也别喷我.......我只是写代码不行,不代表喷人不行啊,,有问题的可以随时私信我或者加我qq952288306问我,)
org.springframework.boot spring-boot-starter-websocket
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 开启WebSocket支持
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter getServerEndpointExporter(){
return new ServerEndpointExporter();
}
}
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.concurrent.ConcurrentHashMap;
/**
* WebSocketServer
*
*/
@ServerEndpoint("/imserver/{userId}")
@Component
public class WebSocketServer {
static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
*/
private static int onlineCount = 0;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
private static ConcurrentHashMap webSocketMap = new ConcurrentHashMap<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 接收userId
*/
private String userId = "";
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
//加入set中
} else {
webSocketMap.put(userId, this);
//加入set中
addOnlineCount();
//在线数加1
}
log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
try {
sendMessage("连接成功");
} catch (IOException e) {
log.error("用户:" + userId + ",网络异常!!!!!!");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
//从set中删除
subOnlineCount();
}
log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用户消息:" + userId + ",报文:" + message);
//可以群发消息
//消息保存到数据库、redis
if (StringUtils.isNotBlank(message)) {
if ("ping".equals(message.toLowerCase())) {
try {
webSocketMap.get(this.userId).sendMessage("pong");
return;
} catch (IOException e) {
e.printStackTrace();
}
}
// try {
// //解析发送的报文
// JSONObject jsonObject = JSONObject.parseObject(message);
// //追加发送人(防止串改)
// jsonObject.put("fromUserId", this.userId);
// String toUserId = jsonObject.getString("toUserId");
// //传送给对应toUserId用户的websocket
// if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
// webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
// } else {
// log.error("请求的userId:" + toUserId + "不在该服务器上");
// //否则不在这个服务器上,发送到mysql或者redis
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
}
}
/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 发送自定义消息
*/
public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
log.info("发送消息到:" + userId + ",报文:" + message);
if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
} else {
log.error("用户" + userId + ",不在线!");
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
import com.example.didi.demo.config.WebSocketServer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
@RestController
public class WebSocketController {
@GetMapping("index")
public ResponseEntity index(){
return ResponseEntity.ok("请求成功");
}
@GetMapping("page")
public ModelAndView page(){
return new ModelAndView("socket");
}
@RequestMapping("/push/{toUserId}")
public ResponseEntity pushToWeb(String message, @PathVariable String toUserId) throws IOException {
WebSocketServer.sendInfo(message,toUserId);
return ResponseEntity.ok("MSG SEND SUCCESS");
}
uniapp端:
this.socketTask1 = uni.connectSocket({
// 【非常重要】必须确保你的服务器是成功的,如果是手机测试千万别使用ws://127.0.0.1:8080【特别容易犯的错误】
url: "ws://域名:端口号/imserver/" + userId",
success(data) {
console.log("websocket创建成功", data);
},
});
//消息的发送和接收必须在正常连接打开中,才能发送或接收【否则会失败】
this.socketTask1.onOpen((res) => {
console.log("socketTask1连接正常打开中...!", res);
this.is_open_socket = true;
// 注:只有连接正常打开中 ,才能正常成功发送消息
this.socketTask1.send({
data: 'socketTask1我再给你发送消息啦!',
success() {
console.log("socketTask1消息发送成功");
},
});
// 注:只有连接正常打开中 ,才能正常收到消息
this.socketTask1.onMessage((res) => {
console.log("socketTask1收到服务器内容:" + JSON.stringify(res));
});
})
// 这里仅是事件监听【如果socket关闭了会执行】
this.socketTask1.onClose((err) => {
console.log("socketTask1已经被关闭了", err) //在此进行重连
})
//先确保清除了之前的心跳定时器
clearInterval(this.pingpangTimes1)
//每过一段时间发送一次心跳,发送Ping,服务器会反馈pong,这样操作以保持socket一直是连接状态,防止断开连接,心跳停止
this.pingpangTimes1 = setInterval(() => {
this.socketTask1.send({
data: "ping",
success: () => {},
fail: () => {
}
});
}, 5000)
*************************************************************************************************************************************************************************************************************************************************************
第二套;后端使用了模版
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig2 implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").setAllowedOrigins("*");
registry.addEndpoint("/sockjs").setAllowedOrigins("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
ThreadPoolTaskScheduler te = new ThreadPoolTaskScheduler();
te.setPoolSize(1);
te.setThreadNamePrefix("wss-heartbeat-thread-");
te.initialize();
// 广播和独播 的路径 广播的话是stomp.subscribe("/queue/all/message",{}) 独播在下面
registry.enableSimpleBroker("/queue/all", "/topic/user").setHeartbeatValue(new long[]{15000, 15000}).setTaskScheduler(te);
// 独播应该是 /topic/user/{id}/message
registry.setUserDestinationPrefix("/topic/user");
// 如果给服务端发消息应该是 /send/toServer......
//registry.setApplicationDestinationPrefixes("/send");
}
}
package com.example.didi.demo.web;
import com.example.didi.demo.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* WebSocketController
*/
@RestController
public class WebSocket2Controller {
@Autowired
private SimpMessagingTemplate template;
@RequestMapping("pushToOne")
public void push(String id, Student entity) {
entity.setAge(12);
entity.setName("wbb");
template.convertAndSendToUser(id, "/message", entity);
}
@RequestMapping("pushToAll")
public void pushToAll(String id, Student entity) {
entity.setAge(12);
entity.setName("wbbb");
template.convertAndSend( "/queue/all/message", entity);
}
}
这里我们使用stomp.js 来实现 订阅和 广播, 因为uniapp的API实际上是不支持这个广播和订阅的, 除非使用第一套的代码, 鉴定userId 可以用原生API实现(不想整理代码了, 实在是太懒了)
//广播消息(一对多),同步拍卖发广播消息
subscribeAllMessage() {
let that = this
//通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息(广播接收信息)
that.stompClient.subscribe("/subscribe/all/message", function(response) {
console.log(response);
that.$u.toast(response.body);
});
},
subscribeUserMessage(clientId) {
//通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息(队列接收信息)
let that = this
that.stompClient.subscribe("subscribe/user/" + clientId + "/message", function(response) {
var message = response.body;
if (message) {
var result = JSON.parse(message);
that.$u.toast(result);
}
});
},
//连接ws
connectWS() {
//var socket = new SockJS("ws://域名:端口号/websocket");
//var socket = new SockJS("wss://域名:端口号/websocket");// APP 端使用会报错 Cannot read property 'prototype' of undefined
//this.stompClient = Stomp.over(socket);
//或者用下面的写法
this.stompClient = Stomp.client("wss://域名:端口号/websocket");
this.stompClient.heartbeat.outgoing = 5000;
this.stompClient.heartbeat.incoming = 5000;
//连接WebSocket服务端
let header={}
let that = this
this.stompClient.connect(header, function(frame) {
console.log("Stomp连接成功");
this.isSocketOpen = true;
uni.setStorageSync("isSocketOpen", true)
// 订阅消息
that.subscribeAllMessage();
//独播 这里写死 id是 10
that.subscribeUserMessage(10);
}, function(err) {
//自动重连接WebSocket服务端
uni.setStorageSync("isSocketOpen", false);
if (that.isSocketOpen == false) {
setTimeout(() => {
console.log("Stomp连接中断,正在重新连接……");
that.connectWS();
}, 2000);
}
});
},
提供一下 stomp.js的下载链接
开源中国
github https://github.com/jmesnil/stomp-websocket lib目录下