背景
正在进行的微信小程业务开发,需要用到websocket.经过不断的调试,终于跑通一个简单的demo。
在阅读下文之前,请先进行环境配置。
nginx配置https及wss
有上文的基础为参考, 下面只是将uniapp代码和boot端代码进行展示。
uniapp
xml
<template>
<view style="text-align: center;">
<view style="border: 1px solid #67C23A; width: 718rpx; height: 400rpx;margin:12rpx auto;background-color: #fff;">
<view v-for="(item, index) in msgList" style="color: #909399; font-size: 14px;">
{{ item }}
</view>
</view>
<tui-input placeholder="请输入内容" v-model="myText"></tui-input>
<button @click="sendMessage()" style="background-color: #fff;">发送消息</button>
</view>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
computed: mapState(['hasLogin','userInfo']),
data() {
return {
msgList: [],
myText: "a",
wsClient: null
}
},
mounted() {
console.log("--mounted--"+ this.userInfo.id);
uni.connectSocket({
url: "wss://xxx.com:443/jeecg-activiti-gateway/ws/push/"+this.userInfo.id,
success: (data) => {
console.log("--connect--"+ JSON.stringify(data));
}
})
this.watchSocket();
},
onLoad() {
},
methods: {
sendMessage(){
console.log("--send--");
uni.sendSocketMessage({
data: this.myText
})
},
watchSocket() {
console.log("--watch--");
uni.onSocketMessage(res=>{
this.msgList.push(JSON.stringify(res))
console.log("res:"+JSON.stringify(res))
})
},
}
}
</script>
<style>
</style>
boot
- WebSocketPusher
java
package com.jeecg.activiti.gateway.modules.websocket;
import okhttp3.WebSocket;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.websocket.server.ServerEndpoint;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author mark
* @description 消息推送
* @date 2024-04-22
*/
@ServerEndpoint(value = "/ws/push/{userId}", encoders = WebSocketCustomEncoding.class)
@Component
public class WebSocketPusher {
private final static Logger logger = LogManager.getLogger(WebSocket.class);
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的
*/
private static AtomicInteger onlineCount = new AtomicInteger();
/**
* concurrent包的线程安全Map,用来存放每个客户端对应的MyWebSocket对象
*/
public static ConcurrentHashMap<String, WebSocketPusher> webSocketMap = new ConcurrentHashMap<>();
/***
* 功能描述:
* concurrent包的线程安全Map,用来存放每个客户端对应的MyWebSocket对象的参数体
*/
public static ConcurrentHashMap<String, PushParams> webSocketParamsMap = new ConcurrentHashMap<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 用户id
*/
private String userId;
/**
* 连接建立成功调用的方法
* onOpen 和 onClose 方法分别被@OnOpen和@OnClose 所注解。他们定义了当一个新用户连接和断开的时候所调用的方法。
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
//加入map
webSocketMap.put(userId, this);
addOnlineCount(); //在线数加1
logger.info("用户{}连接成功,当前在线人数为{}", userId, getOnlineCount());
try {
sendMessage(String.valueOf(this.session.getQueryString()));
} catch (IOException e) {
logger.error("IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
//从map中删除
webSocketMap.remove(userId);
subOnlineCount(); //在线数减1
logger.info("用户{}关闭连接!当前在线人数为{}", userId, getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* onMessage 方法被@OnMessage所注解。这个注解定义了当服务器接收到客户端发送的消息时所调用的方法。
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
logger.info("来自客户端用户:{} 消息:{}", userId, message);
//群发消息
for (String item : webSocketMap.keySet()) {
try {
webSocketMap.get(item).sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发生错误时调用
*
* @OnError
*/
@OnError
public void onError(Session session, Throwable error) {
logger.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
error.printStackTrace();
}
/**
* 向客户端发送消息
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
/**
* 向客户端发送消息
*/
public void sendMessage(Object message) throws IOException, EncodeException {
this.session.getBasicRemote().sendObject(message);
//this.session.getAsyncRemote().sendText(message);
}
/**
* 通过userId向客户端发送消息
*/
public void sendMessageByUserId(String userId, String message) throws IOException {
logger.info("服务端发送消息到{},消息:{}", userId, message);
if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
} else {
logger.error("用户{}不在线", userId);
}
}
/**
* 通过userId向客户端发送消息
*/
public void sendMessageByUserId(String userId, Object message) throws IOException, EncodeException {
logger.info("服务端发送消息到{},消息:{}", userId, message);
if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
} else {
logger.error("用户{}不在线", userId);
}
}
/**
* 通过userId更新缓存的参数
*/
public void changeParamsByUserId(String userId, PushParams pushParams) throws IOException, EncodeException {
logger.info("ws用户{}请求参数更新,参数:{}", userId, pushParams.toString());
webSocketParamsMap.put(userId, pushParams);
}
/**
* 群发自定义消息
*/
public static void sendInfo(String message) throws IOException {
for (String item : webSocketMap.keySet()) {
try {
webSocketMap.get(item).sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount.get();
}
public static synchronized void addOnlineCount() {
onlineCount.getAndIncrement();
}
public static synchronized void subOnlineCount() {
onlineCount.getAndDecrement();
}
}
- WebSocketCustomEncoding
less
import com.alibaba.fastjson.JSON;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
/**
* 在 websocket 中直接发送 obj 会有问题 - No encoder specified for object of class
* 需要对 obj 创建解码类,实现 websocket 中的 Encoder.Text<>
* */
public class WebSocketCustomEncoding implements Encoder.Text<Object> {
/**
* The Encoder interface defines how developers can provide a way to convert their
* custom objects into web socket messages. The Encoder interface contains
* subinterfaces that allow encoding algorithms to encode custom objects to:
* text, binary data, character stream and write to an output stream.
*
* Encoder 接口定义了如何提供一种方法将定制对象转换为 websocket 消息
* 可自定义对象编码为文本、二进制数据、字符流、写入输出流
* Text、TextStream、Binary、BinaryStream
* */
@Override
public void init(EndpointConfig endpointConfig) {
}
@Override
public void destroy() {
}
@Override
public String encode(Object o) throws EncodeException {
return JSON.toJSONString(o);
}
}
- PushParams
arduino
import lombok.Data;
/**
* 功能描述:
*
* @description: ws推送的参数结构
* @Date: 2022/12/1
*/
@Data
public class PushParams {
/**
* 功能描述:
* 类型
*/
private String type;
/**
* 功能描述:
* 开始时间
*/
private String startTime;
/**
* 功能描述:
* 结束时间
*/
private String stopTime;
}
演示效果
用两台手机亲测, 可以实现类似聊天室的效果。 done!