基於騰訊雲的微信小程序搭建

準備域名和證書

  • 域名註冊0
    若是您尚未域名,能夠 在騰訊雲上選購,過程能夠參考下面的視頻。
     
    域名解析0
    域名購買完成後, 須要將域名解析到實驗雲主機上,實驗雲主機的 IP 爲:
    139.199.199.225
    在騰訊雲購買的域名,能夠 到控制檯添加解析記錄,過程可參考下面的視頻:
    域名設置解析後須要過一段時間纔會生效,經過  ping 命令檢查域名是否生效 
     
    ,如:
    ping www.yourmpdomain.com
    若是 ping 命令返回的信息中含有你設置的解析的 IP 地址,說明解析成功。
     
    申請 SSL 證書0
    騰訊雲提供了 SSL 證書的 免費申請,申請方式可參考下面視頻:
    申請提交後,審批結果會以短信的形式通知。審批經過後,能夠到  SSL 控制檯下載您的證書文件,可參考下面的視頻:

     

    搭建小程序開發環境

    • 註冊開發者帳號0
      若是你還不是小程序開發者,請先在微信公衆平臺並註冊:
      https://mp.weixin.qq.com
      具體註冊流程可參考以下視頻:
      若您已註冊,請點擊下一步。
    • 配置小程序服務器信息0
      登陸微信公衆平臺後,依次進入  設置 -  開發設置 -  服務器域名 -  修改
      掃碼完成身份校驗後,request 合法域名和 socket 合法域名均填寫在上一步準備好的域名地址。
      配置完成後,點擊  保存並提交。您能夠點擊以下視頻查看如何進行配置:
    • 運行配套小程序代碼0
      要運行本實驗配套的小程序代碼,請下載下列資源:
      源碼下載後,請解壓到本地工做目錄。
      開發工具下載後,請安裝並啓動,而後用微信掃碼登陸。
      登陸後,選擇  本地小程序項目 -  添加項目,使用如下配置:
      • AppID:填寫小程序的 AppID,請登陸公衆平臺後在 設置開發設置 - 開發者 ID 中查看
      • 項目名稱:填寫任意您喜歡的名稱
      • 項目目錄:選擇剛纔解壓的配套源碼目錄(目錄包含 app.js
      填寫完成後,點擊  添加項目。具體操做可查看以下視頻:
    • 設置實驗域名0
      在開發工具的  編輯 面板中,選中  app.js 進行編輯,須要修改小程序通訊域名
       
      ,請參考下面的配置:
      App({
          config: {
              host: '' // 這個地方填寫你的域名
          },
          onLaunch () {
              console.log('App.onLaunch()');
          }
      });
      固然,這步操做也錄製了對應的視頻:

       

       

      搭建 HTTP 服務

      • 安裝 NodeJS 和 NPM0
        使用下面的命令安裝 NodeJS 和 NPM
        curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash -
        yum install nodejs -y
        安裝完成後,使用下面的命令測試安裝結果
        node -v
      • 編寫 HTTP Server 源碼0

        建立工做目錄

        使用下面的命令在服務器建立一個工做目錄:
        mkdir -p /data/release/weapp
        進入此工做目錄
        cd /data/release/weapp

        建立 package.json

        在剛纔建立的工做目錄 建立 package.json,添加咱們服務器包的名稱和版本號,可參考下面的示例。
        完成後,使用  Ctrl + S 保存文件

        添加 Server 源碼

        在工做目錄 建立 app.js,使用 Express.js 來監聽  8765 端口
         
        ,可參考下面的示例代碼。
      • 運行 HTTP 服務0

        安裝 PM2

        在開始以前,咱們先來安裝 
        PM2
         
        npm install pm2 --global
        PM2 安裝時間可能稍長,請耐心等候 
         

        安裝 Express

        咱們的服務器源碼裏使用到了 Express 模塊,下面的命令使用 NPM 來安裝 Express
        cd /data/release/weapp
        npm install express --save

        啓動服務

        安裝完成後,使用 PM2 來啓動 HTTP 服務
        cd /data/release/weapp
        pm2 start app.js
        如今,您的 HTTP 服務已經在  http://139.199.199.225:8765運行
        要查看服務輸出的日誌,可使用下面的命令:
        pm2 logs
        若是要重啓服務,可使用下面的命令:
        pm2 restart app


        搭建 HTTPS 服務

        • 安裝 Nginx0
          在 CentOS 上,可直接使用  yum 來安裝 Nginx
          yum install nginx -y
          安裝完成後,使用  nginx 命令啓動 Nginx:
          nginx
          此時,訪問  http://139.199.199.225 能夠看到 Nginx 的測試頁面 
           
        • 配置 HTTPS 反向代理0
          外網用戶訪問服務器的 Web 服務由 Nginx 提供,Nginx 須要配置反向代理才能使得 Web 服務轉發到本地的 Node 服務。
          先將以前下載的 SSL 證書(解壓後 Nginx 目錄分別以 crt 和 key 做爲後綴的文件)經過 拖動到左側文件
          瀏覽器/etc/nginx目錄的方式來上傳文件到服務器上
          Nginx 配置目錄在  /etc/nginx/conf.d,咱們在該目錄建立  ssl.conf
          按  Ctrl + S 保存配置文件,讓 Nginx 從新加載配置使其生效:
          nginx -s reload
          在瀏覽器經過 https 的方式訪問你解析的域名來測試 HTTPS 是否成功啓動
        • 在小程序中測試 HTTPS 訪問0
          打開配套的小程序,點擊  實驗一:HTTPS,點擊  發送請求 來測試訪問結果。
          若是服務器響應成功,請點擊下一步。
           
           
           

          小程序會話

          • 安裝 MongoDB0
            使用 Yum 在機器上安裝 
            MongoDB
             
             及其客戶端命令行工具:
            yum install mongodb-server mongodb -y
            安裝結束後,可使用下面的命令查看安裝的版本:
            mongod --version
            mongo --version
          • 啓動 MongoDB0
            建立目錄,用於 MongoDB 數據和日誌存儲:
            mkdir -p /data/mongodb
            mkdir -p /data/logs/mongodb
            建立後,使用下面的命令來啓動 MongoDB:
             
            mongod --fork --dbpath /data/mongodb --logpath /data/logs/mongodb/weapp.log
            可使用下面的命令來檢查是否啓動成功 
             
            netstat -ltp | grep 27017
          • 添加 MongoDB 用戶0
            登陸本地 MongoDB 服務:
            mongo
            登陸後,建立一個用戶  weapp 
             
            use weapp;
            db.createUser({ user: 'weapp', pwd: 'weapp-dev', roles: ['dbAdmin', 'readWrite']});
            建立完成後,使用  exit 退出命令行工具。
          • 安裝 Node 模塊0
            實現小程序的會話功能,咱們須要安裝 
            connect-mongo
             
             和 
            wafer-node-session
             
            cd /data/release/weapp
            npm install connect-mongo wafer-node-session --save
          • 實現小程序會話0
            在工做目錄 建立配置文件 config.js,用於保存咱們服務所用的配置
             
            ,可參考下面的實現(注:請將參考配置文件中的 YORU_APP_ID 和 YOUR_APP_SECRET
            替換爲你申請的小程序對應的 AppID 和 AppSecret):
             
             

            module.exports = {
            serverPort: '8765', html

            // 小程序 appId 和 appSecret
            // 請到 https://mp.weixin.qq.com 獲取 AppID 和 AppSecret
            appId: 'YORU_APP_ID',
            appSecret: 'YOUR_APP_SECRET', node

            // mongodb 鏈接配置,生產環境請使用更復雜的用戶名密碼
            mongoHost: '127.0.0.1',
            mongoPort: '27017',
            mongoUser: 'weapp',
            mongoPass: 'weapp-dev',
            mongoDb: 'weapp'
            };nginx

             

            編輯 app.js,添加會話實現邏輯,可參考下面的代碼:
             

            // 引用 express 來支持 HTTP Server 的實現
            const express = require('express');
            // 引用 wafer-session 支持小程序會話
            const waferSession = require('wafer-node-session');
            // 使用 MongoDB 做爲會話的存儲
            const MongoStore = require('connect-mongo')(waferSession);
            // 引入配置文件
            const config = require('./config'); web

            // 建立一個 express 實例
            const app = express();mongodb

            // 添加會話中間件,登陸地址是 /login
            app.use(waferSession({
            appId: config.appId,
            appSecret: config.appSecret,
            loginPath: '/login',
            store: new MongoStore({
            url: `mongodb://${config.mongoUser}:${config.mongoPass}@${config.mongoHost}:${config.mongoPort}/${config.mongoDb}`
            })
            })); express

            // 在路由 /me 下,輸出會話裏包含的用戶信息
            app.use('/me', (request, response, next) => {
            response.json(request.session ? request.session.userInfo : { noBody: true });
            if (request.session) {
            console.log(`Wafer session success with openId=${request.session.userInfo.openId}`);
            }
            }); npm

            // 實現一箇中間件,對於未處理的請求,都輸出 "Response from express"
            app.use((request, response, next) => {
            response.write('Response from express');
            response.end();
            });json

            // 監聽端口,等待鏈接
            app.listen(config.serverPort);小程序

            // 輸出服務器啓動日誌
            console.log(`Server listening at http://127.0.0.1:${config.serverPort}`);微信小程序

             

            源碼編寫完成後,重啓服務:
            pm2 restart app
            重啓後,使用配套的小程序完成會話測試:打開配套小程序 - 點擊  實驗二:會話 -  獲取會話
            若是您能看到您的微信頭像,那就表示會話已經成功獲取了。
             

            WebSocket 服務

            • 安裝 Node 模塊0
              本實驗使用  ws 模塊來在服務器上支持 WebSocket 協議,下面使用 NPM 來安裝:
              cd /data/release/weapp
              npm install ws --save
            • 實現 WebSocket 服務器0
              建立 websocket.js,實現 WebSocket 服務,可參考下面的代碼:
               

              // 引入 ws 支持 WebSocket 的實現
              const ws = require('ws');

              // 導出處理方法
              exports.listen = listen;

              /**
              * 在 HTTP Server 上處理 WebSocket 請求
              * @param {http.Server} server
              * @param {wafer.SessionMiddleware} sessionMiddleware
              */
              function listen(server, sessionMiddleware) {
              // 使用 HTTP Server 建立 WebSocket 服務,使用 path 參數指定須要升級爲 WebSocket 的路徑
              const wss = new ws.Server({ server, path: '/ws' });

              // 監聽 WebSocket 鏈接創建
              wss.on('connection', (ws,request) => {// 要升級到 WebSocket 協議的 HTTP 鏈接

              // 被升級到 WebSocket 的請求不會被 express 處理,
              // 須要使用會話中間節獲取會話
              sessionMiddleware(request, null, () => {
              const session = request.session;
              if (!session) {
              // 沒有獲取到會話,強制斷開 WebSocket 鏈接
              ws.send(JSON.stringify(request.sessionError) || "No session avaliable");
              ws.close();
              return;
              }
              // 保留這個日誌的輸出可以讓實驗室能檢查到當前步驟是否完成
              console.log(`WebSocket client connected with openId=${session.userInfo.openId}`);
              serveMessage(ws, session.userInfo);
              });
              });

              // 監聽 WebSocket 服務的錯誤
              wss.on('error', (err) => {
              console.log(err);
              });
              }

              /**
              * 進行簡單的 WebSocket 服務,對於客戶端發來的全部消息都回復回去
              */
              function serveMessage(ws, userInfo) {
              // 監聽客戶端發來的消息
              ws.on('message', (message) => {
              console.log(`WebSocket received: ${message}`);
              ws.send(`Server: Received(${message})`);
              });

              // 監聽關閉事件
              ws.on('close', (code, message) => {
              console.log(`WebSocket client closed (code: ${code}, message: ${message || 'none'})`);
              });

              // 鏈接後立刻發送 hello 消息給會話對應的用戶
              ws.send(`Server: 恭喜,${userInfo.nickName}`);
              }

               

               
              編輯 app.js,調用 WebSocket 服務,可參考下面代碼:
               

              // HTTP 模塊同時支持 Express 和 WebSocket
              const http = require('http');
              // 引用 express 來支持 HTTP Server 的實現
              const express = require('express');
              // 引用 wafer-session 支持小程序會話
              const waferSession = require('wafer-node-session');
              // 使用 MongoDB 做爲會話的存儲
              const MongoStore = require('connect-mongo')(waferSession);
              // 引入配置文件
              const config = require('./config');
              // 引入 WebSocket 服務實現
              const websocket = require('./websocket');

              // 建立一個 express 實例
              const app = express();

              // 獨立出會話中間件給 express 和 ws 使用
              const sessionMiddleware = waferSession({
              appId: config.appId,
              appSecret: config.appSecret,
              loginPath: '/login',
              store: new MongoStore({
              url: `mongodb://${config.mongoUser}:${config.mongoPass}@${config.mongoHost}:${config.mongoPort}/${config.mongoDb}`
              })
              });
              app.use(sessionMiddleware);

              // 在路由 /me 下,輸出會話裏包含的用戶信息
              app.use('/me', (request, response, next) => {
              response.json(request.session ? request.session.userInfo : { noBody: true });
              if (request.session) {
              console.log(`Wafer session success with openId=${request.session.userInfo.openId}`);
              }
              });

              // 實現一箇中間件,對於未處理的請求,都輸出 "Response from express"
              app.use((request, response, next) => {
              response.write('Response from express');
              response.end();
              });

              // 建立 HTTP Server 而不是直接使用 express 監聽
              const server = http.createServer(app);

              // 讓 WebSocket 服務在建立的 HTTP 服務器上監聽
              websocket.listen(server, sessionMiddleware);

              // 啓動 HTTP 服務
              server.listen(config.serverPort);

              // 輸出服務器啓動日誌
              console.log(`Server listening at http://127.0.0.1:${config.serverPort}`);

               

               
              修改完成後,按  Ctrl + S 保存文件,並重啓服務:
              pm2 restart app
            • 更新 Nginx 代理0
              編輯 Nginx 配置 ssl.conf,添加 WebSocket 支持,可參考下面的配置(注:請將
              參考配置文件中的 www.example.com 替換爲前面步驟申請的域名,將 1_www.example.com.crt
              和 2_www.example.com.key 替換爲前面步驟申請並上傳的 SSL 證書的名稱):
               

              # WebSocket 配置
              map $http_upgrade $connection_upgrade {
              default upgrade;
              '' close;
              }

              server {
              listen 443;
              server_name www.example.com; # 改成綁定證書的域名
              # ssl 配置
              ssl on;
              ssl_certificate 1_www.example.com.crt; # 改成本身申請獲得的 crt 文件的名稱
              ssl_certificate_key 2_www.example.com.key; # 改成本身申請獲得的 key 文件的名稱
              ssl_session_timeout 5m;
              ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
              ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
              ssl_prefer_server_ciphers on;

              # WebSocket 配置
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection $connection_upgrade;

              location / {
              proxy_pass http://127.0.0.1:8765;
              }
              }

               

               
              配置完成後,按  Ctrl + S 保存,而且通知 Nginx 進程從新加載配置:
              nginx -s reload
            • 測試 WebSocket0
              打開配套的小程序,點擊  實驗三:WebSocket。進入測試頁面後,點擊  鏈接 按鈕,
              若是出現鏈接成功的提示,表示 WebSocket 服務已經正常運行,能夠收發消息。
               
               

              剪刀石頭布小遊戲

              • 實現遊戲房間邏輯0
                建立 /data/release/weapp/game 目錄用於存放剪刀石頭布小遊戲的代碼
                mkdir -p /data/release/weapp/game
                添加  game/Room.js 實現遊戲房間邏輯
                 
                ,可參考下面的代碼:
                 


                /**
                enum GameChoice {
                // 剪刀
                Scissors = 1,
                // 石頭
                Rock = 2,
                // 布
                Paper = 3
                }
                */
                function judge(choice1, choice2) {
                // 和局
                if (choice1 == choice2) return 0;
                // Player 1 沒出,Player 2 勝出
                if (!choice1) return 1;
                // Player 2 沒出,Player 1 勝出
                if (!choice2) return -1;
                // 都出了就這麼算
                return (choice1 - choice2 + 3) % 3 == 1 ? -1 : 1;
                }

                /** @type {Room[]} */
                const globalRoomList = [];

                // 每一個房間最多兩人
                const MAX_ROOT_MEMBER = 2;

                // 遊戲時間,單位秒
                const GAME_TIME = 3;

                let nextRoomId = 0;

                /** 表示一個房間 */
                module.exports = class Room {

                /** 獲取全部房間 */
                static all() {
                return globalRoomList.slice();
                }

                /** 獲取有座位的房間 */
                static findRoomWithSeat() {
                return globalRoomList.find(x => !x.isFull());
                }

                /** 建立新房間 */
                static create() {
                const room = new Room();
                globalRoomList.unshift(room);
                return room;
                }

                constructor() {
                this.id = `room${nextRoomId++}`;
                this.players = [];
                }

                /** 添加玩家 */
                addPlayer(player) {
                const { uid, uname } = player.user;
                console.log(`Player ${uid}(${uname}) enter ${this.id}`);
                this.players.push(player);
                if (this.isFull()) {
                this.startGame();
                }
                }

                /** 刪除玩家 */
                removePlayer(player) {
                const { uid, uname } = player.user;
                console.log(`Player ${uid}(${uname}) leave ${this.id}`);
                const playerIndex = this.players.indexOf(player);
                if (playerIndex != -1) {
                this.players.splice(playerIndex, 1);
                }
                if (this.players.length === 0) {
                console.log(`Room ${this.id} is empty now`);
                const roomIndex = globalRoomList.indexOf(this);
                if (roomIndex > -1) {
                globalRoomList.splice(roomIndex, 1);
                }
                }
                }

                /** 玩家已滿 */
                isFull() {
                return this.players.length == MAX_ROOT_MEMBER;
                }

                /** 開始遊戲 */
                startGame() {
                // 保留這行日誌輸出可讓實驗室檢查到實驗的完成狀況
                console.log('game started!');

                // 當局積分清零
                this.players.forEach(player => player.gameData.roundScore = 0);

                // 集合玩家用戶和遊戲數據
                const players = this.players.map(player => Object.assign({}, player.user, player.gameData));

                // 通知全部玩家開始
                for (let player of this.players) {
                player.send('start', {
                gameTime: GAME_TIME,
                players
                });
                }

                // 計時結束
                setTimeout(() => this.finishGame(), GAME_TIME * 1000);
                }

                /** 結束遊戲 */
                finishGame() {
                const players = this.players;

                // 兩兩對比算分
                for (let i = 0; i < MAX_ROOT_MEMBER; i++) {
                let p1 = players[i];
                if (!p1) break;
                for (let j = i + 1; j < MAX_ROOT_MEMBER; j++) {
                let p2 = players[j];
                const result = judge(p1.gameData.choice, p2.gameData.choice);
                p1.gameData.roundScore -= result;
                p2.gameData.roundScore += result;
                }
                }
                // 計算連勝獎勵
                for (let player of players) {
                const gameData = player.gameData;
                // 勝局積分
                if (gameData.roundScore > 0) {
                gameData.winStreak++;
                gameData.roundScore *= gameData.winStreak;
                }
                // 敗局清零
                else if (gameData.roundScore < 0) {
                gameData.roundScore = 0;
                gameData.winStreak = 0;
                }
                // 累積總分
                gameData.totalScore += gameData.roundScore;
                }
                // 計算結果
                const result = players.map(player => {
                const { uid } = player.user;
                const { roundScore, totalScore, winStreak, choice } = player.gameData;
                return { uid, roundScore, totalScore, winStreak, choice };
                });
                // 通知全部玩家遊戲結果
                for (let player of players) {
                player.send('result', { result });
                }
                }
                }

                 

                 
              • 實現玩家邏輯0
                添加  game/Player.js 實現玩家邏輯
                 
                ,可參考下面的代碼:
                 

                const Room = require("./Room");

                /**
                * 表示一個玩家,處理玩家的公共遊戲邏輯,消息處理部分須要具體的玩家實現(請參考 ComputerPlayer 和 HumanPlayer)
                */
                module.exports = class Player {
                constructor(user) {
                this.id = user.uid;
                this.user = user;
                this.room = null;
                this.gameData = {
                // 當前的選擇(剪刀/石頭/布)
                choice: null,
                // 局積分
                roundScore: 0,
                // 總積分
                totalScore: 0,
                // 連勝次數
                winStreak: 0
                };
                }

                /**
                * 上線當前玩家,而且異步返回給玩家分配的房間
                */
                online(room) {
                // 處理玩家 'join' 消息
                // 爲玩家尋找一個可用的房間,而且異步返回
                this.receive('join', () => {
                if (this.room) {
                this.room.removePlayer(this);
                }
                room = this.room = room || Room.findRoomWithSeat() || Room.create();
                room.addPlayer(this);
                });

                // 處理玩家 'choise' 消息
                // 須要記錄玩家當前的選擇,而且通知到房間裏的其它玩家
                this.receive('choice', ({ choice }) => {
                this.gameData.choice = choice;
                this.broadcast('movement', {
                uid: this.user.uid,
                movement: "choice"
                });
                });

                // 處理玩家 'leave' 消息
                // 讓玩家下線
                this.receive('leave', () => this.offline);
                }

                /**
                * 下線當前玩家,從房間離開
                */
                offline() {
                if (this.room) {
                this.room.removePlayer(this);
                this.room = null;
                }
                this.user = null;
                this.gameData = null;
                }

                /**
                * 發送指定消息給當前玩家,須要具體子類實現
                * @abstract
                * @param {string} message 消息類型
                * @param {*} data 消息數據
                */
                send(message, data) {
                throw new Error('Not implement: AbstractPlayer.send()');
                }

                /**
                * 處理玩家發送的消息,須要具體子類實現
                * @abstract
                * @param {string} message 消息類型
                * @param {Function} handler
                */
                receive(message, handler) {
                throw new Error('Not implement: AbstractPlayer.receive()');
                }

                /**
                * 給玩家所在房間裏的其它玩家發送消息
                * @param {string} message 消息類型
                * @param {any} data 消息數據
                */
                broadcast(message, data) {
                if (!this.room) return;
                this.others().forEach(neighbor => neighbor.send(message, data));
                }

                /**
                * 得到玩家所在房間裏的其餘玩家
                */
                others() {
                return this.room.players.filter(player => player != this);
                }
                }

                 
              • 實現電腦玩家0
                在實現人類玩家以前,咱們先來建立  ComputerPlayer.js 來實現電腦玩家
                 
                 

                const EventEmitter = require('events');
                const Player = require('./Player');

                let nextComputerId = 0;

                /**
                * 機器人玩家實現,使用 EventEmitter 接收和發送消息
                */
                module.exports = class ComputerPlayer extends Player {
                constructor() {
                const computerId = `robot-${++nextComputerId}`;
                super({
                uid: computerId,
                uname: computerId,
                uavatar: 'http://www.scoutiegirl.com/wp-content/uploads/2015/06/Blue-Robot.png'
                });
                this.emitter = new EventEmitter();
                }

                /**
                * 模擬玩家行爲
                */
                simulate() {
                this.receive('start', () => this.play());
                this.receive('result', () => this.stop());
                this.send('join');
                }

                /**
                * 遊戲開始後,隨機時間後隨機選擇
                */
                play() {
                this.playing = true;
                const randomTime = () => Math.floor(100 + Math.random() * 2000);
                const randomChoice = () => {
                if (!this.playing) return;
                this.send("choice", {
                choice: Math.floor(Math.random() * 10000) % 3 + 1
                });
                setTimeout(randomChoice, randomTime());
                }
                setTimeout(randomChoice, 10);
                }

                /**
                * 遊戲結束後,標記起來,阻止繼續隨機選擇
                */
                stop() {
                this.playing = false;
                }

                /**
                * 發送消息給當前玩家,直接轉發到 emitter
                */
                send(message, data) {
                this.emitter.emit(message, data);
                }

                /**
                * 從當前的 emitter 處理消息
                */
                receive(message, handle) {
                this.emitter.on(message, handle);
                }
                }

                 
              • 實現人類玩家0
                人類玩家經過 WebSocket 信道來實現玩家的輸入輸出
                 
                ,咱們須要添加  game/Tunnel.js 和  game/HumanPlayer.js 來實現人類玩家邏輯,可參考下面的代碼:
                 

                const EventEmitter = require('events');

                /**
                * 封裝 WebSocket 信道
                */
                module.exports = class Tunnel {
                constructor(ws) {
                this.emitter = new EventEmitter();
                this.ws = ws;
                ws.on('message', packet => {
                try {
                // 約定每一個數據包格式:{ message: 'type', data: any }
                const { message, data } = JSON.parse(packet);
                this.emitter.emit(message, data);
                } catch (err) {
                console.log('unknown packet: ' + packet);
                }
                });
                }

                on(message, handle) {
                this.emitter.on(message, handle);
                }

                emit(message, data) {
                this.ws.send(JSON.stringify({ message, data }));
                }
                }

                 

                 

                const co = require('co');
                const Player = require('./Player');
                const ComputerPlayer = require('./ComputerPlayer');
                const Tunnel = require('./Tunnel');

                /**
                * 人類玩家實現,經過 WebSocket 信道接收和發送消息
                */
                module.exports = class HumanPlayer extends Player {
                constructor(user, ws) {
                super(user);
                this.ws = ws;
                this.tunnel = new Tunnel(ws);
                this.send('id', user);
                }

                /**
                * 人類玩家上線後,還須要監聽信道關閉,讓玩家下線
                */
                online(room) {
                super.online(room);
                this.ws.on('close', () => this.offline());

                // 人類玩家請求電腦玩家
                this.receive('requestComputer', () => {
                const room = this.room;
                while(room && !room.isFull()) {
                const computer = new ComputerPlayer();
                computer.online(room);
                computer.simulate();
                }
                });
                }

                /**
                * 下線後關閉信道
                */
                offline() {
                super.offline();
                if (this.ws && this.ws.readyState == this.ws.OPEN) {
                this.ws.close();
                }
                this.ws = null;
                this.tunnel = null;
                if (this.room) {
                // 清理房間裏面的電腦玩家
                for (let player of this.room.players) {
                if (player instanceof ComputerPlayer) {
                this.room.removePlayer(player);
                }
                }
                this.room = null;
                }
                }

                /**
                * 經過 WebSocket 信道發送消息給玩家
                */
                send(message, data) {
                this.tunnel.emit(message, data);
                }

                /**
                * 從 WebSocket 信道接收玩家的消息
                */
                receive(message, callback) {
                this.tunnel.on(message, callback);
                }
                }

                 
              • 添加遊戲服務入口0
                遊戲的實現已經完成了,接下來,編輯  websocket.js 添加服務入口,可參考下面的代碼:
                 

                // 引入 url 模塊用於解析 URL
                const url = require('url');
                // 引入 ws 支持 WebSocket 的實現
                const ws = require('ws');
                // 引入人類玩家
                const HumanPlayer = require('./game/HumanPlayer');

                // 導出處理方法
                exports.listen = listen;

                /**
                * 在 HTTP Server 上處理 WebSocket 請求
                * @param {http.Server} server
                * @param {wafer.SessionMiddleware} sessionMiddleware
                */
                function listen(server, sessionMiddleware) {
                // 使用 HTTP Server 建立 WebSocket 服務,使用 path 參數指定須要升級爲 WebSocket 的路徑
                const wss = new ws.Server({ server });

                // 同時支持 /ws 和 /game 的 WebSocket 鏈接請求
                wss.shouldHandle = (request) => {
                const path = url.parse(request.url).pathname;
                request.path = path;
                return ['/ws', '/game'].indexOf(path) > -1;
                };

                // 監聽 WebSocket 鏈接創建
                wss.on('connection', (ws, request) => {
                // request: 要升級到 WebSocket 協議的 HTTP 鏈接

                // 被升級到 WebSocket 的請求不會被 express 處理,
                // 須要使用會話中間節獲取會話
                sessionMiddleware(request, null, () => {
                const session = request.session;
                if (!session) {
                // 沒有獲取到會話,強制斷開 WebSocket 鏈接
                ws.send(JSON.stringify(request.sessionError) || "No session avaliable");
                ws.close();
                return;
                }
                console.log(`WebSocket client connected with openId=${session.userInfo.openId}`);

                // 根據請求的地址進行不一樣處理
                switch (request.path) {
                case '/ws': return serveMessage(ws, session.userInfo);
                case '/game': return serveGame(ws, session.userInfo);
                default: return ws.close();
                }
                });
                });

                // 監聽 WebSocket 服務的錯誤
                wss.on('error', (err) => {
                console.log(err);
                });
                }

                /**
                * 進行簡單的 WebSocket 服務,對於客戶端發來的全部消息都回復回去
                */
                function serveMessage(ws, userInfo) {
                // 監聽客戶端發來的消息
                ws.on('message', (message) => {
                console.log(`WebSocket received: ${message}`);
                ws.send(`Server: Received(${message})`);
                });

                // 監聽關閉事件
                ws.on('close', (code, message) => {
                console.log(`WebSocket client closed (code: ${code}, message: ${message || 'none'})`);
                });

                // 鏈接後立刻發送 hello 消息給會話對應的用戶
                ws.send(`Server: 恭喜,${userInfo.nickName}`);
                }

                /**
                * 使用 WebSocket 進行遊戲服務
                */
                function serveGame(ws, userInfo) {
                const user = {
                uid: userInfo.openId,
                uname: userInfo.nickName,
                uavatar: userInfo.avatarUrl
                };
                // 建立玩家
                const player = new HumanPlayer(user, ws);
                // 玩家上線
                player.online();
                }

                 
              • 安裝 co 模塊0
                咱們的源碼中使用到了 co 進行協程管理,啓動遊戲服務前,須要先安裝:
                cd /data/release/weapp
                npm install co --save
              • 測試遊戲服務0
                重啓 Node 服務:
                pm2 restart app
                打開配套的小程序,點擊  實驗四 - 剪刀石頭布小遊戲,點擊  開始 按鈕進行遊戲。
              • 完成實驗0
                恭喜!您已經完成了小程序服務的所有實驗內容!你能夠選擇保留已經運行的服務,繼續進行小程序的學習研究,建議留用機器。
相關文章
相關標籤/搜索