做的一个校园版的美团外卖项目,这里分享一下订单提醒功能,使用uniapp+WebSocket实现。
<!--socket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
package com.ningxun.restcard.controller;
import com.alibaba.fastjson.JSONObject;
import com.ningxun.restcard.auth.UserAuthenticationProvider;
import com.ningxun.restcard.entity.StudentUserEntity;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.apache.commons.lang.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;
//这个是客户端访问的接口
@ServerEndpoint("/imserver/app/{token}")
@Component
public class WebSocketServerApp {
//日志
static Logger log = LoggerFactory.getLogger(WebSocketServerApp.class);
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
*/
private static int onlineCount = 0;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
//新建一个ConcurrentHashMap webSocketMap 用于接收当前userId的WebSocket,方便IM之间对userId进行推送消息。
private static ConcurrentHashMap<String, WebSocketServerApp> webSocketMap = new ConcurrentHashMap<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
// /**接收userId*/
private String userId = "";
public static Session temp;
public static boolean flag = false;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("token") String token) {
//解析JWT
Claims claims = Jwts.parser()
.setSigningKey(UserAuthenticationProvider.STORE_PWD)
.parseClaimsJws(token).getBody();
StudentUserEntity user = JSONObject.parseObject(JSONObject.toJSON(claims.get("user")).toString(), StudentUserEntity.class);
if (claims != null) {
claims.setId(user.getId() + "");
//获取到session和userId
this.session = session;
this.userId = claims.getId();
this.temp = session;
flag = true;
//如果userId存在,则移除,再添加一个相同的userId和新的Session(防止session过期)
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
} else {
webSocketMap.put(userId, this);
//在线数加1
addOnlineCount();
}
log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
try {
sendMessage(this.userId + "连接成功");
} catch (IOException e) {
log.error("用户:" + userId + ",网络异常!!!!!!");
}
} else {
log.error("用户:" + userId + ",没有权限!!!!!!");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
//如果存在userId则移除,然后人数-1
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
//从set中删除
subOnlineCount();
flag = false;
}
log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
}
/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
error.printStackTrace();
}
// 收到客户端消息后调用的方法
@OnMessage
public void onMessage(String message, Session session) {
log.info(message);
}
/**
* 实现服务器主动推送
*/
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() {
WebSocketServerApp.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServerApp.onlineCount--;
}
public void setSession(Session session) {
this.session = session;
}
}
export default {
components: {
uniLoadMore
},
data() {
return {
timer: '', //心跳定时器
heartbeatFailNum: 0, //心跳失败次数
socketTask: null,
// 确保websocket是打开状态
is_open_socket: false
};
},
onLoad() {
this.connectSocketInit();
},
// 关闭websocket【必须在实例销毁之前关闭,否则会是underfined错误】
beforeDestroy() {
this.closeSocket();
clearInterval(this.timer);
},
methods: {
// 进入这个页面的时候创建websocket连接【整个页面随时使用】
connectSocketInit() {
// 创建一个this.socketTask对象【发送、接收、关闭socket都由这个对象操作】
this.socketTask = uni.connectSocket({
// 【非常重要】必须确保你的服务器是成功的
url: this.$student_api_url + "/imserver/app/" + uni.getStorageSync("token"),
success(data) {
console.log("websocket连接成功");
},
});
// 消息的发送和接收必须在正常连接打开中,才能发送或接收【否则会失败】
//连接成功时回调
this.socketTask.onOpen((res) => {
console.log("WebSocket连接正常打开中...!");
this.is_open_socket = true;
//收到信息的回调
this.socketTask.onMessage((res) => {
console.log("收到服务器内容:" + res.data);
var state = res.data;
//连接成功消息不处理,业务消息加提示铃声
if (state.indexOf("连") != -1) {} else {
const innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.autoplay = true;
innerAudioContext.src =
'http://ppt.1ppt.com/uploads/soft/2008/2-200R5152453.mp3';
innerAudioContext.onPlay(() => {
console.log('开始播放');
});
innerAudioContext.onError((res) => {
console.log(res.errMsg);
console.log(res.errCode);
});
}
//未接单状态且页面选中待接单页
。。。。。//这里业务代码,没看的必要
});
})
// 这里仅是事件监听【如果socket关闭了会执行】
this.socketTask.onClose(() => {
console.log("已经被关闭了")
})
//开启心跳机制
this.heartCheck();
},
// 关闭websocket【离开这个页面的时候执行关闭】
closeSocket() {
this.socketTask.close({
success(res) {
this.is_open_socket = false;
console.log("关闭成功", res)
},
fail(err) {
console.log("关闭失败", err)
}
})
},
//心跳检测30秒一次
heartCheck() {
clearInterval(this.timer);
var that=this;
this.timer = setInterval(function() {
//发送信息
uni.sendSocketMessage({
data: 'ping',
success: res => {
console.log("心跳发送成功");
that.heartbeatFailNum = 0;
},
fail: err => {
// 未连接关闭在打开
console.log("心跳发送失败");
that.heartbeatFailNum + 1;
if (that.heartbeatFailNum <= 10) {
that.closeSocket();
that.connectSocketInit();
} else {
console.log("连续十次心跳检测失败,不再发起重连请求")
}
}
});
}, 1000 * 30);
},
};
参考了一个博主的博文,回过头就找不到了,要不可以分享个链接。最后上测试服的时候遇到了WebSocket连接不上的问题。这属于微信小程序的问题,需要对IP做备案。