139.199.199.225
ping
命令檢查域名是否生效
,如:
ping www.yourmpdomain.com
設置
-
開發設置
-
服務器域名
-
修改
。
保存並提交
。您能夠點擊以下視頻查看如何進行配置:
本地小程序項目
-
添加項目
,使用如下配置:
設置
- 開發設置
- 開發者 ID
中查看app.js
)添加項目
。具體操做可查看以下視頻:
編輯
面板中,選中
app.js
進行編輯,須要修改小程序通訊域名
,請參考下面的配置:
App({ config: { host: '' // 這個地方填寫你的域名 }, onLaunch () { console.log('App.onLaunch()'); } });
curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash - yum install nodejs -y
node -v
mkdir -p /data/release/weapp
cd /data/release/weapp
Ctrl + S
保存文件
8765
端口
,可參考下面的示例代碼。
npm install pm2 --global
cd /data/release/weapp npm install express --save
cd /data/release/weapp pm2 start app.js
pm2 logs
pm2 restart app
yum
來安裝 Nginx
yum install nginx -y
nginx
命令啓動 Nginx:
nginx
拖動到左側文件
瀏覽器/etc/nginx目錄
的方式來上傳文件到服務器上
Ctrl + S
保存配置文件,讓 Nginx 從新加載配置使其生效:
nginx -s reload
實驗一:HTTPS
,點擊
發送請求
來測試訪問結果。
yum install mongodb-server mongodb -y
mongod --version mongo --version
mkdir -p /data/mongodb mkdir -p /data/logs/mongodb
mongod --fork --dbpath /data/mongodb --logpath /data/logs/mongodb/weapp.log
netstat -ltp | grep 27017
mongo
weapp
:
use weapp; db.createUser({ user: 'weapp', pwd: 'weapp-dev', roles: ['dbAdmin', 'readWrite']});
exit
退出命令行工具。
cd /data/release/weapp npm install connect-mongo wafer-node-session --save
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
// 引用 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
實驗二:會話
-
獲取會話
,
ws
模塊來在服務器上支持 WebSocket 協議,下面使用 NPM 來安裝:
cd /data/release/weapp npm install ws --save
// 引入 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}`);
}
// 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
# 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
實驗三:WebSocket
。進入測試頁面後,點擊
鏈接
按鈕,
mkdir -p /data/release/weapp/game
/**
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 });
}
}
}
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);
}
}
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);
}
}
// 引入 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();
}
cd /data/release/weapp npm install co --save
pm2 restart app
實驗四 - 剪刀石頭布小遊戲
,點擊
開始
按鈕進行遊戲。