web-server 注册、登录、获取服务器列表;game-server token 校验

This commit is contained in:
liangtongchuan
2020-08-19 14:40:11 +08:00
parent 0458817b51
commit 940879016f
32 changed files with 1144 additions and 38 deletions

View File

@@ -0,0 +1,8 @@
export const TURBO_CORE_URL = 'https://coresrv.tgamebox.cn';
export const APP_ID = 'AXaXmIHPs9eONvzrBesD8aSKQNXYdALF';
export const TURBO_PARM_SECRET = 'ipqw05du6ob4x130w89t31yrqd6xs005zzltcmg2zpqnvrjp1s';
export const ENCRYPT_IV = 'f7182j5f04e377ux';
export const ENCRYPT_KEY = 'fiqaxijabbantusmprc234fj';
export const AUTH_SMS_CNT_PER_DAY = 8;

View File

@@ -0,0 +1,23 @@
import BaseModel from './BaseModel';
import { index, getModelForClass, prop } from '@typegoose/typegoose';
/**
* 短信字段接口
*/
@index({ name: 1 })
export default class Counter extends BaseModel {
@prop({ required: true })
name: string;
@prop({ required: true, default: 1 })
seq: number;
public static async getNewCounter(name: string) {
const counter = await CounterModel.findOneAndUpdate({name}, {$inc: {seq: 1}}, {new: true, upsert: true}).lean();
return counter?.seq;
}
}
export const CounterModel = getModelForClass(Counter);

View File

@@ -0,0 +1,70 @@
import { APP_ID } from './../consts/consts';
import BaseModel from './BaseModel';
import { index, getModelForClass, prop } from '@typegoose/typegoose';
class ServerInfo {
@prop({ required: true})
name: string;
@prop({ required: true})
host: string;
@prop({ required: false})
port: number;
@prop({ required: true})
status: number;
@prop({ required: true})
createTime: Date;
@prop({ required: true})
serverType: string;
}
/**
* 用户字段接口
*/
@index({ id: 1 })
export default class Game extends BaseModel {
@prop({ required: true})
id: number;
@prop({ required: true})
name: string;
@prop({ required: true})
nameEn: string;
@prop({ required: true})
des: string;
@prop({ required: true})
serverList: Array<ServerInfo>;
@prop({ required: true})
iconUrl: string;
@prop({ required: true})
version: string;
@prop({ required: true})
versionCode: number;
public static async getServerListByType(serverType: string) {
let game = await GameModel.findOne().lean();
if (!game) {
const serverInfo: ServerInfo = {name: '常山少年', host: 'pinus_test.trgame.cn', port: 3014, status: 1, createTime: new Date(), serverType: 'official'};
const iconUrl = `https://download.tgamebox.cn/avatar/${APP_ID}/1.png`;
game = await GameModel.findOneAndUpdate({}, {id: 1, name: '赵云传', nameEn: 'zyz', des: '牛逼的战棋', iconUrl, version: '0.0.1', versionCode: 1, $push: {serverList: serverInfo}}, {upsert: true, new: true}).lean();
}
let serverList: Array<ServerInfo> = game ? game.serverList : [];
serverList = serverList.filter(item => { return item.serverType === serverType; })
return serverList;
}
//#endregion
}
export const GameModel = getModelForClass(Game);

62
game-server/app/db/Sms.ts Normal file
View File

@@ -0,0 +1,62 @@
import BaseModel from './BaseModel';
import { index, getModelForClass, prop } from '@typegoose/typegoose';
const moment = require('moment');
/**
* 短信字段接口
*/
@index({ tel: 1 })
export default class Sms extends BaseModel {
@prop({ required: true })
tel: string;
@prop({ required: true })
telHash: string;
@prop({ required: true })
code: string;
@prop({ required: true })
used: boolean;
@prop({ required: true })
updateTime: Date;
@prop({ required: true })
countToday: number;
public static async findByTel(tel: string) {
let sms = await smsModel.findOne({ tel }).lean();
return sms;
}
public static async updateByTel(tel: string, code: string, used: boolean, updateTime: Date, countToday: number) {
await smsModel.findOneAndUpdate({tel}, {code, used, updateTime, countToday}, {upsert: true});
}
public static async validateSms(tel: string, code: string) {
const record = await smsModel.findOneAndUpdate({tel, code, used: false}, {used: true});
return !!record;
}
public async timeLimit(interval: number) {
if (this.updateTime.getTime() > Date.now() - interval) {
return true;
}
return false;
}
public async cntLimit(cnt: number) {
if (this.hasSendToday() && this.countToday >= cnt) {
return true;
}
return false;
}
public async hasSendToday() {
return moment(this.updateTime).format("YYYY-MM-DD") === moment(Date.now()).format("YYYY-MM-DD");
}
}
export const smsModel = getModelForClass(Sms);

View File

