上一篇文章是對需求的分析,本次將逐漸進入代碼階段。本次主要的內容包括服務端wss的部署以及小程序端用戶受權的時序及邏輯。html
wss的配置與部署算法
微信小程序出於安全考慮,要求全部涉及到網絡的操做,必須使用安全的網絡請求,如https和wss,卻使用的域名必須備案以及不能帶端口號。詳情請參考微信小程序官方文檔小程序
雖然在開發的過程當中,微信提供的開發者工具能夠忽略https和wss的校驗,但程序上線時,就必須使用https和wss協議,因此,在這裏,先來介紹下wss協議的部署。微信小程序
WSS 是 Web Socket Secure 的簡稱, 它是 WebSocket 的加密版本. 咱們知道 WebSocket 中的數據是不加密的, 可是不加密的數據很容易被別有用心的人竊取, 所以爲了保護數據安全, 人們將 WebSocket 與 SSL 結合, 實現了安全的 WebSocket 通訊, 即 WebSocket Secure.api
因此說 WSS 是使用 SSL 進行加密了的 WebSocket 通訊技術.安全
由於咱們的對戰答題功能對及時性要求比較高,傳統的http沒法知足要求,因此,咱們使用WebSocket做爲客戶端與服務端,客戶端與客戶端之間的通信。服務器
要使用wss首先咱們須要購買個域名證書,如今騰訊雲和阿里雲都有提供免費的域名證書。下面我已騰訊云爲例,簡單介紹下域名證書的購買。微信
進入騰訊雲官網,在頂部導航中,找到雲產品。以下圖所示:網絡
點擊ssl證書管理。進入證書管理頁面,session
點擊購買證書,進入下級頁面,選擇免費版
進入下級頁面,填寫證書基本信息
下一步,驗證域名全部權。這裏我選擇手動DNS驗證。點擊確認申請。
點擊查看證書詳情,而後根據騰訊雲提供的DNS信息,添加域名解析。
下圖是域名解析填寫的信息(前提是你要有個域名):
而後,回到騰訊雲,點擊以下圖中的查詢按鈕:
當出現以下圖所示的信息後,稍後幾秒後,便可證書便可申請成功。
申請成功後,刷新證書詳情頁面,以下圖,點擊下載按鈕,將證書下載下來。
將下載後的文件解壓備用。
下面進入WebSocket服務端代碼實現階段。這裏我使用.net平臺開源的Socket框架SuperSocket。首先打開vs,新建一個控制檯應用程序項目。
這裏咱們經過Nuget的方式引用SuperSocket。以下圖所示:
分別安裝上圖中標註的包。安裝完成後,而後將以前下載的證書拷貝過來。使用以前解壓的文件裏的iis文件夾裏的證書文件。將iis文件夾中的證書文件拷貝到項目。而後右擊證書文件,選擇屬性,進入屬性設置頁面。
在複製到輸出目錄中,選擇始終複製。
控制檯中的代碼:
1 static void Main(string[] args) 2 3 { 4 5 #region 證書配置 6 7 var certConfig = new CertificateConfig(); 8 9 certConfig.FilePath = "ttt.vqicard.com.pfx";//證書路徑 10 11 certConfig.Password = "123123";//證書密碼。申請證書時填寫的密碼。沒填,則此處爲空 12 13 certConfig.KeyStorageFlags = X509KeyStorageFlags.UserKeySet; 14 15 certConfig.ClientCertificateRequired = false; 16 17 #endregion 18 19 var ws = new WebSocketServer(); 20 21 var serverConfig = new ServerConfig(); 22 23 serverConfig.Security = "tls"; 24 25 serverConfig.Certificate = certConfig; 26 27 serverConfig.Ip = IPAddress.Any.ToString();//綁定的ip 28 29 serverConfig.Port = 2018;//監聽的端口號。此處填寫默認端口。因爲個人服務器的443端口已經被佔用, 30 31 //因此,這裏使用其餘端口。由於微信不支持帶端口的地址,因此,正式部署後,必須設置爲443端口。 32 33 ws.NewDataReceived += Ws_NewDataReceived;//接收到新數據的回調 34 35 ws.NewMessageReceived += Ws_NewMessageReceived;//接收到新字符串的回調 36 37 ws.SessionClosed += Ws_SessionClosed;//回話關閉的回調 38 39 ws.NewSessionConnected += Ws_NewSessionConnected;//新用戶接入的回調 40 41 if (ws.Setup(serverConfig)) 42 43 { 44 45 ws.Start(); 46 47 Console.WriteLine("監聽開始"); 48 49 Console.ReadKey(); 50 51 } 52 53 } 54 55 private static void Ws_NewSessionConnected(WebSocketSession session) 56 57 { 58 59 //接收到新鏈接後,回覆消息給客戶端 60 61 session.Send("hello"); 62 63 } 64 65 private static void Ws_SessionClosed(WebSocketSession session, SuperSocket.SocketBase.CloseReason value) 66 67 { 68 69 } 70 71 private static void Ws_NewMessageReceived(WebSocketSession session, string value) 72 73 { 74 75 } 76 77 private static void Ws_NewDataReceived(WebSocketSession session, byte[] value) 78 79 { 80 81 } 82 83 }
代碼編寫完成後,運行。而後編寫小程序端鏈接WebSocket的代碼。
使用wx.connectSocket接口放回一個SocketTask對象。代碼以下:
1 let task = wx.connectSocket({ 2 3 url: 'wss://ttt.vqicard.com:2018', 4 5 success: function (res) { 6 7 console.log(res) 8 9 } 10 11 })
而後SocketTask.onOpen監聽鏈接打開事件。
SocketTask.onClose監聽鏈接關閉事件。
SocketTask.onMessage(CALLBACK)
監聽接收到服務器消息的事件。
經過SocketTask.send方法能夠向服務器發送數據。
實例代碼以下:
1 task.onOpen(res => { 2 3 console.log('鏈接服務器成功') 4 5 }) 6 7 task.onMessage(res => { 8 9 console.log(res) 10 11 })
wss的基本配置到這裏就完成了。
微信小程序登陸時序分析
下圖是微信官方提供的小程序的登陸邏輯:
從上圖咱們能夠大概分析出用戶的小程序端用戶受權登陸的流程與邏輯。
1.小程序端,調用wx.login()獲取code。
2.使用wx.request()將code發送給開發者服務器。
3.開發者服務器使用appid,appsecret,code調用微信提供的接口,獲取當前用戶的session_key以及openid,這裏的session_key是微信服務器生成的針對用戶數據加密簽名的密鑰。
4.開發者服務器使用指定的算法生成足夠安全的第三方session。目的是保證session_key的安全性。因此,生成的第三方session應該知足以下條件:長度足夠長,避免使用時間戳做爲隨機參數,設置必定的有效時間,過時即視爲不合法。
5.以3rd_session爲key,session_key+openid爲value,寫入session存儲。目的是,能夠經過3rd_session獲取到真實的session_key。
6.將3rd_session返回到小程序端,在小程序端,使用storage存儲到本地。
7.後續使用時,先判斷3rd_session是否存在,若是不存在則從新從第一步開始。
以上爲受權的基本流程,實際操做中,可能會比以上分析的麻煩一點,由於可能會涉及到用戶不一樣意受權。或者之前點過不一樣意,如今又想點贊成的狀況。因此,具體的操做,仍是經過代碼來理解的比較透徹。
代碼中,有兩個地方是須要給服務器交互的,一個是驗證本地存儲的session是否合法,另外一個是經過code換取第三方session。一般狀況下,是使用https的方式與服務器交互,相關的代碼在示例中我也寫到。但這個答題項目主要是使用wss的方式與服務端通信,因此,爲了方便代碼的管理,檢測session和換取session的操做我都是用wss的方式,具體的看代碼。下面是小程序的代碼,註釋已經很清楚了,我就不一一解釋了。
1 var wsTask 2 3 //app.js 4 5 App({ 6 7 onLaunch: function () { 8 9 wsTask = wx.connectSocket({ 10 11 url: 'ws://192.168.0.253:2018' 12 13 }) 14 15 this.wsTask = wsTask 16 17 wsTask.onOpen(()=>{ 18 19 console.log('鏈接服務器成功') 20 21 }) 22 23 wsTask.onMessage(msg=>{ 24 25 var res = JSON.parse(msg.data) 26 27 switch(res.option){ 28 29 case 'checkSession': 30 31 if(!res.status){ 32 33 this.login() 34 35 }else{ 36 37 console.log('登陸成功') 38 39 } 40 41 break 42 43 case 'login': 44 45 if(res.status){ 46 47 wx.setStorage({ 48 49 key: '3rd_session', 50 51 data: res.session 52 53 }) 54 55 console.log('登陸成功') 56 57 } 58 59 break 60 61 } 62 63 }) 64 65 }, 66 67 checkSession: function () { 68 69 //首先檢測登陸狀態是否失效 70 71 wx.checkSession({ 72 73 complete: cr => { 74 75 if (cr.errMsg == 'checkSession:ok') { 76 77 //受權狀態有效,需判斷3rd_session是否存在 78 79 let rd_session = wx.getStorageSync('3rd_session') 80 81 if (rd_session) { 82 83 //第三方session存在 84 85 wsTask.send({ 86 87 data: JSON.stringify({ option:'checkSession',session:rd_session}) 88 89 }) 90 91 return 92 93 //將第三方session發送到服務器,驗證合法性已是否有效 94 95 wx.request({ 96 97 url: 'checkSessionUrl', 98 99 success: function (res) { 100 101 if (res.status) { 102 103 //根據服務端返回的驗證結果進行判斷,若是status爲1,則表示3rdsession合法,且在有效期內。 104 105 } else { 106 107 //session無效 108 109 this.login() 110 111 } 112 113 }, 114 115 fail: function (e) { 116 117 console.error(e);//打印錯誤信息 118 119 } 120 121 }) 122 123 } else { 124 125 //session不存在,則需從新進入受權流程 126 127 this.login() 128 129 } 130 131 } else { 132 133 //受權狀態失效,則需從新進入受權流程 134 135 this.login() 136 137 } 138 139 } 140 141 }) 142 143 }, 144 145 login: function () { 146 147 //檢查用戶是否已贊成受權 148 149 wx.authorize({ 150 151 scope: 'scope.userInfo', 152 153 complete: res => { 154 155 //不容許受權 156 157 if (res.errMsg != 'authorize:ok') { 158 159 //則獲取用戶的受權設置 160 161 wx.getSetting({ 162 163 success: r => { 164 165 //未開啓受權 166 167 if (!r.authSetting['scope.userInfo']) { 168 169 //詢問是否開啓受權 170 171 wx.showModal({ 172 173 title: '登陸', 174 175 content: '小程序須要使用您的受權信息,是否繼續?', 176 177 success: res => { 178 179 console.log(res) 180 181 if (res.confirm) { 182 183 //贊成開啓受權,則跳轉到設置頁面,由用戶打開受權。用戶打開受權後,由用戶操做,返回小程序,此時能夠再onShow方法中再次調用login方法。 184 185 wx.openSetting() 186 187 } 188 189 } 190 191 }) 192 193 } 194 195 } 196 197 }) 198 199 } else { 200 201 //表示已受權,此時,能夠調用登陸接口 202 203 wx.login({ 204 205 success: res => { 206 207 if(res.errMsg=='login:ok'){ 208 209 wsTask.send({data:JSON.stringify({option:'login',code:res.code})}) 210 211 return 212 213 wx.request({ 214 215 url: 'loginUrl', 216 217 data:{code:res.code}, 218 219 success:rq=>{ 220 221 //將此處返回的3rdsession保存在storage中,整個受權流程結束 222 223 } 224 225 }) 226 227 } 228 229 } 230 231 }) 232 233 } 234 235 } 236 237 }) 238 239 }, 240 241 onShow: function () { 242 243 this.checkSession() 244 245 }, 246 247 globalData: { 248 249 userInfo: null 250 251 } 252 253 })
服務端的代碼以下:
1 private static void Ws_NewMessageReceived(WebSocketSession session, string value) 2 3 { 4 5 var jobj = JsonConvert.DeserializeObject<JObject>(value); 6 7 var option = jobj.Value<string>("option"); 8 9 switch (option) 10 11 { 12 13 case "checkSession": 14 15 var rdsession = jobj.Value<string>("session"); 16 17 var model = wxUserlist.FirstOrDefault(f => f.MyKey == rdsession); 18 19 session.Send(JsonConvert.SerializeObject(new { option = option, status =model!=null?1:0})); 20 21 break; 22 23 case "login": 24 25 var code = jobj.Value<string>("code"); 26 27 var res = LoginApi.CodeToMySession("你的appid", "你的appsecret", code); 28 29 wxUserlist.Add(res); 30 31 session.Send(JsonConvert.SerializeObject(new { status=1,session=res.MyKey,option=option})); 32 33 break; 34 35 } 36 37 } 38 39
如需源碼,請掃描二維碼,關注微信公衆號。回覆:對戰二