根据 chat 示例创建 game-server,支持分布式部署、域名访问、数据库连接和基础使用
This commit is contained in:
24
game-server/app/db/BaseModel.ts
Normal file
24
game-server/app/db/BaseModel.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { prop, pre } from '@typegoose/typegoose';
|
||||
|
||||
/**
|
||||
* BaseModel
|
||||
*/
|
||||
@pre<BaseModel>('save', function (next) {
|
||||
if (!this.createdAt || this.isNew) {
|
||||
this.createdAt = this.updatedAt = new Date()
|
||||
} else {
|
||||
this.updatedAt = new Date()
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
export default class BaseModel {
|
||||
|
||||
_id?: string
|
||||
|
||||
@prop()
|
||||
createdAt: Date
|
||||
|
||||
@prop()
|
||||
updatedAt: Date
|
||||
}
|
||||
45
game-server/app/db/User.ts
Normal file
45
game-server/app/db/User.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import BaseModel from './BaseModel';
|
||||
import { index, getModelForClass, prop } from '@typegoose/typegoose';
|
||||
|
||||
|
||||
/**
|
||||
* 用户字段接口
|
||||
*/
|
||||
@index({ userNo: 1 })
|
||||
export default class User extends BaseModel {
|
||||
|
||||
@prop({ required: true})
|
||||
userNo: number;
|
||||
|
||||
@prop({ required: true})
|
||||
userName: string;
|
||||
|
||||
@prop({ required: true})
|
||||
token: string;
|
||||
|
||||
@prop({ required: true})
|
||||
telHash: string;
|
||||
|
||||
//#region(实例方法 和 实例方法)
|
||||
public async userInstanceTestMethods() {
|
||||
|
||||
const user: User = new User();
|
||||
user.userName = '我是实例化方法测试';
|
||||
user.userNo = 9527;
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public static async userStaticTestMethods() {
|
||||
|
||||
const user: User = new User();
|
||||
user.userName = '我是静态方法测试';
|
||||
user.userNo = 9527;
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export const UserModel = getModelForClass(User);
|
||||
76
game-server/app/servers/chat/handler/chatHandler.ts
Normal file
76
game-server/app/servers/chat/handler/chatHandler.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { UserModel } from './../../../db/User';
|
||||
import { ChatRemote } from '../remote/chatRemote';
|
||||
import {Application, BackendSession} from 'pinus';
|
||||
import { FrontendSession } from 'pinus';
|
||||
|
||||
export default function(app: Application) {
|
||||
return new ChatHandler(app);
|
||||
}
|
||||
|
||||
export class ChatHandler {
|
||||
constructor(private app: Application) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send messages to users
|
||||
*
|
||||
* @param {Object} msg message from client
|
||||
* @param {Object} session
|
||||
*
|
||||
*/
|
||||
async send(msg: {content: string , target: string}, session: BackendSession) {
|
||||
let rid = session.get('rid');
|
||||
let username = session.uid.split('*')[0];
|
||||
let channelService = this.app.get('channelService');
|
||||
let param = {
|
||||
msg: msg.content,
|
||||
from: username,
|
||||
target: msg.target
|
||||
};
|
||||
let channel = channelService.getChannel(rid, false);
|
||||
|
||||
// the target is all users
|
||||
if (msg.target === '*') {
|
||||
channel.pushMessage('onChat', param);
|
||||
}
|
||||
// the target is specific user
|
||||
else {
|
||||
let tuid = msg.target + '*' + rid;
|
||||
let tsid = channel.getMember(tuid)['sid'];
|
||||
channelService.pushMessageByUids('onChat', param, [{
|
||||
uid: tuid,
|
||||
sid: tsid
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
async send2(msg: {content: string , target: string}, session: BackendSession) {
|
||||
let rid = session.get('rid');
|
||||
let username = session.uid.split('*')[0];
|
||||
let channelService = this.app.get('channelService');
|
||||
let param = {
|
||||
msg: msg.content,
|
||||
from: username,
|
||||
target: msg.target
|
||||
};
|
||||
let channel = channelService.getChannel(rid, false);
|
||||
|
||||
console.log(`got user in send2 :`,);
|
||||
const user = await UserModel.findOneAndUpdate({userName: username}, {userName: username, userNo: 666}, {upsert: true, new: true}).lean();
|
||||
console.log(`got user in send2 :`, user);
|
||||
|
||||
// the target is all users
|
||||
if (msg.target === '*') {
|
||||
channel.pushMessage('onChat', param);
|
||||
}
|
||||
// the target is specific user
|
||||
else {
|
||||
let tuid = msg.target + '*' + rid;
|
||||
let tsid = channel.getMember(tuid)['sid'];
|
||||
channelService.pushMessageByUids('onChat', param, [{
|
||||
uid: tuid,
|
||||
sid: tsid
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
92
game-server/app/servers/chat/remote/chatRemote.ts
Normal file
92
game-server/app/servers/chat/remote/chatRemote.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Application, ChannelService, FrontendSession, RemoterClass } from 'pinus';
|
||||
|
||||
export default function (app: Application) {
|
||||
return new ChatRemote(app);
|
||||
}
|
||||
|
||||
// rpc 定义挪到单独的定义文件(user.rpc.define.ts)。解决ts-node 有可能找不到定义的问题。
|
||||
// 你也可以用其它方法解决,或者没有遇到过这个问题的话,定义还是可以放在这里。
|
||||
|
||||
// UserRpc的命名空间自动合并
|
||||
// declare global {
|
||||
// interface UserRpc {
|
||||
// chat: {
|
||||
// chatRemote: RemoterClass<FrontendSession, ChatRemote>;
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
export class ChatRemote {
|
||||
|
||||
constructor(private app: Application) {
|
||||
this.app = app;
|
||||
this.channelService = app.get('channelService');
|
||||
}
|
||||
|
||||
private channelService: ChannelService;
|
||||
|
||||
/**
|
||||
* Add user into chat channel.
|
||||
*
|
||||
* @param {String} uid unique id for user
|
||||
* @param {String} sid server id
|
||||
* @param {String} name channel name
|
||||
* @param {boolean} flag channel parameter
|
||||
*
|
||||
*/
|
||||
public async add(uid: string, sid: string, name: string, flag: boolean) {
|
||||
let channel = this.channelService.getChannel(name, flag);
|
||||
let username = uid.split('*')[0];
|
||||
let param = {
|
||||
user: username
|
||||
};
|
||||
channel.pushMessage('onAdd', param);
|
||||
|
||||
if (!!channel) {
|
||||
channel.add(uid, sid);
|
||||
}
|
||||
|
||||
return this.get(name, flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user from chat channel.
|
||||
*
|
||||
* @param {Object} opts parameters for request
|
||||
* @param {String} name channel name
|
||||
* @param {boolean} flag channel parameter
|
||||
* @return {Array} users uids in channel
|
||||
*
|
||||
*/
|
||||
private get(name: string, flag: boolean) {
|
||||
let users: string[] = [];
|
||||
let channel = this.channelService.getChannel(name, flag);
|
||||
if (!!channel) {
|
||||
users = channel.getMembers();
|
||||
}
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
users[i] = users[i].split('*')[0];
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kick user out chat channel.
|
||||
*
|
||||
* @param {String} uid unique id for user
|
||||
* @param {String} sid server id
|
||||
* @param {String} name channel name
|
||||
*
|
||||
*/
|
||||
public async kick(uid: string, sid: string, name: string) {
|
||||
let channel = this.channelService.getChannel(name, false);
|
||||
// leave channel
|
||||
if (!!channel) {
|
||||
channel.leave(uid, sid);
|
||||
}
|
||||
let username = uid.split('*')[0];
|
||||
let param = {
|
||||
user: username
|
||||
};
|
||||
channel.pushMessage('onLeave', param);
|
||||
}
|
||||
}
|
||||
63
game-server/app/servers/connector/handler/entryHandler.ts
Normal file
63
game-server/app/servers/connector/handler/entryHandler.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {Application} from 'pinus';
|
||||
import {FrontendSession} from 'pinus';
|
||||
|
||||
export default function (app: Application) {
|
||||
return new EntryHandler(app);
|
||||
}
|
||||
|
||||
export class EntryHandler {
|
||||
constructor(private app: Application) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* New client entry chat server.
|
||||
*
|
||||
* @param {Object} msg request message
|
||||
* @param {Object} session current session object
|
||||
*/
|
||||
async enter(msg: { rid: string, username: string }, session: FrontendSession) {
|
||||
let self = this;
|
||||
let rid = msg.rid;
|
||||
let uid = msg.username + '*' + rid;
|
||||
let sessionService = self.app.get('sessionService');
|
||||
|
||||
// duplicate log in
|
||||
if (!!sessionService.getByUid(uid)) {
|
||||
return {
|
||||
code: 500,
|
||||
error: true
|
||||
};
|
||||
}
|
||||
|
||||
await session.abind(uid);
|
||||
session.set('rid', rid);
|
||||
session.push('rid', function (err) {
|
||||
if (err) {
|
||||
console.error('set rid for session service failed! error is : %j', err.stack);
|
||||
}
|
||||
});
|
||||
session.on('closed', this.onUserLeave.bind(this));
|
||||
|
||||
// put user into channel
|
||||
let users = await self.app.rpc.chat.chatRemote.add.route(session)(uid, self.app.get('serverId'), rid, true);
|
||||
|
||||
return {
|
||||
users: users
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* User log out handler
|
||||
*
|
||||
* @param {Object} app current application
|
||||
* @param {Object} session current session object
|
||||
*
|
||||
*/
|
||||
onUserLeave(session: FrontendSession) {
|
||||
if (!session || !session.uid) {
|
||||
return;
|
||||
}
|
||||
this.app.rpc.chat.chatRemote.kick.route(session, true)(session.uid, this.app.get('serverId'), session.get('rid'));
|
||||
}
|
||||
}
|
||||
42
game-server/app/servers/gate/handler/gateHandler.ts
Normal file
42
game-server/app/servers/gate/handler/gateHandler.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { dispatch } from '../../../util/dispatcher';
|
||||
import { Application , BackendSession} from 'pinus';
|
||||
|
||||
export default function (app: Application) {
|
||||
return new GateHandler(app);
|
||||
}
|
||||
|
||||
export class GateHandler {
|
||||
constructor(private app: Application) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gate handler that dispatch user to connectors.
|
||||
*
|
||||
* @param {Object} msg message from client
|
||||
* @param {Object} session
|
||||
*
|
||||
*/
|
||||
async queryEntry(msg: {uid: string}, session: BackendSession) {
|
||||
let uid = msg.uid;
|
||||
if (!uid) {
|
||||
return {
|
||||
code: 500
|
||||
};
|
||||
}
|
||||
// get all connectors
|
||||
let connectors = this.app.getServersByType('connector');
|
||||
console.log('ltc connectors', connectors);
|
||||
if (!connectors || connectors.length === 0) {
|
||||
return {
|
||||
code: 500
|
||||
};
|
||||
}
|
||||
// select connector
|
||||
let res = dispatch(uid, connectors);
|
||||
return {
|
||||
code: 200,
|
||||
host: res.host,
|
||||
port: res.clientPort
|
||||
};
|
||||
}
|
||||
}
|
||||
14
game-server/app/servers/user.rpc.define.ts
Normal file
14
game-server/app/servers/user.rpc.define.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
// 这种
|
||||
|
||||
// UserRpc的命名空间自动合并
|
||||
import { FrontendSession, RemoterClass } from 'pinus';
|
||||
import { ChatRemote } from './chat/remote/chatRemote';
|
||||
|
||||
declare global {
|
||||
interface UserRpc {
|
||||
chat: {
|
||||
chatRemote: RemoterClass<FrontendSession, ChatRemote>;
|
||||
};
|
||||
}
|
||||
}
|
||||
7
game-server/app/util/dispatcher.ts
Normal file
7
game-server/app/util/dispatcher.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import * as crc from 'crc';
|
||||
import { ServerInfo } from 'pinus';
|
||||
|
||||
export function dispatch(uid: string , connectors: ServerInfo[]) {
|
||||
let index = Math.abs(crc.crc32(uid)) % connectors.length;
|
||||
return connectors[index];
|
||||
}
|
||||
15
game-server/app/util/routeUtil.ts
Normal file
15
game-server/app/util/routeUtil.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
import { dispatch} from './dispatcher';
|
||||
import { Session, Application } from 'pinus';
|
||||
|
||||
export function chat(session: Session, msg: any, app: Application, cb: (err: Error , serverId ?: string) => void) {
|
||||
let chatServers = app.getServersByType('chat');
|
||||
|
||||
if(!chatServers || chatServers.length === 0) {
|
||||
cb(new Error('can not find chat servers.'));
|
||||
return;
|
||||
}
|
||||
|
||||
let res = dispatch(session.get('rid'), chatServers);
|
||||
cb(null, res.id);
|
||||
}
|
||||
Reference in New Issue
Block a user