@@ -1,44 +1,78 @@
import { COUNTER } from './../consts/consts';
import { CounterModel } from './Counter';
import BaseModel from './BaseModel'; import BaseModel from './BaseModel';
import { index, getModelForClass, prop } from '@typegoose/typegoose'; import { index, getModelForClass, prop } from '@typegoose/typegoose';
/** /**
* 用户字段接口 * 用户字段接口
*/ */
@index({ userNo: 1 }) @index({ tel: 1 })
@index({ uid: 1 })
export default class User extends BaseModel { export default class User extends BaseModel {
@prop({ required: true}) @prop({ required: true})
userNo: number; uid: number;
@prop({ required: true}) @prop({ required: true})
userName: string; username: string;
@prop({ required: true}) @prop({ required: true})
token: string; token: string;
@prop({ required: true})
tel: string;
@prop({ required: true}) @prop({ required: true})
telHash: string; telHash: string;
//#region实例方法 和 实例方法) @prop({ required: true})
public async userInstanceTestMethods() { lastLoginTime: Date;
const user: User = new User(); @prop({ required: true})
user.userName = '我是实例化方法测试'; createTime: Date;
user.userNo = 9527;
// 平台ios, android, web, pc
@prop({ required: true})
platform: string;
@prop({ required: true})
pkgName: string;
// 服务器类型official, channel, ios, oversea
@prop({ required: true})
serverType: string;
public static async createUser() {
}
public static async updateToken(tel: string, token: string, platform: string, pkgName: string, serverType: string) {
let user = await UserModel.findOne({tel}).lean();
const curTime: Date = new Date();
let update = {};
if (!user) {
const uid = await CounterModel.getNewCounter(COUNTER.UID);
update = Object.assign(update, {platform, pkgName, serverType, createTime: curTime, uid, username: `用户${uid}`});
}
update = Object.assign(update, {token, lastLoginTime: curTime});
user = await UserModel.findOneAndUpdate({tel}, update, {upsert: true, new: true}).lean();
return user; return user;
} }
public static async userStaticTestMethods() { public static async findUserByToken(token: string) {
const user = await UserModel.findOne({token}).select('uid token').lean();
const user: User = new User();
user.userName = '我是静态方法测试';
user.userNo = 9527;
return user; return user;
} }
public static async findUserByTel(tel: string) {
const user = await UserModel.findOne({tel}).select('uid tel').lean();
return user;
}
public static async findUserByUid(uid: number) {
const user = await UserModel.findOne({uid}).select('uid tel').lean();
return user;
}
//#endregion //#endregion
} }

View File

