本节思路:
cmd命令按照如下流程: 当客户端点击准备按钮,发送ready(client->server)命令到服务端,服务端更新该用户的ready标识位(存于redis),计算房间人数是否已满,以及房间所有用户是否都已设置ready标识位,如是,则发送start(server->client)命令给所有用户,开始游戏。 客户端选择好自己的出拳信息,发送guess(client->server)命令到服务端,服务端记录该用户出拳数据,判断房间内所有人都以提交guess命令,则计算最后结果,发送result(server->client)命令给所有用户。 客户端接收到result命令后,重新进入ready流程。 如退出小程序,客户端发送logout(client->server)命令到服务端,服务端从列表中删除该用户,重新发送init(server->client)命令到所有其他在线用户,更新在线用户列表。
命令流程如下:
效果示意图: go服务端 package main import ( "golang.org/x/net/websocket" "fmt" "log" "net/http" "github.com/go-redis/redis" "encoding/json" "strconv" ) const max_room_num = 2 var ( JSON = websocket.JSON // codec for JSON Message = websocket.Message // codec for string, []byte ActiveClients = make(map[string]ClientConn) // map containing clients //在线websocket列表 User = make(map[string]string) ) type ClientConn struct { websocket *websocket.Conn } type UserMsg struct { Room string Cmd string User string AvatarUrl string Content string Uuid string HandNum string GuessNum string } type UserInfo struct { User string AvatarUrl string Uuid string } type ReplyMsg struct { Room string Cmd string Data string } type GuessResult struct { Result string CurrentNum int HandRecord map[string]string GuessRecord map[string]string } func echoHandler(ws *websocket.Conn) { var err error var userMsg UserMsg for { var data []byte if err = websocket.Message.Receive(ws, &data); err != nil { fmt.Println("can't receive") break } err = json.Unmarshal(data, &userMsg) fmt.Println(userMsg) go wsHandler(ws,userMsg) } } func wsHandler(ws *websocket.Conn,userMsg UserMsg) { sockCli := ClientConn{ws} var err error redisClient := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set DB: 0, // use default DB }) //登录 if userMsg.Cmd == "login" { fmt.Println("login") //判断房间人数是否已满 checkNumTmp := redisClient.SCard(userMsg.Room) checkNum := checkNumTmp.Val() if(checkNum < max_room_num) { fmt.Println("checkNum success") //socket用户列表新增当前用户websocket连接 ActiveClients[userMsg.Uuid] = sockCli //用户uuid保存到redis房间set集合内 redisClient.SAdd("ROOM:"+userMsg.Room,userMsg.Uuid) var me UserInfo me.User = userMsg.User me.AvatarUrl = userMsg.AvatarUrl me.Uuid = userMsg.Uuid //生成用户信息json串 b, err := json.Marshal(me) if err != nil { fmt.Println("Encoding User Faild") } else { //保存用户信息到redis redisClient.Set("USER:"+me.Uuid,b,0) //初始化用户 initOnlineMsg(redisClient,userMsg) } } else { var rm ReplyMsg rm.Room = userMsg.Room rm.Cmd = "loginFailed" rm.Data = "登录失败,人数已满" sendMsg,err2 := json.Marshal(rm) sendMsgStr := string(sendMsg) fmt.Println(sendMsgStr) if err2 != nil { } else { if err = websocket.Message.Send(ws, sendMsgStr); err != nil { log.Println("Could not send UsersList to ", userMsg.User, err.Error()) } } } //准备 } else if userMsg.Cmd == "ready" { redisClient.Set("READY:"+userMsg.Uuid,"ready",0) //从redis取房间内的所有用户uuid roomSlice := redisClient.SMembers("ROOM:"+userMsg.Room) //用户uuid保存到一个go切片online online := roomSlice.Val() i := 0 //循环取在线用户个人信息 if len(online) != 0 { for _, na := range online { if na != "" { userJson := redisClient.Get("READY:"+na) userJson2 := userJson.Val() if userJson2 == "ready" { i++ } } } } if i == len(online) && i == max_room_num { var rm ReplyMsg rm.Room = userMsg.Room rm.Cmd = "start" rm.Data = "" broadcast(redisClient,userMsg,rm) } //退出 } else if userMsg.Cmd == "logout" { fmt.Println("logout") //socket用户列表删除该用户websocket连接 delete(ActiveClients,userMsg.Uuid) //从redis房间set集合内删除该用户uuid redisClient.SRem("ROOM:"+userMsg.Room,userMsg.Uuid) //初始化用户 initOnlineMsg(redisClient,userMsg) //出拳 } else if userMsg.Cmd == "guess" { var result string fmt.Println("guess") fmt.Println(userMsg.HandNum) fmt.Println(userMsg.GuessNum) myHandNum,_ := strconv.Atoi(userMsg.HandNum) myGuessNum,_ := strconv.Atoi(userMsg.GuessNum) redisClient.Set("HANDNUM:"+userMsg.Uuid,myHandNum,0) redisClient.Set("GUESSNUM:"+userMsg.Uuid,myGuessNum,0) //从redis取房间内的所有用户uuid roomSlice := redisClient.SMembers("ROOM:"+userMsg.Room) //用户uuid保存到一个go切片online online := roomSlice.Val() i := 0 //循环取在线用户 if len(online) != 0 { for _, na := range online { if na != "" { handnumCmd := redisClient.Get("HANDNUM:"+na) handnum := handnumCmd.Val() if handnum != "" { i++ } } } } //房间内所有人都已提交,则计算最后结果 if i == len(online) && i == max_room_num { var handRecordList map[string]string handRecordList = make(map[string]string) var guessRecordList map[string]string guessRecordList = make(map[string]string) //计算正确结果currentNum currentNum := 0 //循环取在线用户 if len(online) != 0 { for _, na := range online { if na != "" { //取某用户的出拳数据,已用户名为key,存入结果map handnumCmd := redisClient.Get("HANDNUM:"+na) handnum := handnumCmd.Val() guessnumCmd := redisClient.Get("GUESSNUM:"+na) guessnum := guessnumCmd.Val() userJson := redisClient.Get("USER:"+na) userJson2 := userJson.Val() var user UserInfo json.Unmarshal([]byte(userJson2), &user) handRecordList[user.User] = handnum guessRecordList[user.User] = guessnum //计算结果 thandnum,_ := strconv.Atoi(handnum) currentNum = currentNum + thandnum } } } //给各个用户发送结果消息 if len(online) != 0 { for _, na := range online { if na != "" { guessnumCmd := redisClient.Get("GUESSNUM:"+na) guessnum := guessnumCmd.Val() tguessnum ,_ := strconv.Atoi(guessnum) if tguessnum == currentNum { result = "1" } else { result = "0" } var guessResult GuessResult guessResult.Result = result guessResult.CurrentNum = currentNum guessResult.HandRecord = handRecordList guessResult.GuessRecord = guessRecordList resultTmp,_ := json.Marshal(guessResult) resultData := string(resultTmp) //删除用户准备状态 redisClient.Del("READY:"+na) //删除用户猜拳数据 redisClient.Del("HANDNUM:"+na) redisClient.Del("GUESSNUM:"+na) var rm ReplyMsg rm.Room = userMsg.Room rm.Cmd = "result" rm.Data = resultData sendMsg,_ := json.Marshal(rm) sendMsgStr := string(sendMsg) if err = websocket.Message.Send(ActiveClients[na].websocket, sendMsgStr); err != nil { log.Println("Could not send UsersList to ", "", err.Error()) } } } } } //发消息 } else { /* //从redis取房间内的所有用户uuid roomSlice := redisClient.SMembers(userMsg.Room) //用户uuid保存到一个go切片online online := roomSlice.Val() //循环给房间内用户发送消息 if len(online) != 0 { for _, na := range online { if na != "" { //ActiveClients[na].websocket就是用户对应的websocket链接 if err = websocket.Message.Send(ActiveClients[na].websocket, userMsg.User+"说:"+userMsg.Content); err != nil { log.Println("Could not send message to ", userMsg.User, err.Error()) } } } }*/ } } //房间成员初始化,有人加入或者退出都要重新初始化,相当于聊天室的在线用户列表的维护 func initOnlineMsg(redisClient *redis.Client,userMsg UserMsg) { var err error //从redis取房间内的所有用户uuid roomSlice := redisClient.SMembers("ROOM:"+userMsg.Room) //用户uuid保存到一个go切片online online := roomSlice.Val() var onlineList []string //循环取在线用户个人信息 if len(online) != 0 { for _, na := range online { if na != "" { userJson := redisClient.Get("USER:"+na) userJson2 := userJson.Val() onlineList = append(onlineList,userJson2) } } } fmt.Println("get online success") //生成在线用户信息json串 //c, err := json.Marshal(onlineList) onlineListStr,err2 := json.Marshal(onlineList) var rm ReplyMsg rm.Room = userMsg.Room rm.Cmd = "init" rm.Data = string(onlineListStr) sendMsg,err2 := json.Marshal(rm) sendMsgStr := string(sendMsg) fmt.Println("init") if err2 != nil { } else { //给所有用户发初始化消息 if len(online) != 0 { for _, na := range online { if na != "" { if err = websocket.Message.Send(ActiveClients[na].websocket, sendMsgStr); err != nil { log.Println("Could not send UsersList to ", "", err.Error()) } } } } //若房间人数满,发送就绪消息 if len(online) >= max_room_num { fmt.Println("full") var rm ReplyMsg rm.Room = userMsg.Room rm.Cmd = "full" rm.Data = "" sendMsg,_ := json.Marshal(rm) sendMsgStr := string(sendMsg) for _, na := range online { if na != "" { if err = websocket.Message.Send(ActiveClients[na].websocket, sendMsgStr); err != nil { log.Println("Could not send UsersList to ", "", err.Error()) } } } } } } //广播消息 func broadcast(redisClient *redis.Client,userMsg UserMsg,rm ReplyMsg) { var err error //从redis取房间内的所有用户uuid roomSlice := redisClient.SMembers("ROOM:"+userMsg.Room) //用户uuid保存到一个go切片online online := roomSlice.Val() sendMsg,err2 := json.Marshal(rm) sendMsgStr := string(sendMsg) fmt.Println("broadcast") if err2 != nil { } else { //给所有用户发消息 if len(online) != 0 { for _, na := range online { if na != "" { if err = websocket.Message.Send(ActiveClients[na].websocket, sendMsgStr); err != nil { log.Println("Could not send UsersList to ", "", err.Error()) } } } } } } func main() { http.Handle("/echo", websocket.Handler(echoHandler)) http.Handle("/", http.FileServer(http.Dir("."))) err := http.ListenAndServe(":8929", nil) if err != nil { panic("ListenAndServe: " + err.Error()) } }
小程序代码: //app.js App({ onLaunch: function () { console.log("App生命周期函数——onLaunch函数"); }, checkSession:function(mysessionid) { return new Promise(function(resolve, reject) { wx.request({ url: 'https://xxx.xxxxx.com/check.php', header: { sessionid:mysessionid }, success: function(res) { console.log("检查sessionid是否有效") resolve(res.data) }, fail: function(e) { reject(e) } }) }) }, login:function() { return new Promise(function(resolve, reject) { wx.login({ success: function (res0) { if (res0.code) { wx.request({ url: 'https://xxx.xxxxx.com/login.php', data: { code: res0.code }, header: { 'content-type': 'application/json' }, success: function(res) { console.log("取得新的sessionid") console.log(res.data) var mysessionid = res.data.k wx.setStorageSync("mysessionid",mysessionid) var myuuid = res.data.v wx.setStorageSync("myuuid",myuuid) resolve(mysessionid) }, fail: function(e) { reject(e) } }) } } }) }) }, getWxUserInfo:function() { return new Promise(function(resolve, reject) { wx.getUserInfo({ withCredentials: false, success: function(res) { console.log("取得新的userInfo") var userInfo = res.userInfo wx.setStorageSync("userInfo",userInfo) console.log("setUserInfo") resolve(userInfo) } }) }) }, getUserInfo:function() { var that = this return new Promise(function(resolve, reject) { var mysessionid = wx.getStorageSync('mysessionid') if(mysessionid) { console.log("sessionid存在") that.checkSession(mysessionid).then(function(sessionContent){ if(sessionContent == 0) { console.log("sessionid无效-取userInfo存到本地") that.login().then(function(){ that.getWxUserInfo().then(function(userInfo){ resolve(userInfo) }) }) } else { console.log("sessionid有效-直接取本地userInfo") var userInfo = wx.getStorageSync("userInfo") resolve(userInfo) } }) } else { console.log("sessionid不存在,重新走登录流程") that.login().then(function(){ that.getWxUserInfo().then(function(userInfo){ resolve(userInfo) }) }) } }) }, globalData:{ userInfo:null, onlineList:[], onlineStatus:false, myHandNum:0, myGuessNum:0 } }) page/index.js //index.js //获取应用实例 var app = getApp() Page({ data: { userInfo: {}, onlineList:{}, status:0, statusStr:"等待中", guessBoxStatus:"hideBox", handList:['0','1','2','3','4','5'], handStyleList:['primary','default','default','default','default','default'], guessList:['0','1','2','3','4','5','6','7','8','9','10'], guessStyleList:['primary','default','default','default','default','default','default','default','default','default','default'], buttonList:['0','1','2'], buttonStrList:['准备','开始','提交'], buttonStyleList:['btnShow','btnHide','btnHide'], buttonFuncList:['ready','start','guess'] }, onLoad: function () { console.log("Page onLoad函数"); wx.playBackgroundAudio({ dataUrl: 'https://xxx.xxxxx.com/8585.mp3', title: '古琴音效', coverImgUrl: 'https://xxx.xxxxx.com/logo.png', success: function() { console.log("播放音效") } }) }, onHide: function() { console.log('发送注销消息') var myuuid = wx.getStorageSync('myuuid') var msg = new Object(); msg.Room = '1'; msg.Cmd = 'logout'; msg.Uuid = myuuid; var str = JSON.stringify(msg) wx.sendSocketMessage({ data:str }) wx.closeSocket() app.globalData.onlineStatus = false }, onShow: function() { var that = this app.getUserInfo().then(function(userInfo){ that.setData({ userInfo:userInfo }) that.wsHandler(userInfo) that.initBox() }) }, wsHandler: function(userInfo) { var that = this //websocket wx.connectSocket({ url: 'wss://xx.xxxxx.com/echo' }) wx.onSocketOpen(function(res) { console.log('WebSocket连接已打开!') var myuuid = wx.getStorageSync('myuuid') var msg = new Object(); msg.Room = '1'; msg.Cmd = 'login'; msg.User = userInfo.nickName; msg.AvatarUrl = userInfo.avatarUrl; msg.Uuid = myuuid; var str = JSON.stringify(msg) wx.sendSocketMessage({ data:str }) }) wx.onSocketMessage(function(res) { var msg = JSON.parse(res.data) if(msg.Cmd == 'init') { var userList = JSON.parse(msg.Data) app.globalData.onlineList = [] for(var i=0;i<userList.length;i++){ var user = JSON.parse(userList[i]) app.globalData.onlineList.push(user) } that.setData({ onlineList:app.globalData.onlineList, status:0, statusStr:'等待中' }) } if(msg.Cmd == 'full') { that.setData({ status:1, statusStr:'准备开始' }) } if(msg.Cmd == 'result') { var result = JSON.parse(msg.Data) var content = "总数为"+result.CurrentNum+"\n" for (var value in result.HandRecord) { content = content+value+"出拳:"+result.HandRecord[value]+"\n"; } for (var value in result.GuessRecord) { content = content+value+"猜拳:"+result.GuessRecord[value]+"\n"; } if(result.Result == 1) { content = "恭喜你,猜中啦\n" + content wx.showModal({ content: content, showCancel: false, success: function (res) { if (res.confirm) { that.initBox() } } }); } if(result.Result == 0) { content = "很遗憾,猜错啦\n" + content wx.showModal({ content: content, showCancel: false, success: function (res) { if (res.confirm) { that.initBox() } } }); } } if(msg.Cmd == 'start') { that.setData({ status:2, statusStr:'游戏中', guessBoxStatus:'showBox', buttonStyleList:['btnHide','btnHide','btnShow'], }) } }) }, setHandNum: function(event) { var that = this console.log(event.target.dataset.handnum) app.globalData.myHandNum = event.target.dataset.handnum var myList = that.data.handStyleList for(var i=0;i<myList.length;i++) { if(i == event.target.dataset.handnum) { myList[i] = 'primary' } else { myList[i] = 'default' } } that.setData({ handStyleList:myList }) }, setGuessNum: function(event) { var that = this console.log(event.target.dataset.guessnum) app.globalData.myGuessNum = event.target.dataset.guessnum var myList = that.data.guessStyleList for(var i=0;i<myList.length;i++) { if(i == event.target.dataset.guessnum) { myList[i] = 'primary' } else { myList[i] = 'default' } } that.setData({ guessStyleList:myList }) }, guess: function() { var that = this var userInfo = that.data.userInfo var myuuid = wx.getStorageSync('myuuid') var msg = new Object(); msg.Room = '1'; msg.Cmd = 'guess'; msg.User = userInfo.nickName; msg.AvatarUrl = userInfo.avatarUrl; msg.Uuid = myuuid; msg.HandNum = app.globalData.myHandNum msg.GuessNum = app.globalData.myGuessNum var str = JSON.stringify(msg) wx.sendSocketMessage({ data:str }) }, ready: function() { var that = this var userInfo = that.data.userInfo var myuuid = wx.getStorageSync('myuuid') var msg = new Object(); msg.Room = '1'; msg.Cmd = 'ready'; msg.User = userInfo.nickName; msg.AvatarUrl = userInfo.avatarUrl; msg.Uuid = myuuid; var str = JSON.stringify(msg) wx.sendSocketMessage({ data:str }) that.setData({ status:1, statusStr:'等待对手,准备开始', buttonStyleList:['btnHide','btnHide','btnHide'], }) }, start: function() { var that = this var userInfo = that.data.userInfo var myuuid = wx.getStorageSync('myuuid') var msg = new Object(); msg.Room = '1'; msg.Cmd = 'start'; msg.User = userInfo.nickName; msg.AvatarUrl = userInfo.avatarUrl; msg.Uuid = myuuid; var str = JSON.stringify(msg) wx.sendSocketMessage({ data:str }) }, initBox: function() { var that = this that.setData({ status:0, statusStr:"等待中", guessBoxStatus:"hideBox", handList:['0','1','2','3','4','5'], handStyleList:['primary','default','default','default','default','default'], guessList:['0','1','2','3','4','5','6','7','8','9','10'], guessStyleList:['primary','default','default','default','default','default','default','default','default','default','default'], buttonList:['0','1','2'], buttonStrList:['准备','开始','提交'], buttonStyleList:['btnShow','btnHide','btnHide'], buttonFuncList:['ready','start','guess'] }) }, getAudioStatus: function() { wx.getBackgroundAudioPlayerState({ success: function(res) { var status = res.status var dataUrl = res.dataUrl var currentPosition = res.currentPosition var duration = res.duration var downloadPercent = res.downloadPercent console.log("音乐状态"+status) console.log("音乐长度"+duration) } }) } })
ps:播放音乐的功能,在开发工具可以看到,真机上没有听到声音,暂时还没找到解决办法 check.php <?php $post_data = $_POST; $header = get_all_headers(); $sessionid = $header['sessionid']; $host = '127.0.0.1'; $port = '6379'; $timeout = 0; $redis = new Redis(); $redis->connect($host, $port, $timeout); $session_content = $redis->get("miniappsession:".$sessionid); if($session_content) { echo $session_content; } else { echo 0; } /** * 获取自定义的header数据 */ function get_all_headers(){ // 忽略获取的header数据 $ignore = array('host','accept','content-length','content-type'); $headers = array(); foreach($_SERVER as $key=>$value){ if(substr($key, 0, 5)==='HTTP_'){ $key = substr($key, 5); $key = str_replace('_', ' ', $key); $key = str_replace(' ', '-', $key); $key = strtolower($key); if(!in_array($key, $ignore)){ $headers[$key] = $value; } } } return $headers; } login.php <?php $code = $_GET['code']; define("APPID",'xxxxxxxxxxxxxxxxxxxxx'); define("SECRET",'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'); $url = "https://api.weixin.qq.com/sns/jscode2session?appid=".APPID."&secret=".SECRET."&js_code=".$code."&grant_type=authorization_code"; $rs = curlGet($url); $arr = json_decode($rs); $str = randomFromDev(32); $host = '127.0.0.1'; $port = '6379'; $timeout = 0; $redis = new Redis(); $redis->connect($host, $port, $timeout); $expires_time = 15*24*60*60; $session_content = md5($arr->openid.$expires_time); $redis->setex("miniappsession:".$str,$expires_time,$session_content); $sessionObj['k'] = $str; $sessionObj['v'] = $session_content; echo json_encode($sessionObj); function randomFromDev($len) { $fp = @fopen('/dev/urandom','rb'); $result = ''; if ($fp !== FALSE) { $result .= @fread($fp, $len); @fclose($fp); } else { trigger_error('Can not open /dev/urandom.'); } $result = md5($result); // convert from binary to string //$result = base64_encode($result); // remove none url chars //$result = strtr($result, '+/', '-_'); // Remove = from the end //$result = str_replace('=', ' ', $result); return $result; } function curlGet($url, $method = 'get', $data = '') { $ch = curl_init(); $header = 'Accept-Charset: utf-8'; curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method)); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)'); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_AUTOREFERER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $temp = curl_exec($ch); return $temp; } |