微信小程序:uniapp+WebSocket实现订单提醒

背景

做的一个校园版的美团外卖项目,这里分享一下订单提醒功能,使用uniapp+WebSocket实现。

开始

一、导入pom

<!--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做备案。

你可能感兴趣的:(java,websocket)