欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于webSocket实现的点对点微信对战小程序

程序员文章站 2024-02-26 17:53:52
...

基于webSocket实现的点对点微信对战小程序

项目背景:

在小程序流行的年代,大多数年轻人在坐地铁坐公交或者其他空余时间都会找点小程序打发时间。快速的生活,人们总是在追寻着新鲜事物,创新能过促进多巴胺的分泌。微信小程序是一种适合快速开发,周期端,学习成本低,加载快,等优良特性,入场小白最近在websocket的时候结合初学的小程序知识写了一个点对点案例,供大家学习参考。入场小白也是初学者,有什么不好的地方,请大家多提出建议,写的仓促代码写的不好,请大家多多包容。

项目介绍:

点的点是一款简单的对战游戏,通过点击按钮的点击次数来决定胜负,扩展可定义游戏时长对战人数,分区域统计点击数。项目分为两部分

小程序部分:负责界面,首页选择游戏模式,选择游戏模式后进入进入匹配界面,匹配之后进入游戏界面,最后到结束界面,首页还有排行榜和个人,后面回详细介绍各页面。

服务后端部分:是springboot架构,包含一些websocket服务和一些机器人数据生成,与小程序端交互的数据,及数据的入库操作,后面也着重介绍。

先看一下运行效果:

基于webSocket实现的点对点微信对战小程序
小程序介绍:
App.js在这里会引用一个chat类 this.chat=newChat(this)this.chat = new Chat(this) ,然后调用this.chat.connectSocket() 类中connectSocket()方法连接服务端的webscoket。
Chat类代码:

/**
 * Chat
 * 
 */
import baseConfig from './baseConfig.js'
class Chat {
  constructor(app) {
    this.chat_id = null // chat_id
    this.connectStatus = 0 // websocket 连接状态 0:未连接,1:已连接
    this.heartListen = null // 心跳
    this.watcherList = [] // 订阅者
    this.app = app // 方便在Chat内部操作app
  }
  /* 初始化连接 */
  connectSocket() {
    const userinfo = this.app.globalData.userInfo;
    this.chat_id = new Date().getTime()
    const url = `wss://www.dianduidiantwo.xyz/wss`;
    console.log(url);
    console.log('开始连接')
    // websocket连接
    wx.connectSocket({
      url: url,
      header: {
        'content-type': 'application/json'
      },
      method: 'post',
      success: res => {
        console.log('连接成功', res)
        // 设置连接状态
        this.connectStatus = 1
        // 心跳
        clearInterval(this.heartListen)
        this.heartListen = setInterval(() => {
          if (this.connectStatus === 0) {
            console.log('监听到没心跳了,抢救一下')
            clearInterval(this.heartListen)
            this.reconnect()
          } else {
             console.log('我还活着')
          }
        }, 30000)
      },
      fail: err => {
        console.error('连接失败')
      }
    })
    // 监听webSocket错误
    wx.onSocketError(res => {
      console.log('监听到 WebSocket 打开错误,请检查!')
      // 修改连接状态
      this.connectStatus = 0
    })
    // 监听WebSocket关闭
    wx.onSocketClose(res => {
      console.log('监听到 WebSocket 已关闭!')
      this.connectStatus = 0
    })
    // websocket打开
    wx.onSocketOpen(res => {
      console.log('监听到 WebSocket 连接已打开!')
    })
    // 收到websocket消息
    wx.onSocketMessage(res => {
      this.getSocketMsg(JSON.parse(res.data))  // 收到的消息为字符串,需处理一下
    })

  }

  /* 重连 */
  reconnect() {
    console.log('尝试重连')
    wx.closeSocket() // 重连之前手动关闭一次
    this.connectSocket()
  }

  /* 关闭websocket */
  closeSocket(removeChat) {
    wx.closeSocket({
      success: res => {
        // code
      }
    })
  }

  /* 添加watcher */
  addWatcher(fn) {
    console.info("进来了");
    this.watcherList.push(fn)
    return this.watcherList.length - 1 // 返回添加位置的下标,Page unload的时候方便删除List成员
  }

  /* 删除watcher */
  delWatcher(index) {
    this.watcherList.splice(index, 1)
    // console.log('销毁watcher', this.watcherList)
  }