@@ -1,4 +1,3 @@
import { UserModel } from './../../../db/User';
import { ChatRemote } from '../remote/chatRemote'; import { ChatRemote } from '../remote/chatRemote';
import {Application, BackendSession} from 'pinus'; import {Application, BackendSession} from 'pinus';
import { FrontendSession } from 'pinus'; import { FrontendSession } from 'pinus';
@@ -55,10 +54,6 @@ export class ChatHandler {
}; };
let channel = channelService.getChannel(rid, false); 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 // the target is all users
if (msg.target === '*') { if (msg.target === '*') {
channel.pushMessage('onChat', param); channel.pushMessage('onChat', param);

View File

@@ -1,3 +1,4 @@
import { UserModel } from './../../../db/User';
import {Application} from 'pinus'; import {Application} from 'pinus';
import {FrontendSession} from 'pinus'; import {FrontendSession} from 'pinus';
@@ -16,14 +17,24 @@ export class EntryHandler {
* @param {Object} msg request message * @param {Object} msg request message
* @param {Object} session current session object * @param {Object} session current session object
*/ */
async enter(msg: { rid: string, username: string }, session: FrontendSession) { async enter(msg: { rid: string, username: string, token: string }, session: FrontendSession) {
let self = this; let self = this;
let rid = msg.rid; let rid = msg.rid;
let uid = msg.username + '*' + rid; let uid = msg.username + '*' + rid;
let sessionService = self.app.get('sessionService'); let sessionService = self.app.get('sessionService');
let user = await UserModel.findUserByToken(msg.token);
if (!user) {
console.log('user token not found');
return {
code: 500,
error: true
};
}
// duplicate log in // duplicate log in
if (!!sessionService.getByUid(uid)) { if (!!sessionService.getByUid(uid)) {
console.log('uid not found');
return { return {
code: 500, code: 500,
error: true error: true

View File

@@ -25,7 +25,6 @@ export class GateHandler {
} }
// get all connectors // get all connectors
let connectors = this.app.getServersByType('connector'); let connectors = this.app.getServersByType('connector');
console.log('ltc connectors', connectors);
if (!connectors || connectors.length === 0) { if (!connectors || connectors.length === 0) {
return { return {
code: 500 code: 500

278
package-lock.json generated Normal file
View File

@@ -0,0 +1,278 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"@typegoose/typegoose": {
"version": "7.3.2",
"resolved": "https://registry.npm.taobao.org/@typegoose/typegoose/download/@typegoose/typegoose-7.3.2.tgz",
"integrity": "sha1-nefcvV1/2cCEPvoEeYQgO5Vgu18=",
"requires": {
"lodash": "^4.17.19",
"loglevel": "^1.6.8",
"reflect-metadata": "^0.1.13",
"semver": "^7.3.2",
"tslib": "^2.0.0"
}
},
"bl": {
"version": "2.2.0",
"resolved": "https://registry.npm.taobao.org/bl/download/bl-2.2.0.tgz?cache=0&sync_timestamp=1584503263385&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbl%2Fdownload%2Fbl-2.2.0.tgz",
"integrity": "sha1-4aV0zfUo5AUwGbuACwQcCsiNpJM=",
"requires": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
}
},
"bluebird": {
"version": "3.5.1",
"resolved": "https://registry.npm.taobao.org/bluebird/download/bluebird-3.5.1.tgz",
"integrity": "sha1-2VUfnemPH82h5oPRfukaBgLuLrk="
},
"bson": {
"version": "1.1.5",
"resolved": "https://registry.npm.taobao.org/bson/download/bson-1.1.5.tgz?cache=0&sync_timestamp=1597069108497&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbson%2Fdownload%2Fbson-1.1.5.tgz",
"integrity": "sha1-Kqrpj832dQwISLDLod3sPHMGCjQ="
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz",
"integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
"requires": {
"ms": "2.0.0"
},
"dependencies": {
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz?cache=0&sync_timestamp=1575472461218&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"denque": {
"version": "1.4.1",
"resolved": "https://registry.npm.taobao.org/denque/download/denque-1.4.1.tgz",
"integrity": "sha1-Z0T/dkHBSMP4ppwwflEjXB9KN88="
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz",
"integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"kareem": {
"version": "2.3.1",
"resolved": "https://registry.npm.taobao.org/kareem/download/kareem-2.3.1.tgz",
"integrity": "sha1-3vEtnJQQF/q/sA+HOvlenJnhvoc="
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npm.taobao.org/lodash/download/lodash-4.17.20.tgz?cache=0&sync_timestamp=1597336097104&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.20.tgz",
"integrity": "sha1-tEqbYpe8tpjxxRo1RaKzs2jVnFI="
},
"loglevel": {
"version": "1.6.8",
"resolved": "https://registry.npm.taobao.org/loglevel/download/loglevel-1.6.8.tgz",
"integrity": "sha1-iiX7ddCSIw7NRFcnDYC1TigBEXE="
},
"memory-pager": {
"version": "1.5.0",
"resolved": "https://registry.npm.taobao.org/memory-pager/download/memory-pager-1.5.0.tgz",
"integrity": "sha1-2HUWVdItOEaCdByXLyw9bfo+ZrU=",
"optional": true
},
"mongodb": {
"version": "3.6.0",
"resolved": "https://registry.npm.taobao.org/mongodb/download/mongodb-3.6.0.tgz",
"integrity": "sha1-ur1xcuxxfi7T+F4Hmz8aop3ORyQ=",
"requires": {
"bl": "^2.2.0",
"bson": "^1.1.4",
"denque": "^1.4.1",
"require_optional": "^1.0.1",
"safe-buffer": "^5.1.2",
"saslprep": "^1.0.0"
}
},
"mongoose": {
"version": "5.10.0",
"resolved": "https://registry.npm.taobao.org/mongoose/download/mongoose-5.10.0.tgz?cache=0&sync_timestamp=1597421711994&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmongoose%2Fdownload%2Fmongoose-5.10.0.tgz",
"integrity": "sha1-BaNfWj2EhWE8mYiuuVSChalwg/c=",
"requires": {
"bson": "^1.1.4",
"kareem": "2.3.1",
"mongodb": "3.6.0",
"mongoose-legacy-pluralize": "1.0.2",
"mpath": "0.7.0",
"mquery": "3.2.2",
"ms": "2.1.2",
"regexp-clone": "1.0.0",
"safe-buffer": "5.2.1",
"sift": "7.0.1",
"sliced": "1.0.1"
}
},
"mongoose-legacy-pluralize": {
"version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/mongoose-legacy-pluralize/download/mongoose-legacy-pluralize-1.0.2.tgz",
"integrity": "sha1-O6n5H6UHtRhtOZ+0CFS/8Y+1Y+Q="
},
"mpath": {
"version": "0.7.0",
"resolved": "https://registry.npm.taobao.org/mpath/download/mpath-0.7.0.tgz",
"integrity": "sha1-IOgQLidrcXCdbgfp+NTQ9kGvv7g="
},
"mquery": {
"version": "3.2.2",
"resolved": "https://registry.npm.taobao.org/mquery/download/mquery-3.2.2.tgz",
"integrity": "sha1-4Tg6OVGFLOI+N/YZqbNQ8fs2ZOc=",
"requires": {
"bluebird": "3.5.1",
"debug": "3.1.0",
"regexp-clone": "^1.0.0",
"safe-buffer": "5.1.2",
"sliced": "1.0.1"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz?cache=0&sync_timestamp=1589129010497&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-buffer%2Fdownload%2Fsafe-buffer-5.1.2.tgz",
"integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0="
}
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&sync_timestamp=1575472461218&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz",
"integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk="
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz",
"integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I="
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.7.tgz",
"integrity": "sha1-Hsoc9xGu+BTAT2IlKjamL2yyO1c=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz?cache=0&sync_timestamp=1589129010497&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-buffer%2Fdownload%2Fsafe-buffer-5.1.2.tgz",
"integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0="
}
}
},
"reflect-metadata": {
"version": "0.1.13",
"resolved": "https://registry.npm.taobao.org/reflect-metadata/download/reflect-metadata-0.1.13.tgz",
"integrity": "sha1-Z648pXyXKiqhZCsQ/jY/4y1J3Ag="
},
"regexp-clone": {
"version": "1.0.0",
"resolved": "https://registry.npm.taobao.org/regexp-clone/download/regexp-clone-1.0.0.tgz",
"integrity": "sha1-Ii25Z2IydwViYLmSYmNUoEzpv2M="
},
"require_optional": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/require_optional/download/require_optional-1.0.1.tgz",
"integrity": "sha1-TPNaQkf2TKPfjC7yCMxJSxyo/C4=",
"requires": {
"resolve-from": "^2.0.0",
"semver": "^5.1.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npm.taobao.org/semver/download/semver-5.7.1.tgz?cache=0&sync_timestamp=1586886301819&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-5.7.1.tgz",
"integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc="
}
}
},
"resolve-from": {
"version": "2.0.0",
"resolved": "https://registry.npm.taobao.org/resolve-from/download/resolve-from-2.0.0.tgz",
"integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.1.tgz?cache=0&sync_timestamp=1589129010497&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-buffer%2Fdownload%2Fsafe-buffer-5.2.1.tgz",
"integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY="
},
"saslprep": {
"version": "1.0.3",
"resolved": "https://registry.npm.taobao.org/saslprep/download/saslprep-1.0.3.tgz",
"integrity": "sha1-TAL5RrVs9UKX40e6EJPnrKxM8iY=",
"optional": true,
"requires": {
"sparse-bitfield": "^3.0.3"
}
},
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npm.taobao.org/semver/download/semver-7.3.2.tgz?cache=0&sync_timestamp=1586886301819&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-7.3.2.tgz",
"integrity": "sha1-YElisFK4HtB4aq6EOJ/7pw/9OTg="
},
"sift": {
"version": "7.0.1",
"resolved": "https://registry.npm.taobao.org/sift/download/sift-7.0.1.tgz?cache=0&sync_timestamp=1594916082498&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsift%2Fdownload%2Fsift-7.0.1.tgz",
"integrity": "sha1-R9YsULFZ0xbxNy+LU/nBDNIaSwg="
},
"sliced": {
"version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/sliced/download/sliced-1.0.1.tgz",
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
},
"sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npm.taobao.org/sparse-bitfield/download/sparse-bitfield-3.0.3.tgz",
"integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
"optional": true,
"requires": {
"memory-pager": "^1.0.2"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz",
"integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=",
"requires": {
"safe-buffer": "~5.1.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz?cache=0&sync_timestamp=1589129010497&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-buffer%2Fdownload%2Fsafe-buffer-5.1.2.tgz",
"integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0="
}
}
},
"tslib": {
"version": "2.0.1",
"resolved": "https://registry.npm.taobao.org/tslib/download/tslib-2.0.1.tgz?cache=0&sync_timestamp=1596751904317&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftslib%2Fdownload%2Ftslib-2.0.1.tgz",
"integrity": "sha1-QQ6w0RPltjVkkO7HSWA3JbAhtD4="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
}
}
}

2
pushdocker.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
rsync -av --include '.babelrc' --include '.eslintrc.js' --exclude '.*' --exclude './game-server/node_modules' --exclude './game-server/node_modules' --exclude 'node_modules' --exclude 'bower_components' --exclude 'dist' --progress --inplace --no-owner --no-group --rsh='ssh -p22' . root@ltctest:/root/zyz/

10
shared/statusCode.ts Normal file
View File

@@ -0,0 +1,10 @@
export const STATUS_SUC = {code: 0, simStr: '成功'};
export const STATUS_WRONG_PARMS = {code: 1, simStr: '参数错误'};
export const STATUS_TOKEN_ERR = {code: 2, simStr: 'token失效'};
export const STATUS_INTERNAL_ERR = {code: 3, simStr: '内部错误'};
export const SMS_IN_60S = {code: 100001, simStr: '60秒内只能发送一次'};
export const SMS_CNT_LIMIT = {code: 100002, simStr: '今日短信条数已达上限'};
export const SMS_INVALID = {code: 100003, simStr: '验证码无效'};
export const SERVER_NOT_FOUND = {code: 200001, simStr: '未找到服务器列表'};

58
web-server/app.ts Normal file
View File

@@ -0,0 +1,58 @@
import 'reflect-metadata'
import * as mongoose from 'mongoose';
import { Application, IBoot } from 'egg';
export default class FooBoot implements IBoot {
private readonly app: Application;
constructor(app: Application) {
this.app = app;
}
async configWillLoad() {
// Ready to call configDidLoad,`
// Config, plugin files are referred,`
// this is the last chance to modify the config.
await this.connectDB(this.app)
}
configDidLoad() {
// Config, plugin files have loaded.
}
async didLoad() {
// All files have loaded, start plugin here.
}
async willReady() {
// All plugins have started, can do some thing before app ready.
// await this.customLoadModel();
}
async didReady() {
// Worker is ready, can do some things
// don't need to block the app boot.
}
async serverDidReady() {
// Server is listening.
}
async beforeClose() {
// Do some thing before app close.
}
//#region 手动挂载model测试需要ctx.model
public async connectDB(app: Application) {
const { url, options } = app.config.mongoose
if (url) {
const connection = await mongoose.connect(url, options)
app.context.connection = connection
}
}
//#endregion
}
module.exports = FooBoot;

View File

@@ -1,3 +1,12 @@
export const TURBO_CORE_URL = 'https://coresrv.tgamebox.cn'; export const TURBO_CORE_URL = 'https://coresrv.tgamebox.cn';
export const APP_ID = 'KbZMUfUfppLFDG7dXtNkLWbyapK0JTHY'; export const APP_ID = 'AXaXmIHPs9eONvzrBesD8aSKQNXYdALF';
export const PARM_SECRET = 'ipqw05du6ob4x130w89t31yrqd6xs005zzltcmg2zpqnvrjp1s'; export const TURBO_PARM_SECRET = 'ipqw05du6ob4x130w89t31yrqd6xs005zzltcmg2zpqnvrjp1s';
export const ENCRYPT_IV = 'f7182j5f04e377ux';
export const ENCRYPT_KEY = 'fiqaxijabbantusmprc234fj';
export const AUTH_SMS_CNT_PER_DAY = 8;
export const COUNTER = {
UID: 'uid'
}

View File

@@ -3,7 +3,13 @@ import { Controller } from 'egg';
export default class AccountController extends Controller { export default class AccountController extends Controller {
public async getSms() { public async getSms() {
const { ctx } = this; const { ctx } = this;
console.log(ctx.request.body); const { tel } = ctx.request.body;
ctx.body = await ctx.service.turboCore.getSms(ctx.request.body.telNo); ctx.body = await ctx.service.auth.getSms(tel);
}
public async smsLogin() {
const { ctx } = this;
const { tel, code, platform, pkgName, serverType } = ctx.request.body;
ctx.body = await ctx.service.auth.smsLogin(tel, code, platform, pkgName, serverType);
} }
} }

View File

@@ -0,0 +1,17 @@
import { SERVER_NOT_FOUND } from './../../../shared/statusCode';
import { STATUS_SUC } from '../../../shared/statusCode';
import { GameModel } from '../db/Game';
import { Controller } from 'egg';
export default class GameController extends Controller {
public async getServerList() {
const { ctx } = this;
const { serverType } = ctx.request.body;
let serverList: Array<any> = await GameModel.getServerListByType(serverType);
if (serverList && serverList.length > 0) {
ctx.body = {status: STATUS_SUC.code, data: { serverList }};
} else {
ctx.body = ctx.service.utils.exceptionResult(SERVER_NOT_FOUND);
}
}
}

View 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
}

View File

@@ -0,0 +1,23 @@
import BaseModel from './BaseModel';
import { index, getModelForClass, prop } from '@typegoose/typegoose';
/**
* 短信字段接口
*/
@index({ name: 1 })
export default class Counter extends BaseModel {
@prop({ required: true })
name: string;
@prop({ required: true, default: 1 })
seq: number;
public static async getNewCounter(name: string) {
const counter = await CounterModel.findOneAndUpdate({name}, {$inc: {seq: 1}}, {new: true, upsert: true}).lean();
return counter?.seq;
}
}
export const CounterModel = getModelForClass(Counter);

70
web-server/app/db/Game.ts Normal file
View File

@@ -0,0 +1,70 @@
import { APP_ID } from './../consts/consts';
import BaseModel from './BaseModel';
import { index, getModelForClass, prop } from '@typegoose/typegoose';
class ServerInfo {
@prop({ required: true})
name: string;
@prop({ required: true})
host: string;
@prop({ required: false})
port: number;
@prop({ required: true})
status: number;
@prop({ required: true})
createTime: Date;
@prop({ required: true})
serverType: string;
}
/**
* 用户字段接口
*/
@index({ id: 1 })
export default class Game extends BaseModel {
@prop({ required: true})
id: number;
@prop({ required: true})
name: string;
@prop({ required: true})
nameEn: string;
@prop({ required: true})
des: string;
@prop({ required: true})
serverList: Array<ServerInfo>;
@prop({ required: true})
iconUrl: string;
@prop({ required: true})
version: string;
@prop({ required: true})
versionCode: number;
public static async getServerListByType(serverType: string) {
let game = await GameModel.findOne().lean();
if (!game) {
const serverInfo: ServerInfo = {name: '常山少年', host: 'pinus_test.trgame.cn', port: 3014, status: 1, createTime: new Date(), serverType: 'official'};
const iconUrl = `https://download.tgamebox.cn/avatar/${APP_ID}/1.png`;
game = await GameModel.findOneAndUpdate({}, {id: 1, name: '赵云传', nameEn: 'zyz', des: '牛逼的战棋', iconUrl, version: '0.0.1', versionCode: 1, $push: {serverList: serverInfo}}, {upsert: true, new: true}).lean();
}
let serverList: Array<ServerInfo> = game ? game.serverList : [];
serverList = serverList.filter(item => { return item.serverType === serverType; })
return serverList;
}
//#endregion
}
export const GameModel = getModelForClass(Game);

62
web-server/app/db/Sms.ts Normal file
View File

@@ -0,0 +1,62 @@
import BaseModel from './BaseModel';
import { index, getModelForClass, prop } from '@typegoose/typegoose';
const moment = require('moment');
/**
* 短信字段接口
*/
@index({ tel: 1 })
export default class Sms extends BaseModel {
@prop({ required: true })
tel: string;
@prop({ required: true })
telHash: string;
@prop({ required: true })
code: string;
@prop({ required: true })
used: boolean;
@prop({ required: true })
updateTime: Date;
@prop({ required: true })
countToday: number;
public static async findByTel(tel: string) {
let sms = await smsModel.findOne({ tel }).lean();
return sms;
}
public static async updateByTel(tel: string, code: string, used: boolean, updateTime: Date, countToday: number) {
await smsModel.findOneAndUpdate({tel}, {code, used, updateTime, countToday}, {upsert: true});
}
public static async validateSms(tel: string, code: string) {
const record = await smsModel.findOneAndUpdate({tel, code, used: false}, {used: true});
return !!record;
}
public async timeLimit(interval: number) {
if (this.updateTime.getTime() > Date.now() - interval) {
return true;
}
return false;
}
public async cntLimit(cnt: number) {
if (this.hasSendToday() && this.countToday >= cnt) {
return true;
}
return false;
}
public async hasSendToday() {
return moment(this.updateTime).format("YYYY-MM-DD") === moment(Date.now()).format("YYYY-MM-DD");
}
}
export const smsModel = getModelForClass(Sms);

79
web-server/app/db/User.ts Normal file
View File

@@ -0,0 +1,79 @@
import { COUNTER } from './../consts/consts';
import { CounterModel } from './Counter';
import BaseModel from './BaseModel';
import { index, getModelForClass, prop } from '@typegoose/typegoose';
/**
* 用户字段接口
*/
@index({ tel: 1 })
@index({ uid: 1 })
export default class User extends BaseModel {
@prop({ required: true})
uid: number;
@prop({ required: true})
username: string;
@prop({ required: true})
token: string;
@prop({ required: true})
tel: string;
@prop({ required: true})
telHash: string;
@prop({ required: true})
lastLoginTime: Date;
@prop({ required: true})
createTime: Date;
// 平台ios, android, web, pc
@prop({ required: true})
platform: string;
@prop({ required: true})
pkgName: string;
// 服务器类型official, channel, ios, oversea
@prop({ required: true})
serverType: string;
public static async createUser() {
}
public static async updateToken(tel: string, token: string, platform: string, pkgName: string, serverType: string) {
let user = await UserModel.findOne({tel}).lean();
const curTime: Date = new Date();
let update = {};
if (!user) {
const uid = await CounterModel.getNewCounter(COUNTER.UID);
update = Object.assign(update, {platform, pkgName, serverType, createTime: curTime, uid, username: `用户${uid}`});
}
update = Object.assign(update, {token, lastLoginTime: curTime});
user = await UserModel.findOneAndUpdate({tel}, update, {upsert: true, new: true}).lean();
return user;
}
public static async findUserByToken(token: string) {
const user = await UserModel.findOne({token}).select('uid token').lean();
return user;
}
public static async findUserByTel(tel: string) {
const user = await UserModel.findOne({tel}).select('uid tel').lean();
return user;
}
public static async findUserByUid(uid: number) {
const user = await UserModel.findOne({uid}).select('uid tel').lean();
return user;
}
//#endregion
}
export const UserModel = getModelForClass(User);

View File

@@ -0,0 +1,48 @@
import { ENCRYPT_KEY, ENCRYPT_IV } from './../consts/consts';
import { Context } from 'egg';
const crypto = require('crypto');
const isJSON = require('koa-is-json');
function aesEncrypt(data, key, iv) {
const cipher = crypto.createCipheriv('aes-192-cbc', key, iv);
var crypted = cipher.update(data, 'utf8', 'hex');
crypted += cipher.final('hex');
return crypted;
}
function aesDecrypt(data, key, iv) {
const decipher = crypto.createDecipheriv('aes-192-cbc', key, iv);
var decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
module.exports = options => {
return async function parmsDecode(ctx: Context, next) {
if (options.threshold && ctx.length < options.threshold) return;
let reqBody = ctx.request.body;
if (!reqBody.data) return;
if (isJSON(reqBody)) {
let encodeStr = aesEncrypt(JSON.stringify(reqBody), ENCRYPT_KEY, ENCRYPT_IV);
console.log(`encoded str: ${encodeStr}`);
}
let decodeStr = aesDecrypt(reqBody.data, ENCRYPT_KEY, ENCRYPT_IV);
try {
ctx.request.body = JSON.parse(decodeStr);
} catch (e) {
console.error('parms parse err');
}
await next();
let resBody = ctx.body;
console.log('return value:', resBody);
if (isJSON(resBody)) {
ctx.body = {result: aesEncrypt(JSON.stringify(resBody), ENCRYPT_KEY, ENCRYPT_IV)};
} else {
ctx.body = {result: aesEncrypt(JSON.stringify({status: 3, data: 'internal err'}), ENCRYPT_KEY, ENCRYPT_IV)};
}
}
}

View File

@@ -0,0 +1,19 @@
import { STATUS_TOKEN_ERR, STATUS_WRONG_PARMS } from './../../../shared/statusCode';
import { UserModel } from './../db/User';
import { Context } from 'egg';
module.exports = () => {
return async function tokenParser(ctx: Context, next) {
if (!ctx.request.body || !ctx.request.body.token) {
ctx.body = ctx.service.utils.exceptionResult(STATUS_WRONG_PARMS);
return;
}
const user = await UserModel.findUserByToken(ctx.request.body.token);
if (!user) {
console.log('token invalid');
ctx.body = ctx.service.utils.exceptionResult(STATUS_TOKEN_ERR);
return;
}
await next();
}
}

View File

@@ -2,7 +2,9 @@ import { Application } from 'egg';
export default (app: Application) => { export default (app: Application) => {
const { controller, router } = app; const { controller, router } = app;
const tokenParser = app.middleware.tokenParser();
router.get('/', controller.home.index); router.get('/', controller.home.index);
router.post('/user/getsms', controller.account.getSms); router.post('/user/getsms', controller.account.getSms);
router.post('/user/smslogin', controller.account.smsLogin);
router.post('/game/getserverlist', tokenParser, controller.game.getServerList);
}; };

View File

@@ -0,0 +1,92 @@
import { UserModel } from './../db/User';
import { SMS_IN_60S, SMS_CNT_LIMIT, STATUS_SUC, STATUS_WRONG_PARMS, SMS_INVALID } from './../../../shared/statusCode';
import { smsModel } from './../db/Sms';
import { Service } from 'egg';
const _ = require('underscore');
/**
* Test Service
*/
export default class Auth extends Service {
public checkTelNo(telNo) {
if (!_.isString(telNo)) {
return {status: 1, data: '参数类型错误'};
}
if (telNo.length !== 11) {
return {status: 1, data: '手机号长度错误'};
}
return {status: 0, data: '手机号合法'};
}
async sendSmsCodeByGuodu(tel, code) {
const ctx = this.ctx;
let url = `http://221.179.172.68:8000/QxtSms/QxtFirewall?OperID=bantu3&OperPass=c8XcTffG&DesMobile=${tel}&Content=${encodeURIComponent(`【同人游戏】验证码${code},您正在登录赵云传,若非本人操作,请勿泄露`)}&Content_Code=1`
const result = await ctx.curl(url, {
method: 'GET',
});
return result.data;
}
/**
* 用户获取手机验证码
* @param telNo - 用户手机号
*/
public async getSms(tel: string) {
const telVerify = this.checkTelNo(tel);
if (telVerify.status !== 0) {
return telVerify;
}
let sms = await smsModel.findByTel(tel);
if (!!sms) {
if (sms.timeLimit(60000)) {
return this.ctx.service.utils.exceptionResult(SMS_IN_60S);
}
if (sms.cntLimit(8)) {
return this.ctx.service.utils.exceptionResult(SMS_CNT_LIMIT);
}
}
let code: string = '';
if (!sms || !sms.used) {
code = this.ctx.service.utils.generateNum(6);
} else {
code = sms.code;
}
await this.sendSmsCodeByGuodu(tel, code);
await smsModel.updateByTel(tel, code, false, new Date(), sms?.hasSendToday() ? sms.countToday + 1 : 1);
return this.ctx.service.utils.exceptionResult(STATUS_SUC);
}
/**
* 用户获取到手机验证码之后发送验证登录请求
* @param telNo 登录手机号
* @param code 登录验证码
*/
public async smsLogin(tel: string, code: string, platform: string, pkgName: string, serverType: string) {
const ctx = this.ctx;
// 参数检查
const telVerify = this.checkTelNo(tel);
if (telVerify.status !== 0) {
return telVerify;
}
if (!_.isString(code) || code.length !== 6) {
return ctx.service.utils.exceptionResult(STATUS_WRONG_PARMS);
}
// 手机验证码核验
const smsValid: boolean = await smsModel.validateSms(tel, code);
if (!smsValid) {
return ctx.service.utils.exceptionResult(SMS_INVALID);
}
// 用户注册登录
const token = ctx.service.utils.generateStr(256);
const user = await UserModel.updateToken(tel, token, platform, pkgName, serverType);
return {status: STATUS_SUC, data: { token, uid: user?.uid }};
}
}

View File

@@ -1,4 +1,4 @@
import { TURBO_CORE_URL, APP_ID, PARM_SECRET } from './../consts/consts'; import { TURBO_CORE_URL, APP_ID, TURBO_PARM_SECRET } from './../consts/consts';
import { Service } from 'egg'; import { Service } from 'egg';
const crypto = require('crypto'); const crypto = require('crypto');
/** /**
@@ -7,31 +7,56 @@ const crypto = require('crypto');
export default class TurboCore extends Service { export default class TurboCore extends Service {
/** /**
* sayHi to you * 用户获取手机验证码
* @param name - your name * @param telNo - 用户手机号
*/ */
public async getSms(telNo: string) { public async getSms(telNo: string) {
const ctx = this.ctx; const ctx = this.ctx;
let body = { let body = {
'telNo':telNo telNo
} };
const result = await ctx.curl(`${TURBO_CORE_URL}/user/getSms`, { const result = await ctx.curl(`${TURBO_CORE_URL}/user/getSms`, {
// 必须指定 method
method: 'POST', method: 'POST',
// 通过 contentType 告诉 HttpClient 以 JSON 格式发送
contentType: 'json', contentType: 'json',
headers: { headers: {
'AppId': APP_ID, 'AppId': APP_ID,
'sign': this.getTurboSign( body, PARM_SECRET) 'sign': this.getTurboSign( body, TURBO_PARM_SECRET)
}, },
data: body, data: body,
// 明确告诉 HttpClient 以 JSON 格式处理返回的响应 body
dataType: 'json', dataType: 'json',
}); });
return result.data; return result.data;
} }
/**
* 用户获取到手机验证码之后发送验证登录请求
* @param telNo 登录手机号
* @param code 登录验证码
*/
public async smsLogin(telNo: string, code: string) {
const ctx = this.ctx;
let body = {
telNo, code
};
const result = await ctx.curl(`${TURBO_CORE_URL}/user/getSms`, {
method: 'POST',
contentType: 'json',
headers: {
'AppId': APP_ID,
'sign': this.getTurboSign( body, TURBO_PARM_SECRET)
},
data: body,
dataType: 'json',
});
return result.data;
}
/**
* 获取多宝规则下的参数签名
* @param params 参数列表
* @param secret 密钥
*/
private getTurboSign(params, secret) { private getTurboSign(params, secret) {
let paramsString = this.joinParamsStr(params); let paramsString = this.joinParamsStr(params);
let stringToSign = paramsString; let stringToSign = paramsString;
@@ -46,6 +71,10 @@ export default class TurboCore extends Service {
} }
} }
/**
* 将参数组合成字符串
* @param params 参数列表
*/
private joinParamsStr(params) { private joinParamsStr(params) {
let signString = Object.keys(params).filter(function (key) { let signString = Object.keys(params).filter(function (key) {
return params[key] !== undefined && params[key] !== '' && ['pfx', 'partner_key', 'sign', 'key'].indexOf(key) < 0; return params[key] !== undefined && params[key] !== '' && ['pfx', 'partner_key', 'sign', 'key'].indexOf(key) < 0;

View File

@@ -0,0 +1,32 @@
import { Service } from 'egg';
const csprng = require('csprng');
/**
* Utils Service
*/
export default class Utils extends Service {
/**
* 生成 len 长度的随机字符串
* @param len 长度
* @param radix 基数
*/
public generateStr(len: number, radix: number = 36) {
return csprng(len, radix);
}
/**
* 生成指定长度的随机数
* @param len 随机数长度
*/
public generateNum(len: number) {
let code = "";
for (let i = 0; i < len; i++) {
code += parseInt(`${Math.random() * 10}`);
}
return code;
}
public exceptionResult(status) {
const { code, simStr} = status;
return {status: code, data: simStr};
}
}

View File

@@ -12,7 +12,12 @@ export default (appInfo: EggAppInfo) => {
}, },
}; };
// add your egg config in here // add your egg config in here
config.middleware = []; config.middleware = ['parmsDecode'];
config.mongoose = {
url: 'mongodb://root:zyz_2020@dds-8vbdb47c6fb58a541.mongodb.zhangbei.rds.aliyuncs.com:3717,dds-8vbdb47c6fb58a542.mongodb.zhangbei.rds.aliyuncs.com:3717/admin?replicaSet=mgset-500808098', // 内网
options: {},
};
// add your special config in here // add your special config in here
const bizConfig = { const bizConfig = {

View File

@@ -2844,6 +2844,14 @@
"which": "^1.2.9" "which": "^1.2.9"
} }
}, },
"csprng": {
"version": "0.1.2",
"resolved": "https://registry.npm.taobao.org/csprng/download/csprng-0.1.2.tgz",
"integrity": "sha1-S8aPEvo2jSUqWYQcusqXSxirReI=",
"requires": {
"sequin": "*"
}
},
"csrf": { "csrf": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npm.taobao.org/csrf/download/csrf-3.1.0.tgz", "resolved": "https://registry.npm.taobao.org/csrf/download/csrf-3.1.0.tgz",
@@ -9137,6 +9145,11 @@
"upper-case-first": "^1.1.2" "upper-case-first": "^1.1.2"
} }
}, },
"sequin": {
"version": "0.1.1",
"resolved": "https://registry.npm.taobao.org/sequin/download/sequin-0.1.1.tgz",
"integrity": "sha1-XC04nWajg3NOqvvEXt6ywcsb5wE="
},
"serialize-json": { "serialize-json": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npm.taobao.org/serialize-json/download/serialize-json-1.0.3.tgz", "resolved": "https://registry.npm.taobao.org/serialize-json/download/serialize-json-1.0.3.tgz",
@@ -10096,6 +10109,11 @@
"random-bytes": "~1.0.0" "random-bytes": "~1.0.0"
} }
}, },
"underscore": {
"version": "1.10.2",
"resolved": "https://registry.npm.taobao.org/underscore/download/underscore-1.10.2.tgz?cache=0&sync_timestamp=1585605854253&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funderscore%2Fdownload%2Funderscore-1.10.2.tgz",
"integrity": "sha1-c9aqNmjzGI5K2w8ZQ70Sz9fvqq8="
},
"unescape": { "unescape": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npm.taobao.org/unescape/download/unescape-1.0.1.tgz", "resolved": "https://registry.npm.taobao.org/unescape/download/unescape-1.0.1.tgz",

View File

@@ -22,8 +22,10 @@
"clean": "ets clean" "clean": "ets clean"
}, },
"dependencies": { "dependencies": {
"csprng": "^0.1.2",
"egg": "^2.6.1", "egg": "^2.6.1",
"egg-scripts": "^2.6.0" "egg-scripts": "^2.6.0",
"underscore": "^1.10.2"
}, },
"devDependencies": { "devDependencies": {
"@types/mocha": "^2.2.40", "@types/mocha": "^2.2.40",

View File

@@ -3,11 +3,13 @@
import 'egg'; import 'egg';
import ExportAccount from '../../../app/controller/account'; import ExportAccount from '../../../app/controller/account';
import ExportGame from '../../../app/controller/game';
import ExportHome from '../../../app/controller/home'; import ExportHome from '../../../app/controller/home';
declare module 'egg' { declare module 'egg' {
interface IController { interface IController {
account: ExportAccount; account: ExportAccount;
game: ExportGame;
home: ExportHome; home: ExportHome;
} }
} }

View File

@@ -0,0 +1,13 @@
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
import ExportParmsDecode from '../../../app/middleware/parmsDecode';
import ExportTokenParser from '../../../app/middleware/tokenParser';
declare module 'egg' {
interface IMiddleware {
parmsDecode: typeof ExportParmsDecode;
tokenParser: typeof ExportTokenParser;
}
}

View File

@@ -6,12 +6,16 @@ type AnyClass = new (...args: any[]) => any;
type AnyFunc<T = any> = (...args: any[]) => T; type AnyFunc<T = any> = (...args: any[]) => T;
type CanExportFunc = AnyFunc<Promise<any>> | AnyFunc<IterableIterator<any>>; type CanExportFunc = AnyFunc<Promise<any>> | AnyFunc<IterableIterator<any>>;
type AutoInstanceType<T, U = T extends CanExportFunc ? T : T extends AnyFunc ? ReturnType<T> : T> = U extends AnyClass ? InstanceType<U> : U; type AutoInstanceType<T, U = T extends CanExportFunc ? T : T extends AnyFunc ? ReturnType<T> : T> = U extends AnyClass ? InstanceType<U> : U;
import ExportAuth from '../../../app/service/Auth';
import ExportTest from '../../../app/service/Test'; import ExportTest from '../../../app/service/Test';
import ExportTurboCore from '../../../app/service/TurboCore'; import ExportTurboCore from '../../../app/service/TurboCore';
import ExportUtils from '../../../app/service/Utils';
declare module 'egg' { declare module 'egg' {
interface IService { interface IService {
auth: AutoInstanceType<typeof ExportAuth>;
test: AutoInstanceType<typeof ExportTest>; test: AutoInstanceType<typeof ExportTest>;
turboCore: AutoInstanceType<typeof ExportTurboCore>; turboCore: AutoInstanceType<typeof ExportTurboCore>;
utils: AutoInstanceType<typeof ExportUtils>;
} }
} }