目标:我这里要做的目标,就是当一个用户(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表里新增加了一条数据