import {existsSync, readFileSync} from 'fs'; import {join, resolve} from 'path'; import {LogLevel, WsServer} from 'tsrpc'; import {ServiceType as ServiceTypeCross, serviceProto as serviceProtoCross} from './cross/protocols/serviceProto'; import {Encrypt} from './encrypt'; import {FightFun} from './public/fight'; import {PayFun} from './public/pay'; import {Rank} from './public/rank/rank'; import {createWsClient} from './setWsClient'; import {MsgPay} from './shared/protocols/msg_c2s/MsgPay'; import {ServiceType, serviceProto} from './shared/protocols/serviceProto'; import {player} from './shared/protocols/user/type'; import {unQueueByConn} from './api_s2c/user/ApiLogin'; import {clusterPublish, setUidProcessId} from './clusterUtils'; import {clearGud, getGud} from './public/gud'; import {mylogger} from './gameLog'; export async function createWs() { if (G.argv.serverType == 'cross') { G.serverCross = new WsServer(serviceProtoCross, { //本地里,考虑到一台服务器可能既启动game,又启动cross //会导致端口冲突,所以本地特别配了一个crossPort //外网环境的config.json里,是没有crossPort的,无论游服还是跨服,都直接用msgPort port: G.config.crossPort || G.config.msgPort, json: true, //API超时时间5分钟,为登陆排队做准备 apiTimeout: 300000, logLevel: G.argv.logModel as LogLevel, logger: mylogger }); setCrossWs(G.serverCross); await G.serverCross.autoImplementApi(resolve(__dirname, 'api_cross'), true); await G.serverCross.start(); } else { G.server = new WsServer(serviceProto, { port: G.config.msgPort, logLevel: G.argv.logModel as LogLevel, wss: getWssFile(), //API超时时间5分钟,为登陆排队做准备,只针对游服 apiTimeout: 300000, logger: mylogger }); setWs(G.server); await G.server.autoImplementApi(resolve(__dirname, 'api_s2c'), true); await G.server.start(); G.argv.serverType == 'msg' && G.config.corssWsUrl && await createWsClient(); } } function getWssFile() { if (G.config.wss.key && G.config.wss.cert && existsSync(join(__dirname, '../' + G.config.wss.key)) && existsSync(join(__dirname, '../' + G.config.wss.cert))) { return { key: readFileSync(join(__dirname, '../' + G.config.wss.key)), cert: readFileSync(join(__dirname, '../' + G.config.wss.cert)) }; } else { return null; } } const writeList = ['hongdian/Get'] function setWs(server: WsServer) { encryptWs(server); server.uid_connections = {}; //客户端连接后 server.flows.postConnectFlow.push(conn => { conn.requstApiTime = {}; conn.apiLock = {}; return conn; }); //客户端断开连接后 server.flows.postDisconnectFlow.push(node => { unQueueByConn(node.conn); //玩家断开后以玩家uid对socket连接进行解绑 if (node.conn.uid && server.uid_connections[node.conn.uid] && server.uid_connections[node.conn.uid] == node.conn) { server.uid_connections[node.conn.uid] = null; delete server.uid_connections[node.conn.uid]; } if (node.conn.uid) { clearGud(node.conn.uid); G.emit('PLAYER_DISCONNECT', node.conn.uid); } //清理数据 delete node.conn.gud; delete node.conn.requstApiTime delete node.conn.apiLock; delete node.conn.lshd; delete node.conn.onlineTime; return node; }); //执行 API 接口实现之前 server.flows.preApiCallFlow.push(async call => { // 临时停服维护方案 // let lng = { // "zh-TW": "停服維護中,請等待!", // "ko": "서버 점검 중, 잠시만 기다려 주세요.", // "ja": "サーバーメンテナンス中、しばらくお待ちください。", // "en": "Server under maintenance. Please wait.", // } // call.error("", {code: -1, message: lng[call.req.lng] || lng["ja"]}) // return null; //是否短时间内重复请求某个api // let timeIntervalLimit = call.service.conf?.timeIntervalLimit == undefined ? 500 : call.service.conf?.timeIntervalLimit; // if (new Date().getTime() - call.conn.requstApiTime[call.service.name] < timeIntervalLimit) { // call.error('', { code: -1, message: '', time: timeIntervalLimit }); // } else { // call.conn.requstApiTime[call.service.name] = new Date().getTime(); // } if (call.service.conf?.needGudKey?.length > 0) { let gud: player; gud = await getGud(call.uid); for (let key of call.service.conf.needGudKey) { if (!gud[key]) { call.error(`you need has ${key}`); return null; } } } //判断是否登录 if (!['user/Ping', 'user/Login'].includes(call.service.name) && !call.conn.uid) { call.error("qingxiandenglu"); return null; } //处理API锁,极限情况下只锁10s,防止死锁 //在下方postApiReturnFlow里会解锁 if (call.conn.apiLock[call.service.name] && call.conn.apiLock[call.service.name] > new Date().getTime()) { call.error('', {code: -100, message: '', apilock: 1}); return null; } //API锁定到什么时候 if (!writeList.includes(call.service.name)) { call.conn.apiLock[call.service.name] = new Date().getTime() + 10000; } //API耗时统计 call.conn.requstApiTime[call.service.name] = new Date().getTime(); //为了维持原有的逻辑,这里在每次请求时,将gud数据写入conn中 if (call.conn.uid) { call.conn.gud = await getGud(call.conn.uid); } return call; }); server.flows.preApiReturnFlow.push(node => { // if (!node.return.isSucc && node.return.err && node.return.err.type == "ApiError") { // if (node.return.err?.message){ // node.return.err.message = "" // node.call.return.err.message = "" // } // } return node; }); //API 接口返回结果(call.succ、call.error)之后 server.flows.postApiReturnFlow.push(async node => { //接口响应速度统计 let startTime = node.call.conn.requstApiTime[node.call.service.name] if (startTime && node.return.isSucc && G.mongodb) { let needTime = new Date().getTime() - startTime; G.mongodb.collection("apiCount").updateOne( {api: node.call.service.name}, {$inc: {callnums: 1, needtimes: needTime}}, {upsert: true} ) } //玩家登陆后以玩家uid对socket连接进行绑定 if (node.call.service.name == 'user/Login' && node.return.isSucc) { //玩家uid已经登陆在线 通知账号在其他地方登录 const uid = node.return.res.gud.uid; server.uid_connections[uid] = node.call.conn; setUidProcessId(uid); } try { G.emit('API_CALL', node); } catch (e) { console.log('EMIT_API_CALL_ERROR', e); } //API解锁 let now = new Date().getTime(); if (node.return.isSucc) { if (!writeList.includes(node.call.service.name)){ node.call.conn.apiLock[node.call.service.name] = now + 200; } } else { if (!node.return.err.apilock) { delete node.call.conn.apiLock[node.call.service.name]; } } return node; }); server.listenMsg(/^msg_c2s\//, async msgHandler => { switch (msgHandler.service.name as (keyof ServiceType['msg'] & `msg_c2s/${string}`)) { case 'msg_c2s/Pay': let msgPay = msgHandler.msg as MsgPay; let gud = await getGud(msgHandler.conn.uid); await PayFun.check(gud, msgPay.id, msgPay.args); break; } }); } function setCrossWs(server: WsServer) { // encryptWs(server); server.listenMsg('msg_cross/CrossChat', msg => { //跨服收到这条信息的时候,转发给连接到本跨服的所有客户端,即所有的服务端进程 server.connections.forEach(conn => conn.sendMsg('msg_cross/CrossChat', msg.msg)); }); // server.listenMsg('msg_cross/HbzbSendUser', sendData => { // Rank.list.hbzbCross.addNew(sendData.msg.user); // }); server.listenMsg('msg_cross/HbzbJfsLog', handle => { FightFun.saveLog(handle.msg.uid, 'hbzbJfs', handle.msg.log); FightFun.saveLog(handle.msg.toUid, 'hbzbJfs', handle.msg.log); }); server.listenMsg('msg_cross/HbzbZbsLog', handle => { FightFun.saveLog(handle.msg.uid, 'hbzbZbs', handle.msg.log); FightFun.saveLog(handle.msg.toUid, 'hbzbZbs', handle.msg.log); }); // server.listenMsg('msg_cross/HbzbChangeRank', handle => { // //Rank.list.hbzbZbsCross.changeRank(handle.msg.uid, handle.msg.toUid); // }); } /**数据传输加解密 */ function encryptWs(server: WsServer) { //处理收到的数据前 server.flows.preRecvDataFlow.push(node => { // 接收前解密 node.data = Encrypt.decrypt(node.data); return node; }); //发送数据前 server.flows.preSendDataFlow.push(node => { // 发送前加密 node.data = Encrypt.encrypt(node.data); return node; }); }