  /* 收到消息 */
  getSocketMsg(data) {
    data.code = 0;
    console.log('收到消息', data)
    if (data.code == 5100) { // 处理登录过期
      wxLogin()
        .then(res => {
          console.log('登录成功')
          // 重新登录成功,发起重连
          this.reconnect()
        })
        .catch(err => {
          console.error('登录失败', err)
        })
      // 正确状态
    } else if (data.code == 0) {
      // 给每个订阅者发消息
      const list = this.watcherList
      for (let i = 0; i < list.length; i++) {
        list[i](data)
      }
      // 其他返回类型
    } else {
      // balabalabala
    }
  }

  // 这里可以写一些方法,如发送消息等
  // code
  sendMessage(message){
    wx.sendSocketMessage({
      data: JSON.stringify(message)
    })
  }
}
export {Chat};

chat类的作用:与服务端websocket建立连接,并报错连接,整个小程序通过websocket发送消息接收都是在这里,需要用websocket的页面会调用addWatcher()发法添加watcher,delWatcher()删除watcher接收消息时会分发给订阅者,发送消息也是一样的原理。
Index.js代码:

//获取应用实例
const app = getApp()

Page({
  data: {
    background: ['btn1', 'btn2','btn3'],
    motto: 'Hello World',
    userInfo: {},
    hasUserInfo: false,
    canIUse: wx.canIUse('button.open-type.getUserInfo')
  },
  //事件处理函数
  bindViewTap: function() {
    wx.navigateTo({
      url: '../logs/logs'
    })
  },
  onLoad: function () {
    if (app.globalData.userInfo) {
      this.setData({
        userInfo: app.globalData.userInfo,
        hasUserInfo: true
      })
    } else if (this.data.canIUse){
      // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
      // 所以此处加入 callback 以防止这种情况
      app.userInfoReadyCallback = res => {
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    } else {
      // 在没有 open-type=getUserInfo 版本的兼容处理
      wx.getUserInfo({
        success: res => {
          app.globalData.userInfo = res.userInfo
          this.setData({
            userInfo: res.userInfo,
            hasUserInfo: true
          })
        }
      })
    }
  },
  getUserInfo: function(e) {
    console.log(e)
    app.globalData.userInfo = e.detail.userInfo
    this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
    })
  },

  start: function(e){
    var id = e.currentTarget.dataset.id
    console.log(id);

    if(id =="btn1"){
      this.solo(e);
    }else if(id == "btn2"){
      this.multiplayerGame(e);
    }else if(id == "btn3"){
      this.found(e);
    }
  },

//单人对战
solo: function(e){
  wx.redirectTo({
    url: '../matching/matching?name=' + e.currentTarget.dataset.name + "&tu="+e.currentTarget.dataset.avatarurl
  })
},
//多人对站
  multiplayerGame: function (e) {
    wx.switchTab({
      url: '../image'
    })
  },
//创建房间
  found: function (e) {
    console.info("sd")
    wx.switchTab({
      url: '../image'
    })
  },
})

Index.js 主要是一些不同按钮的跳转链接
后面一些页面的代码就不一一列举了,可以到github上下载的到
游戏的代码

Github地址: https://github.com/xy8023hsp/dianduidian

