目标:我这里要做的目标,就是当一个用户(id为100,name是xiaoming)在房间01(rid=01)买了一个商品(count=1),然后在mysql的商品表格里记录下一条数据,包括用户的id(id=100)和商品的owner(owner=name加上用户的房间号)。
一、安装环境
1、cocos creator 1.8.2 安装
2、pomelo 安装(包括node.js之类)按照官方教程即可,如果不想装vs2010这类占10G硬盘空间的东西,直接npm install --global --production windows-build-tools,需要大约一共2G左右硬盘。
3、在官网下载Mysql 8.0.11,安装,这里没什么可说的,至少把server和workbench装一下 。
注意:安装mysql 8.0 的时候碰到一个问题,直接用workbench连接会外部组件报错。细查一下是Authentication plugin 'caching_sha2_password'cannot be loaded。
解决方法:进入mysql 命令行窗口
这里root的密码改为111111,使用老版本的身份验证插件方式:
ALTER USER root@localhost IDENTIFIED WITH mysql_native_password BY ‘111111’;
4、为了调试方便,建议安装webstorm。
二、安装用到的案例程序和包
1、安装pomelo框架的经典聊天例子chatofpomelo:命令行直接git clone https://github.com/NetEase/chatofpomelo-websocket.git,然后cd chatofpomelo-websocket,执行npm-install.bat(我是win7系统)。
2、命令行进入到game-server目录,安装最新版的generic-pool,npm install generic-pool
3、配置cocos creator客户端的pomelo库可以参照这个贴子:http://forum.cocos.com/t/cocos-creator-pomelo/60036
三、 配置数据库
1、安装好mysql后,用workbench的root进入,创建一个新的schema,起名就叫pomelo吧。然后创建一个新的用户,权限给全,选择users and privileges,在下方有个add account按钮点一下,,用户名叫test,选项卡选administrative roles,都点上,选项卡再选schema privileges,点击add entry,把数据库pomelo的权限都给test。
2、新建个连接,就叫叫testConnection吧,然后edit一下这个连接,用户就用刚刚创建的test。
3、用新连接testConnection进入mysql,在名为pomelo的数据库(就是schema)下创建一个table,叫goods。给这个表建两个column。一个叫id,第二个叫owner
四、配置服务器端
1、配置数据库连接参数
我们这个例子建立在pomelo官方的例子chatofpomelo之上,进入下载的chatofpomelo,由于数据库的连接参数game-server和web-server都需要用到,所以最好放到一个共享目录。在项目根目录建立一个shared目录,再在下面建立个config目录,在config下面新建一个文件mysql.json,配置连接数据库的参数:
{
"development": {
"host" : "127.0.0.1",
"port" : "3306",
"database" : "pomelo",
"user" : "test",
"password" : "1234qwer"
},
"production": {
"host" : "127.0.0.1",
"port" : "3306",
"database" : "pomelo",
"user" : "test",
"password" : "1234qwer"
}
}
2、配置数据访问接口(DAO),DAO接在数据库资源和业务逻辑中间,把底层的数据访问逻辑和高层的业务逻辑分开。我们对数据库访问的相关操作就放到这个模块里面。如果后面需要更换数据库什么的,可以直接重写这部分代码即可,而不需要在项目的所有逻辑代码里面到处去改sql语句。
var _poolModule = require('generic-pool');
//导入mysql模块,创建数据库连接需要
var mysql = require('mysql');
/*
* Create mysql connection pool.
*/
var createMysqlPool = function(app){
var mysqlConfig = app.get('mysql');
const factory = {
create: function(){
return new Promise(function(resolve, reject){
var client = mysql.createConnection({
host: mysqlConfig.host,
user: mysqlConfig.user,
password: mysqlConfig.password,
database: mysqlConfig.database
});
resolve(client);
});
},
destroy: function(client){
return new Promise(function(resolve){
client.on('end', function(){
resolve()
});
client.disconnect()
});
}
}
return _poolModule.createPool(factory, {max:10, min:2});
};
exports.createMysqlPool = createMysqlPool;
// mysql CRUD
var sqlclient = module.exports;
var _pool;
var NND = {};
/*
* Init sql connection pool
* @param {Object} app The app for the server.
*/
NND.init = function(app){
_pool = require('./dao-pool').createMysqlPool(app);
};
/**
* Excute sql statement
* @param {String} sql Statement The sql need to excute.
* @param {Object} args The args for the sql.
* @param {fuction} cb Callback function.
*
*/
NND.query = function(sql, args, callback){
const resourcePromise = _pool.acquire();
resourcePromise.then(function(client) {
client.query(sql, args, function(err, res) {
_pool.release(client);
callback.apply(null, [err, res]);
});
}).catch(function(err){
if(!!err){
console.error('query error:',err);
}
callback(err);
});
};
/**
* Close connection pool.
*/
NND.shutdown = function(){
_pool.drain().then(function(){
_pool.clear();
});
};
/**
* init database
*/
sqlclient.init = function(app) {
if (!!_pool){
return sqlclient;
} else {
NND.init(app);
sqlclient.insert = NND.query;
sqlclient.update = NND.query;
sqlclient.delete = NND.query;
sqlclient.query = NND.query;
return sqlclient;
}
};
/**
* shutdown database
*/
sqlclient.shutdown = function(app) {
NND.shutdown(app);
};
3、配置一个MD5模块,正式应用里面账号密码肯定不能是明文的,我们加一个md5模块,虽然这里用不到,但是养成好习惯。
进入game-server,命令行 npm install md5。
4、完成前端服务器代码(至于gate代码在这个例子里直接用就好,不做改动)。客户端发起一个请求,客户端发给gate服务器,然后gate服务器给分配一个connector服务器(前端服务器),connector服务器接收客户端的连接请求,创建与客户端的连接,维护与客户端的会话(session)信息,同时接收客户端对后端服务器的请求,按照用户配置的路由策略,将请求路由给具体的后端服务器。当后端服务器要对客户端发消息时,connector也会完成对客户端的消息发送。
进入game-server/app/servers/connector/handler,打开entryHandler.js,完善代码。Handler.enter 负责维护客户端的session 包括建立绑定等,玩家进入后返回一个成功字段,并且分配session,uid为客户端名字
module.exports = function(app) {
return new Handler(app);
};
var Handler = function(app) {
this.app = app;
};
var handler = Handler.prototype;
/**
* New client entry chat server.
*
* @param {Object} msg request message
* @param {Object} session current session object
* @param {Function} next next stemp callback
* @return {Void}
*/
handler.enter = function(msg, session, next) {
var self = this;
var rid = msg.rid;
var uid = msg.name + '*' + rid
var sessionService = self.app.get('sessionService');
//duplicate log in
if( !! sessionService.getByUid(uid)) {
next(null, {
code: 500,
error: true
});
return;
}
session.bind(uid);
session.set('rid', rid);
session.pushAll();
session.on('closed', onUserLeave.bind(null, self.app));
//put user into channel
self.app.rpc.chat.chatRemote.add(session, uid, self.app.get('serverId'), rid, true, function(users){
next(null, {
users:users
});
});
};
/**
* User log out handler
*
* @param {Object} app current application
* @param {Object} session current session object
*
*/
var onUserLeave = function(app, session) {
if(!session || !session.uid) {
return;
}
app.rpc.chat.chatRemote.kick(session, session.uid, app.get('serverId'), session.get('rid'), function(){
console.log("====== kick callback over! ======");
});
};
5、完成后端服务器代码。在servers下新建game/handler/gameHandler.js。负责具体的购买逻辑,该服务不可直接和客户端通讯,也就是客户端pomelo.init 不能连接到后端服务器game。
var pomelo = require('pomelo');
module.exports = function(app){
return new Handler(app);
}
var Handler = function(app){
this.app = app;
}
//prototype属性使您有能力向对象添加属性和方法
var handler = Handler.prototype;
handler.getNotify = function(msg,session,next){
//console.log(msg);
//console.log(session);
next(null,{msg:"welcome"+ msg.name + "enter the game"});
}
handler.buyGoods=function(msg,session,next){
var id = msg.id;
var count = msg.count;
console.log("player uid: "+ session.uid);
if(id=="100" && count ==1){
var sql = "insert into goods (id,owner) values (?,?)";
var args = [id, session.uid];
//获取全局 mysql client
var dbclient= pomelo.app.get('dbclient');
console.log(dbclient);
//执行sql语句,函数insert和query等效
dbclient.query(sql, args, function(err, res){
console.log('........................');
console.log(err+' '+JSON.stringify(res));
console.log('........................');
if(err){
//数据库操作失败
next(null, {msg:'fail to buy', code:200});
}else{
//购买成功
next(null, {msg: 'successful',code:200});
}
});
}else{
//返回客户端调用
next(null,{msg:"fail, code:200"});
}
}
6、配置servers.json和adminServer.json,把新增的game服务器加上。进入game-server/app/config。
{
"development":{
"connector":[
{"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true},
{"id":"connector-server-2", "host":"127.0.0.1", "port":4051, "clientPort": 3051, "frontend": true},
{"id":"connector-server-3", "host":"127.0.0.1", "port":4052, "clientPort": 3052, "frontend": true}
],
"chat":[
{"id":"chat-server-1", "host":"127.0.0.1", "port":6050},
{"id":"chat-server-2", "host":"127.0.0.1", "port":6051},
{"id":"chat-server-3", "host":"127.0.0.1", "port":6052}
],
"game":[
{"id":"game-server-1", "host":"127.0.0.1","port":8000}
],
"gate":[
{"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true}
]
},
"production":{
"connector":[
{"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true},
{"id":"connector-server-2", "host":"127.0.0.1", "port":4051, "clientPort": 3051, "frontend": true},
{"id":"connector-server-3", "host":"127.0.0.1", "port":4052, "clientPort": 3052, "frontend": true}
],
"chat":[
{"id":"chat-server-1", "host":"127.0.0.1", "port":6050},
{"id":"chat-server-2", "host":"127.0.0.1", "port":6051},
{"id":"chat-server-3", "host":"127.0.0.1", "port":6052}
],
"game":[
{"id":"game-server-1","host":"127.0.0.1","port":80000}
],
"gate":[
{"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true}
]
}
}
[{
"type": "connector",
"token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
}, {
"type": "chat",
"token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
},{
"type": "gate",
"token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
},{
"type": "game",
"token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn"
}
]
五、配置客户端。完成cocos creator客户端代码
1、直接建个helloWorld项目,在script文件夹下新建个pomelo文件夹,用来放pomelo 库,这个库的制作参见第二章标题3提到的帖子
2、在scipt文件夹下新建个test.js
cc.Class({
extends: cc.Component,
properties: {
},
onLoad: function () {
pomelo.on('disconnect', function(reason) {
console.log("pomelo.on() disconnect: ", reason);
});
var host = "127.0.0.1";
var port = "3014";
var gateRoute = 'gate.gateHandler.queryEntry';
var uid="u001";
var rid="01";
var name="xiaoming";
//请求链接gate服务器
pomelo.init({
host: host,
port: port,
log: true
}, function(){
//连接成功之后,向gate服务器请求ip和port
pomelo.request(gateRoute,{
uid: uid
}, function(data){
//断开与gate服务器之间的连接
pomelo.disconnect();
//使用gate服务器返回的ip和port请求链接connector服务器
pomelo.init({
host: data.host,
port: data.port,
log: true
},function(){
//连接成功之后,向connector服务器发送登陆请求
var connRoute="connector.entryHandler.enter";
pomelo.request(connRoute,{name: name, rid:rid},function(data){
//登陆成功之后 显示登陆成功
cc.log(JSON.stringify(data));
console.log("登陆成功啦");
console.log("entry :" + data.msg);
var gameRoute="game.gameHandler.getNotify";
var buyRoute='game.gameHandler.buyGoods';
pomelo.request(gameRoute,{name:name},function(data){
//测试后端服务器
cc.log("client.js:"+JSON.stringify(data));
console.log('测试后端服务器');
pomelo.request(buyRoute,{id:"100",count:1},function(data){
cc.log('buy goods:'+JSON.stringify(data));
});
});
});
});
});
pomelo.on('onChat', function(data) {
console.log(data.from, data.target, data.msg);
});
});
},
start () {
},
// update (dt) {},
});
六、运行测试。
用testConnection进入mysql,在webstorm中启动game-server(或者cmd,进入game-server文件夹下 pomelo start),然后运行cocos creator的项目。之后可以看到pomelo数据库中goods表里新增加了一条数据