江西小程序-打造一款简单猜拳小程序(go+websocket+redis+mysql+小程序前端)(五)
本节思路:
基于本系列第二节内容,实现一个websocket服务端,redis为存储介质,以自己约定的cmd命令为简单通讯协议,与小程序进行通讯,实现简单的2人猜拳功能
cmd命令按照如下流程:
login(client->server),小程序发给服务端,登录命令,服务端接收命令后,判断房间人数是否已满,未满则保存客户端websocket连接到列表,客户端用户信息存入redis,更新在线用户列表,然后发送init(server->client)命令,通知所有在线用户,更新在线用户列表。
当客户端点击准备按钮,发送ready(client->server)命令到服务端,服务端更新该用户的ready标识位(存于redis),计算房间人数是否已满,以及房间所有用户是否都已设置ready标识位,如是,则发送start(server->client)命令给所有用户,开始游戏。
客户端选择好自己的出拳信息,发送guess(client->server)命令到服务端,服务端记录该用户出拳数据,判断房间内所有人都以提交guess命令,则计算最后结果,发送result(server->client)命令给所有用户。
客户端接收到result命令后,重新进入ready流程。
如退出小程序,客户端发送logout(client->server)命令到服务端,服务端从列表中删除该用户,重新发送init(server->client)命令到所有其他在线用户,更新在线用户列表。
命令流程如下:
login(client->server)
init(server->client)
ready(client->server)
start(server->client)
guess(client->server)
result(server->client)
logout(client->server)
效果示意图:
login
ready
start
result
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.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;
}
源码:github
https://github.com/keyunq/ytguess