头脑王者——小程序核心功能开发

头脑王者答题对战源码分析教程

  接到业务需求:用微信小程序开发一个答题对战类的游戏,借此机会呢把小程序好好研究一下(小程序出来很长时间了现在才看)。前段时间超级火爆的“头脑王者”就是我最好的研究对象。在研究阶段呢,没有配置前端、设计。所以样式基本是仿照的。废话不多说,进入正题。

  我们实现最核心的玩法就行:单人答题、双人pk答题、排行榜(好友/世界)。

  知识储备:涉及到实时通讯,在nodejsworkman之间,选择了workman需要过一遍微信小程序官方文档。如果有vue基础那么上手就更快了。最重要的一点:ES6语法要熟悉。

  一、用户信息

    在小程序内,因为多个页面都需要用户信息,所以用户信息的获取放在app.js里,做一个本地存储。小程序在启动后是有两个不同步的线程:view thread appservice thread。为了解决线程不同步造成用户信息获取出现的问题,用promise+callback进行了改造。获取用户openid的过程我们放在了第一步。获取用户信息:要判断是否有缓存、是否有授权等情况。

    

 1  const promise = new Promise(function (resolve, reject) {
 2       var openid = wx.getStorageSync('openid');
 3       if (openid == "" || openid == null) {
 4         wx.login({
 5           success: res => {
 6             wx.request({
 7               url: 'https://fotonpickup.risingad.com/wxapi2.php',
 8               data: { 'code': res.code },
 9               success(res) {
10                 wx.setStorage({
11                   key: "openid",
12                   data: res.data.openid
13                 });
14                 resolve(res.data.openid);
15               },
16               fail(res) {
17                 reject(res);
18               }
19             });
20           }
21         })
22       } else {
23         resolve(openid);
24       }
25     });
26     promise.then((openid) => {
27       return new Promise((resolve, reject) => {
28         //先从本地取用户信息
29         var wxinfo = wx.getStorageSync('wxinfo');
30         if (wxinfo) {
31           resolve(wxinfo);
32         } else {
33           wx.getSetting({
34             success: res => {
35               if (res.authSetting['scope.userInfo']) {
36                 // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
37                 wx.getUserInfo({
38                   success: res => {
39                     resolve(res.userInfo);
40                   }
41                 })
42               } else {
43                 //没有授权的情况,不知道正式版本是什么样子的。
44                 wx.getUserInfo({
45                   success: res => {
46                     resolve(res.userInfo);
47                   },fail:res=>{
48                     //resolve(false);
49                     //没有授权的预览版本,用open-type获取,并跳转统一页面,其他页面默认为有用户授权的状态
50                     wx.navigateTo({
51                       url: '/pages/scope/scope'
52                     });
53                   }
54                 })
55               }
56             }
57           })
58         }
59       }).then((wxinfo) => {
60         if (wxinfo) {
61           //将用户信息上传服务器保存一份
62           this.uploadUserInfo(wxinfo);
63           //本地存储用户cookie
64           wx.setStorage({
65             key: "wxinfo",
66             data: wxinfo
67           });
68         }
69         this.globalData.userInfo=wxinfo;
70         if(this.userInfoReadyCallback){
71           this.userInfoReadyCallback(wxinfo);
72         }
73       })
74     })
75   },
View Code

 

  注意看,第74行。此处注入了一个回调函数。是为了解决view thread 和app thread不同步的时候出现的问题。我们看一下怎么使用app中获取的用户信息

 1   onLoad: function () {
 2     wx.showLoading({
 3       title: '加载中',
 4     });
 5     if (app.globalData.userInfo) {
 6       this.setData({
 7         userInfo: app.globalData.userInfo
 8       });
 9       wx.hideLoading();
10 
11     } else {
12       app.userInfoReadyCallback = res => {
13         if (res) {
14           this.setData({
15             userInfo: res
16           });
17           wx.hideLoading();
18         }
19       };
20     }
21 
22   }
View Code

  注意:本地调试的,小程序不再提供弹框的方式授权。需要使用button组件,让用户点击弹出授权。我在获取用户信息的时候,如果没有授权,会跳转到一个专门的授权页面。

 

 

   1 <button wx:if="{{!scopeUserinfo}}" open-type="getUserInfo"bindgetuserinfo="getUserInfo">测试版:请同意授权 2 </button> 

 

 二、workman实时通讯

  这个workman可以查看官方文档,配置起来很简单。不过需要注意的一点就是:小程序只支持wss的协议,必须是带证书的https的请求。我们需要改一下workman中的代码。进入start_gateway.php这个文件,配置下证书参数。transport改为ssl

  

 1 // gateway 进程,这里使用Text协议,可以用telnet测试
 2 // $gateway = new Gateway("tcp://0.0.0.0:8282");
 3 $context = array(
 4     // 更多ssl选项请参考手册 http://php.net/manual/zh/context.ssl.php
 5     'ssl' => array(
 6         // 请使用绝对路径
 7         'local_cert'                 => 'E:/www/GatewayWorker-for-win/1535601_wxapp.x-nev.com.pem', // 也可以是crt文件
 8         'local_pk'                   => 'E:/www/GatewayWorker-for-win/1535601_wxapp.x-nev.com.key',
 9         'verify_peer'               => false,
10         // 'allow_self_signed' => true, //如果是自签名证书需要开启此选项
11     )
12 );
13 $gateway = new Gateway("Websocket://0.0.0.0:1451",$context);
14 $gateway->transport = 'ssl';

  在小程序的开发工具中要配置,wss域名:1451 。这个端口是在workman中配置,可以试任意端口。至此workman和小程序的连接打通了。

  

  邀请好友pk,就是开一个房间(在workman中对应组的概念),然后在分享的链接中加入roomid这个参数。在用户登入之后,通过workman通知对方。

  分享按钮对应的代码:

 1  onShareAppMessage: function (res) {
 2     if (res.from === 'button') {
 3       var timestamp = new Date().getTime();
 4       var r = parseInt(Math.random() * (100000 - 5000 + 1) + 5000, 10);
 5       var roomid = timestamp + '' + r;
 6       var s_endtime = timestamp + 600000;
 7       var s_openid = wx.getStorageSync('openid');
 8       return {
 9         title: '来啊,狗蛋!对战吧',
10         path: '/pages/dz/dz?pkroom=' + roomid+'&endtime='+s_endtime+'&fqzid='+s_openid,
11         success: function () {
12           var datastr= {pkroom: roomid, endtime: s_endtime, fqzid: s_openid};
13           wx.setStorage({
14             key: 'pkinfo',
15             data:datastr
16           })
17           wx.navigateTo({
18             url: '/pages/dz/dz'
19           });
20         }
21 
22       }
23     } else {
24       return {
25         title: '来啊,狗蛋!',
26         path: '/pages/index/index',
27         imageUrl: '/imgs/yang.jpg'
28       }
29     }
30   }
View Code

  用户通过链接进入对战页面初始化代码:

 1  onLoad: function (options) {
 2 
 3     inituserinfo = new Promise(function (resolve, reject) {
 4       if (app.globalData.userInfo) {
 5         resolve(options);
 6       } else {
 7         app.userInfoReadyCallback = res => {
 8           resolve(options);
 9         };
10       }
11     });
12     inituserinfo.then(options => {
13       if (options.pkroom) {
14         //有参数,判断是否有效期内,判断pkinfo,对比openid
15         if (options.endtime > new Date().getTime()) {
16           //有效期内。
17           var pkinfo = wx.getStorageSync('pkinfo') || {};
18           pkinfo.endtime = options.endtime;
19           pkinfo.fqzid = options.fqzid;
20           pkinfo.pkroom = options.pkroom;
21           wx.setStorage({
22             key: 'pkinfo',
23             data: { 'pkroom': pkinfo.pkroom, 'endtime': pkinfo.endtime, 'fqzid': pkinfo.fqzid }
24           })
25           this.intime(pkinfo);
26         } else {
27           //url上房间过期
28           this.overtime();
29         }
30       } else {
31         //没有参数,则判断有无pkinfo;
32         var pkinfo = wx.getStorageSync('pkinfo');
33         console.log("没有参数:取pkinfo:" + pkinfo.endtime);
34         console.log("没有参数:当前时间:" + new Date().getTime());
35         if (pkinfo && (pkinfo.endtime > new Date().getTime())) {
36           //在有效期内,更新计时器。对比openid,加载用户信息
37           this.intime(pkinfo);
38           //开始监听
39         } else {
40           //没有,或则过期,清空cookie
41           this.overtime();
42         }
43       }
44     });
45   },
View Code

  拿到url参数,我们要判断,这个房间谁是房主,谁是挑战者。 要判断房间是否在有效期内,房主是否关闭房间。因为用户进入挑战页的方式太多,不同方式对应这不同的情况。

 1  //有效期内
 2   intime: function (pkinfo) {
 3     //在有效期内,更新计时器。对比openid,加载用户信息
 4     //计算剩余时间
 5     var stime = pkinfo.endtime - new Date().getTime();
 6     endtime = parseInt(stime / 1000);
 7     var s_openid = wx.getStorageSync('openid');
 8     var userInfo = {
 9       'name': app.globalData.userInfo.nickName,
10       'img': app.globalData.userInfo.avatarUrl,
11       'openid': s_openid
12     };
13     if (s_openid == pkinfo.fqzid) {
14       this.setData({
15         fqz: userInfo,
16         type: 'fqz',
17         pkroom: pkinfo.pkroom
18       })
19     } else {
20       this.setData({
21         tzz: userInfo,
22         type: 'tzz',
23         pkroom: pkinfo.pkroom
24       })
25     }
26   },
27   //房间过期
28   overtime: function () {
29     wx.showToast({
30       title: '对战房间已过期',
31       icon: 'info',
32       duration: 1000
33     });
34     wx.removeStorage({
35       key: 'pkinfo'
36     })
37     setTimeout(() => {
38       wx.redirectTo({
39         url: '/pages/index/index'
40       })
41     }, 1800);
42   },
View Code

 

  现在最关键的地方来了:创建socket连接及监听!!!!!

  小程序只允许同时创建不超过两个socket连接。在页面创建连接的时候尤其得注意,页面跳转、退出、隐藏、显示的过程中,要保护好线程。所以我们统一在onunload的时候,主动关闭socket连接。下次进来的时候根据储存到本地的房间信息重新创建连接。我封装了一个sockethelper.js,因为这个init/open的过程都是异步的,所以使用了promise封装了一下。(class、constructor是es6的语法,不清楚的可以先看一下es6的语法)

 1 class webSocket {
 2 
 3     constructor(obj = {}) {
 4         this.hasConn = false
 5         this.hasOpen = false
 6         this.msghandle = obj;
 7         this.msghandle['ping'] = function (e, _this) {
 8             var data = { 'type': 'pong' };
 9             _this.SendMsg(data);
10         }
11         var _this = this;
12         this.SocketTask = wx.connectSocket({
13             url: 'wss://wxapp.x-nev.com:1451',
14             header: {
15                 'content-type': 'application/json'
16             },
17             method: 'post',
18             success: function (res) {
19                 _this.hasConn = true;
20             },
21             fail: function (err) {
22                 wx.showToast({
23                     title: '网络异常!',
24                 })
25             },
26         });
27     }
28     StartListen() {
29         var _this = this;
30         return new Promise((resolve, reject) => {
31 
32             this.SocketTask.onOpen(function (res) {
33                 console.log("page:socket:open");
34                 _this.hasOpen = true;
35                 resolve(res);
36             })
37             this.SocketTask.onClose(function (res) {
38                 console.log("page:socket:close")
39             })
40 
41             this.SocketTask.onError(function (res) {
42                 console.log("page:socket:error-");
43                // reject(res);
44             })
45 
46             this.SocketTask.onMessage(function (onMessage) {
47                 var data = JSON.parse(onMessage.data);
48                 var msgtype = data['type'];
49                 if (msgtype in _this.msghandle) {
50                     _this.msghandle[msgtype](data, _this);
51                 }
52             })
53 
54         });
55 
56     }
57     SendMsg(msg, callback) {
58         if (this.hasOpen && this.hasConn) {
59             this.SocketTask.send({
60                 data: JSON.stringify(msg),
61                 success: (e) => { if (callback) { callback(e) } }
62             })
63         } else {
64             console.log("没有open,调用一下");
65             this.StartListen().then(() => {
66                 this.SocketTask.send({
67                     data: JSON.stringify(msg),
68                     success: (e) => { if (callback) { callback(e) } }
69                 })
70             });
71         }
72 
73     }
74 }
75 
76 module.exports = { webSocket };
View Code

  用户进入房间后,初始化参数后,要通知对方。

 1 onReady: function () {
 2 
 3     var info = {};
 4     var s_openid = wx.getStorageSync('openid');
 5     info.openid = s_openid;
 6     info.pkroom = this.data.pkroom;
 7     if (this.data.type == "fqz") {
 8       info.mark = "fqz";
 9       info.name = this.data.fqz.name;
10       info.img = this.data.fqz.img;
11     } else {
12       info.mark = "tzz"
13       info.name = this.data.tzz.name;
14       info.img = this.data.tzz.img;
15     }
16     //如果从分享挑战页面进来,要注册获取用户信息的回调,注册socket
17     this.registResponse(info);
18     app.globalData.stask.SendMsg({
19       'type': 'pk',
20       'mark': info.mark,
21       'openid': info.openid,
22       'name': info.name,
23       'img': info.img,
24       'pkroom': info.pkroom
25     });
26     this.formattime();
27     t2 = setInterval(() => {
28       this.formattime();
29     }, 1000);
30   },
31   formattime: function () {
32     endtime--;
33     var m = parseInt(endtime / 60);
34     var s = parseInt(endtime % 60);
35     m = m.toString().length > 1 ? m : '0' + m;
36     s = s.toString().length > 1 ? s : '0' + s;
37     var tem = `${m}:${s}`;
38     this.setData({
39       time: tem
40     });
41   },
View Code

  当双方都进入房间后。房主出发start事件,开始答题。start事件请求后台分配对应等级的题目,并通知对方题目

 1 start: function () {
 2     //请求对应等级的题目,
 3     wx.request({
 4       url: 'https://fotonpickup.risingad.com/wxxldev/index.php?c=WXSignApi&a=Question',
 5       data: { 'roomid': this.data.pkroom, 'one': this.data.fqz.openid, 'two': this.data.tzz.openid },
 6       success: (result) => {
 7         this.setData({
 8           qlist: result.data.row,
 9           recordid: result.data.flag,
10           current: 0
11         });
12         //开始答题通知workman,提供题目id。跳转对战页面。
13         app.globalData.stask.SendMsg({
14           'type': 'ready',
15           'recordid': result.data.flag,
16           'uid': this.data.tzz.openid
17         });
18       }
19     });
20 
21   },
View Code

  双方开始答题,配置响应事件。这个就不一一列举了,上代码

  1  registResponse: function (pkinfo) {
  2     var obj = {
  3       'pk': (res, _this) => {
  4         //拿到用户信息,绑定用户头像。
  5         if (res.content.length == 1) {
  6           if (res.content[0].mark == "fqz") {
  7             this.setData({
  8               'fqz': res.content[0]
  9             });
 10           } else {
 11             this.setData({
 12               'tzz': res.content[0]
 13             });
 14           }
 15         } else {
 16           if (res.content[0].mark == "fqz") {
 17             this.setData({
 18               'fqz': res.content[0],
 19               'tzz': res.content[1]
 20             });
 21           } else {
 22             this.setData({
 23               'fqz': res.content[1],
 24               'tzz': res.content[0]
 25             });
 26           }
 27         }
 28       },
 29       'ready': (res, _this) => {
 30         //此处注册的监听,只有tzz可以捕获到。后端是单独推送的
 31         //挑战者请求加载相应的题目,并准备好界面。
 32         console.log("拉取题:" + res.id);
 33         wx.request({
 34           url: 'https://fotonpickup.risingad.com/wxxldev/index.php?c=WXSignApi&a=GetQuestion',
 35           data: { 'id': res.id },
 36           success: (result) => {
 37             this.setData({
 38               qlist: result.data,
 39               current: 0
 40             });
 41             //通知服务器tzz准备好了,
 42             app.globalData.stask.SendMsg({ 'type': 'start' });
 43           }
 44         });
 45       },
 46       'start': (res, _this) => {
 47         //开始各自准备pk界面。
 48         this.setData({
 49           start: true
 50         });
 51         //在这里开启当前计时器。
 52         console.log("1.开启计时器");
 53         t1 = setInterval(() => {
 54 
 55           if (this.data.time2 > 0) {
 56             this.setData({
 57               time2: this.data.time2 - 1
 58             });
 59             if (this.data.myself && this.data.opposite) {
 60               //都已答题,如果不是最后一题,进入下一题。并积分,初始化状态
 61               console.log("2.都已答题");
 62               if (this.data.current == this.data.qlist.length - 1) {
 63                 //结束,出成绩。
 64                 clearInterval(t1);
 65                 console.log("9.结束计时器");
 66                 this.setData({
 67                   isend: true
 68                 });
 69                 this.next(true);
 70               } else {
 71                 console.log("3.调用下一题");
 72                 this.next();
 73               }
 74             }
 75           } else {
 76             //时间到了,未答题。展示正确答案。进入下一题。
 77             //初始化 答题状态、时间、样式。
 78             console.log("4.时间到了有人未答题");
 79             if (this.data.current == this.data.qlist.length - 1) {
 80               //结束,出成绩。
 81               clearInterval(t1);
 82               this.setData({
 83                 isend: true
 84               });
 85               this.next(true);
 86               console.log("10.结束计时器");
 87             } else {
 88               this.data.classlist[this.data.qlist[this.data.current].ropt - 1] = 'right';
 89               setTimeout(() => {
 90                 console.log("5.时间到了有人未答题,显示正确答案,进入下一题");
 91                 this.next();
 92               }, 1000);
 93 
 94             }
 95 
 96           }
 97         }, 1000);
 98       },
 99       'dt': (res, _this) => {
100         console.log("7." + res.mark + "选择了" + res.opts);
101         var isright = res.opts == this.data.qlist[this.data.current].ropt ? true : false;
102         if (res.mark != this.data.type) {
103           this.setData({
104             opposite: true,//对方已答题。
105             oppositeright: isright,
106             oppositeopt: res.opts
107           })
108         } else {
109           this.setData({
110             myself: true,//自己已答题
111             myselfright: isright
112           });
113           if (isright) {
114             //答对
115             this.data.classlist[res.opts - 1] = 'right';
116           } else {
117             this.data.classlist[res.opts - 1] = 'error';
118           }
119         }
120         if (this.data.myself && this.data.opposite) {
121           //都已答题
122           this.data.classlist[this.data.qlist[this.data.current].ropt - 1] = 'right';
123           this.data.classlist[this.data.oppositeopt - 1] = this.data.oppositeright ? 'right' : 'error';
124         }
125         this.setData({
126           'classlist': this.data.classlist
127         });
128       },
129       'gameover': (res, _this) => {
130         wx.showToast({
131           title: '房主关闭房间',
132           icon: 'info',
133           duration: 1000
134         });
135         wx.removeStorage({
136           key: 'pkinfo',
137           complete: function (e) {
138             console.log(e);
139           }
140         });
141         setTimeout(() => {
142           wx.redirectTo({
143             url: '/pages/index/index'
144           })
145         }, 2000);
146       }
147     }
148     //开启监听
149     app.globalData.stask = new webSocket();
150     //注册监听响应事件
151     Object.assign(app.globalData.stask.msghandle, obj);
152 
153   },
View Code

  在上一下workman都监听的代码

  1  /**
  2      * 当客户端发来消息时触发
  3      * @param int $client_id 连接id
  4      * @param mixed $message 具体消息
  5      */
  6     public static function onMessage($client_id, $message)
  7     {
  8         // 向所有人发送
  9 
 10         $message_data = json_decode($message, true);
 11         $now = date('y-m-d H:i:s', time());
 12         $filepath="E:\\www\\GatewayWorker-for-win\\msg.txt";
 13         file_put_contents($filepath, "time:$now,client_id:".$client_id.':msg:'.$message.PHP_EOL, FILE_APPEND);
 14         if (!$message_data) {
 15             return ;
 16         }
 17 
 18         // 根据类型执行不同的业务
 19         switch ($message_data['type']) {
 20 
 21             // 客户端回应服务端的心跳
 22             case 'pong':
 23                 return;
 24               // 客户端登录 message格式: {type:login, name:xx, room_id:1} ,添加到客户端,广播给所有客户端xx进入聊天室
 25             case 'login':
 26                 // 判断是否有房间号
 27                 if (!isset($message_data['room_id'])) {
 28                     return '';
 29                 }
 30                 // 把房间号昵称放到session中
 31                 $room_id = $message_data['room_id'];
 32                 //昵称
 33                 $client_name = htmlspecialchars($message_data['client_name']);
 34                 //头像
 35                 $client_img= $message_data['client_img'];
 36                 // 设置当前用户的sesion可直接设置。等同于 Gateway::setSession(string $client_id, array $session);
 37                 $_SESSION['room_id'] = $room_id;
 38                 $_SESSION['client_name'] = $client_name;
 39                 $_SESSION['client_img'] = $client_img;
 40                 // 转播给当前房间的所有客户端,xx进入聊天室 message {type:login, client_id:xx, name:xx}
 41                 $new_message = array('type'=>$message_data['type'], 'client_id'=>$client_id, 'client_name'=>htmlspecialchars($client_name), 'time'=>date('Y-m-d H:i:s'));
 42                 Gateway::sendToGroup($room_id, json_encode($new_message));
 43                 Gateway::joinGroup($client_id, $room_id);
 44                  // 获取房间内所有用户列表
 45                  $clients_list = Gateway::getClientSessionsByGroup($room_id);
 46                  foreach ($clients_list as $tmp_client_id=>$item) {
 47                      $clients_list[$tmp_client_id] = $item['client_name'];
 48                  }
 49                 // 给当前用户发送用户列表
 50                 $new_message['client_list'] = $clients_list;
 51                 Gateway::sendToCurrentClient(json_encode($new_message));
 52                 return;
 53                 
 54             // 客户端发言 message: {type:say, to_client_id:xx, content:xx}
 55             case 'say':
 56                 // 非法请求
 57                 if (!isset($_SESSION['room_id'])) {
 58                     throw new \Exception("\$_SESSION['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']}");
 59                 }
 60                 $room_id = $_SESSION['room_id'];
 61                 $client_name = $_SESSION['client_name'];
 62                 $client_img = $_SESSION['client_img'];
 63                 // 私聊
 64                 if ($message_data['to_client_id'] != 'all') {
 65                     $new_message = array(
 66                         'type'=>'say',
 67                         'from_client_id'=>$client_id,
 68                         'from_client_name' =>$client_name,
 69                         'to_client_id'=>$message_data['to_client_id'],
 70                         'content'=>nl2br(htmlspecialchars($message_data['content'])),
 71                         'time'=>date('Y-m-d H:i:s'),
 72                     );
 73                     Gateway::sendToClient($message_data['to_client_id'], json_encode($new_message));
 74                     return Gateway::sendToCurrentClient(json_encode($new_message));
 75                 }
 76                 
 77                 $new_message = array(
 78                     'type'=>'say',
 79                     'from_client_id'=>$client_id,
 80                     'from_client_name' =>$client_name,
 81                     'to_client_id'=>'all',
 82                     'client_img'=> $client_img ,
 83                     'content'=>nl2br(htmlspecialchars($message_data['content'])),
 84                     'time'=>date('Y-m-d H:i:s'),
 85                 );
 86                 return Gateway::sendToGroup($room_id, json_encode($new_message));
 87             case 'pk':
 88                     //{type:pk, pkroom:xx,name:'xxx',img:'xxx'}
 89                     $pkroom= $message_data['pkroom'];
 90                     $_SESSION['pkroom']= $pkroom;
 91                     $_SESSION['name']= $message_data['name'];
 92                     $_SESSION['img']= $message_data['img'];
 93                     $_SESSION['mark']= $message_data['mark'];
 94                     $_SESSION['openid']= $message_data['openid'];
 95                     //绑定openid到client_id;
 96                     Gateway::bindUid($client_id, $message_data['openid']);
 97                     //开一个新房间
 98                     Gateway::joinGroup($client_id, $pkroom);
 99                     //返回当前对战房间的人员
100                     
101                     $clients_list = Gateway::getClientSessionsByGroup($pkroom);
102                     foreach ($clients_list as $tmp_client_id=>$item) {
103                         $pklist[]=$item;
104                     }
105                     $new_message = array(
106                         'type'=>'pk',
107                         'content'=>$pklist
108                     );
109                     return Gateway::sendToGroup($pkroom, json_encode($new_message));
110             case 'dt':
111                  $opts=$message_data['opts'];
112                  $mark= $_SESSION['mark'];
113                  $pkroom= $_SESSION['pkroom'];
114                  $new_message = array(
115                     'type'=>'dt',
116                    'opts'=>$opts,
117                    'mark'=>$mark
118                 );
119                 return Gateway::sendToGroup($pkroom, json_encode($new_message));
120             case 'ready':
121                 $recordid= $message_data['recordid'];
122                 $uid= $message_data['uid'];//tzz的uid
123                 $new_message = array(
124                    'type'=>'ready',
125                    'id'=>$recordid
126                 );
127                 //只给tzz响应
128                 return  Gateway::sendToUid($uid, json_encode($new_message));
129             case 'start':
130                  $pkroom= $_SESSION['pkroom'];
131                  $new_message = array(
132                     'type'=>'start'
133                 );
134                  return Gateway::sendToGroup($pkroom, json_encode($new_message));
135             case 'gameover':
136                     $pkroom= $_SESSION['pkroom'];
137                     $new_message = array(
138                     'type'=>'gameover'
139                 );
140                 Gateway::sendToGroup($pkroom, json_encode($new_message));
141                return Gateway::ungroup($pkroom);
142         }
143     }
View Code

  答题基本的思路就是这样。里面有个聊天室也是基于workman开发的,见图5,样式是模仿微信。有空会重新补充一下细节。现在上一下图。本人是后台程序,前端不太擅长,一些细节没有优化,请见谅。有问题的可以联系我:mian_wu@qq.com

     

  

   

 

  

posted @ 2018-12-12 18:32  mian_wu  阅读(2884)  评论(1编辑  收藏  举报