/**
   * 生命周期函数--监听页面加载
   */
  onLoad: function(options) {
    

    var data = JSON.parse(options.mdata)
    this.setData({
      "mdata": data
    })

    this.draw('runCanvas', 0, 60000);
    var that = this;
    const watcherIndex = app.$chat.addWatcher(that.dealFn)
    that.setData({
      watcherIndex
    })
    console.log('添加至 watcherList', app.$chat.watcherList)
    var webSocket = 'mdata.webSocketId';
    let reciprocalNum = that.data.reciprocalNum;
    that.setData({
    timerc: setInterval(function () {
      that.setData({
        [webSocket]: '1002-solo'
      });
      //每秒向服务器去实时数据
      app.sendMessage(that.data.mdata);
      if (reciprocalNum == 0) {
        console.info("执行定时器关闭操作");
        clearInterval(that.data.timerc);
        //游戏时间到,跳转到结束页
        var mdata = JSON.stringify(that.data.mdata);
        // console.info(mdata)
         wx.redirectTo({
           url: '../finish/finish?mdata=' + mdata
         })

      }

使用websocket每秒都与服务器交互数据。

后端websocket核心代码:

@OnMessage
public void onMessage(String message) throws IOException {


    JSONObject jsonTo = JSONObject.fromObject(message);


    String webSocketId = (String) jsonTo.get("webSocketId");

    switch (webSocketId) {
        case "1001-solo":
            Solo solo = webSocketService.solo(jsonTo);
            JSONObject jsonObject = JSONObject.fromObject(solo);
            sendMessageAll(jsonObject.toString());
            break;
        case "1002-solo":
            JSONObject jsonToSelect = webSocketService.soloSelect(jsonTo);
            sendMessageAll(jsonToSelect.toString());
            break;
        case "1003-solo":
            //比赛信息入库操作
            webSocketService.insertion(jsonTo);
            break;
        case "1001-ranking":
            //查询排行榜信息
            webSocketService.selectList(jsonTo);
            break;
        default:
            break;
    }
}

根据小程序端返回的不同编码,调用不同的业务方法,实现不同的逻辑处理

核心业务代码:

@Service
public class WebSocketServiceImpl implements WebSocketService{

    @Autowired
    private SoloMapper soloMapper;
    @Autowired
    private RobotMapper robotMapper;

    @Override
    public Solo solo(JSONObject jsonTo) {
        //单人对战状态

        //假设匹配人机
        //获取人机信息
        Robot robot = createMachine();
        String machineId = (String) jsonTo.get("machineId");
        robot.setMachineId(machineId);

        //构建自身信息
        Robot robotlf = new Robot();
        robotlf.setMachineId(Uuid.getUUID32());
        robotlf.setTuUrl((String)jsonTo.get("oneselfTu"));
        robotlf.setName((String)jsonTo.get("oneselfName"));

        //组合玩家信息
        List<Robot> robots = new ArrayList<>();
        robots.add(robot);
        robots.add(robotlf);


        //查找是否有空余,若没有则创建一个房间
        Solo room = isRoom();
        if (room == null){
            room = new Solo();
            room.setRobots(robots);
            room.setId(UUID.randomUUID().toString());
            room.setCreateTime(new Date());
            room.setReady("1");
            room.setFixture("60");
            room.setBaseNum(2);
        }

        return room;
    }

    @Override
    public JSONObject soloSelect(JSONObject jsonTo) {
        Integer integer = GrowthFactor();
//        List<Robot> robots = (List<Robot>) jsonTo.get("robots");
        List<Robot> robots = (List<Robot>)JSONArray.toCollection(jsonTo.getJSONArray("robots"),Robot.class);
//        List<Robot> studentList1 = JSON.pparseArray(JSON.parseObject(jsonTo).getString("robots"), Robot.class);
//        System.out.println(robots.toString());
        Robot robot = robots.get(0);
        robots.remove(0);
        robot.setScore(robot.getScore()+integer);
        robots.add(0,robot);
        jsonTo.put("robots",robots);
        return jsonTo;
    }

    @Override
    public void insertion(JSONObject jsonTo) {
        Solo   solo = (Solo) JSONObject.toBean(jsonTo, Solo.class);
        String uuid = Uuid.getUUID32();
        solo.setId(uuid);
        solo.setCreateTime(new Date());
        soloMapper.insertSelective(solo);
        List<Robot> robots = (List<Robot>)JSONArray.toCollection(jsonTo.getJSONArray("robots"),Robot.class);
        for (Robot robot : robots) {
            robot.setcreateTime(new Date());
            robot.setSoloId(uuid);
            robotMapper.insertSelective(robot);
        }

    }

    @Override
    public List<Robot> selectList(JSONObject jsonTo) {
        ArrayList<Robot> robots = robotMapper.selectScore();

        List<Robot> robotValues = robots.stream().collect(
            Collectors.collectingAndThen(Collectors.toCollection(
                () -> new TreeSet<>(Comparator.comparing(o -> o.getName()))), ArrayList::new)
        );

        Collections.sort(robotValues, new Comparator<Robot>() {
            @Override
            public int compare(Robot o1, Robot o2) {
                return o2.getScore().compareTo(o1.getScore());
            }
        });

        return robotValues;
    }

    /**
     * 随机设定增涨因子
     */
    public Integer GrowthFactor(){
        Random random = new Random();
        return  random.nextInt(6)+1;
    }

    //创建人机信息方法  Create a machine
    public Robot createMachine(){
        //构造人名
        String baseName = BaseName.getBaseName();
        //随机生成图片
        Random random = new Random();
        String tu = String.valueOf(random.nextInt(10)+1);
        Robot robot = new Robot();
        robot.setName(baseName);
        robot.setTuUrl("https://dian-1259675363.cos.ap-chengdu.myqcloud.com/image/touxiang/touxiang"+tu+".jpg");
        return robot;
    }

    public Solo isRoom(){
        //判断数据库房间表,是否空余房间,有则返回,没有则null
        return null;
    }
}

后端github地址:Github地址: https://github.com/xy8023hsp/dianduidian

相关标签: 小程序 java