diff --git a/game-server/app/servers/activity/handler/gachaHandler.ts b/game-server/app/servers/activity/handler/gachaHandler.ts index d40afad90..982fe77f5 100644 --- a/game-server/app/servers/activity/handler/gachaHandler.ts +++ b/game-server/app/servers/activity/handler/gachaHandler.ts @@ -66,6 +66,7 @@ export class GachaHandler { let resultList: GachaResultIndb[] = [], heroInfo: CreateHeroParam[] = [], items: RewardInter[] = [], consume: ItemInter[] = []; for(let { dic, min, max } of getDicGachas(dicGacha.gachaType, historyCount, count)) { + if(dicGacha.gachaType != GACHA_TYPE.NORMAL && dic.id != gachaId) continue; // console.log('##########', dic, min, max) let _count = max + 1 - min; let gachaPull = new GachaPull(dic, { hope, floor, pickHero }); diff --git a/game-server/app/servers/chat/handler/chatHandler.ts b/game-server/app/servers/chat/handler/chatHandler.ts index 34ba4eaa9..68764a496 100644 --- a/game-server/app/servers/chat/handler/chatHandler.ts +++ b/game-server/app/servers/chat/handler/chatHandler.ts @@ -1,7 +1,7 @@ import { CHANNEL_PREFIX, MSG_SOURCE, getChannelType } from './../../../consts/constModules/chatConst'; import { Application, BackendSession, HandlerService, } from 'pinus'; import { genCode, resResult } from '../../../pubUtils/util'; -import { DEFAULT_MSG_PER_PAGE, STATUS, TASK_TYPE } from '../../../consts'; +import { DEFAULT_MSG_PER_PAGE, SDK_PUSH_MSG_TYPE, SDK_PUSH_TARGET_TYPE, STATUS, TASK_TYPE } from '../../../consts'; import { createAccuseData, createGroupMsg, createPrivateMsg, getPrivateMessages, pushGroupMsgToRoom, pushMsgToRole, updatePrivateMsgReadInfo, recentPrivateChatInfos, recentWorldMsgs, recentSysMsgs, recentGuildMsgs, updatePrivateMsgIsTop, delPrivateMsg, recentServerGroupMsgs, recentLeagueMsgs } from '../../../services/chatService'; import { getSimpleRoleInfo } from '../../../services/roleService'; import { checkTask } from '../../../services/task/taskService'; @@ -11,6 +11,9 @@ import { GVGLeagueModel } from '../../../db/GVGLeague'; import { getAllGroupOfServer } from '../../../services/serverService'; import { getGuildCodeString } from '../../../services/gvg/gvgRecService'; import { GroupMessageType } from '../../../db/GroupMessage'; +import { pushMsg37 } from '../../../services/sdkService'; +import { gameData } from '../../../pubUtils/data'; +import { pushClientMsg } from '../../../services/pushService'; export default function (app: Application) { @@ -212,4 +215,15 @@ export class ChatHandler { if (!accuseRec) return resResult(STATUS.WRONG_PARMS); return resResult(STATUS.SUCCESS, accuseRec); } + + // 客户端推送消息 + async debugPushMessage(msg: { uid: number }, session: BackendSession) { + let dic = gameData.dicPushMessage.get(SDK_PUSH_MSG_TYPE.GUILD_ACTIVITY_START); + if(!dic) return resResult(STATUS.WRONG_PARMS); + // let result = await pushMsg37(Date.now(), dic, SDK_PUSH_TARGET_TYPE.SINGLE, `${msg.uid}`); + + pushClientMsg(SDK_PUSH_MSG_TYPE.GVG_BATTLE_START); + + return resResult(STATUS.SUCCESS); + } } diff --git a/game-server/app/services/checkParam.ts b/game-server/app/services/checkParam.ts index 824689d48..3a268e06f 100644 --- a/game-server/app/services/checkParam.ts +++ b/game-server/app/services/checkParam.ts @@ -2053,6 +2053,7 @@ export function checkRouteParam(route: string, msg: any) { case "guild.gvgBattleHandler.debugAddRobots": case "guild.gvgBattleHandler.debugMoveRobots": case "guild.gvgBattleHandler.debugStopMoveRobot": + case "chat.chatHandler.debugPushMessage": { if (msg.magicWord !== DEBUG_MAGIC_WORD || !isDevelopEnv()) return false; diff --git a/game-server/app/services/pushService.ts b/game-server/app/services/pushService.ts index a36b50eac..0924f86b2 100644 --- a/game-server/app/services/pushService.ts +++ b/game-server/app/services/pushService.ts @@ -1,12 +1,17 @@ import { Channel, pinus } from "pinus"; -import { CHANNEL_PREFIX, PUSH_BATCH, PUSH_INTERVAL, PUSH_ROUTE, STATUS } from "../consts"; +import { CHANNEL_PREFIX, PUSH_BATCH, PUSH_INTERVAL, PUSH_ROUTE, SDK_PUSH_MSG_PLAYER_TYPE, SDK_PUSH_MSG_TYPE, SDK_PUSH_TARGET_TYPE, STATUS } from "../consts"; import { genCode, resResult } from "../pubUtils/util"; import { getCityChannelSid, getGuildChannelSid, getWorldChannelSid, groupRoomId, getGroupShopSid, getGVGAreaChannelSid, getGVGAreaTeamChannelSid, getGVGCityTeamChannelSid } from "./chatService"; import { getAllOnlineRoles, getAllServers, getRoleOnlineInfo } from "./redisService"; import { errlogger, infologger } from '../util/logger'; import { MsgEncrypt } from "../pubUtils/sysUtil"; -import { isSkipEncode } from "../pubUtils/sdkUtil"; +import { isSkipEncode, needPushMsg } from "../pubUtils/sdkUtil"; import { isDevelopEnv } from "./utilService"; +import { gameData } from "../pubUtils/data"; +import { pushMsg37 } from "./sdkService"; +import { RoleModel, RoleType } from "../db/Role"; +import { nowSeconds } from "../pubUtils/timeUtil"; +import { GVGLeagueModel } from "../db/GVGLeague"; export async function sendMessageToAllWithSuc(route: string, data: any, filterCb?: ({ lv, topLineupCe }) => boolean) { await sendMessageToAll(route, resResult(STATUS.SUCCESS, data), filterCb); @@ -260,4 +265,106 @@ function checkNotEncryptRoute(event: string) { PUSH_ROUTE.GVG_CITY_RANK_UPDATE, // onGVGCityRankUpdate 城池积分排名 PUSH_ROUTE.GVG_SPINE_ATTACKED, // onSpinesAttacked 队伍在地图上受到攻击 ].indexOf(event) != -1 +} + +/** + * 推送客户端下拉消息 + * @param type SDK_PUSH_MSG_TYPE + * @returns + */ + +export async function pushClientMsg(type: SDK_PUSH_MSG_TYPE) { + let dicPushMessage = gameData.dicPushMessage.get(type); + if(!dicPushMessage) return; + let targetObj = await getPushTarget(dicPushMessage.playerType); + if(!targetObj) return; + if(!needPushMsg()) return; + let { target, audiences } = targetObj; + let t = Date.now(); + for(let audience of audiences) { + if(audience) await pushMsg37(t, dicPushMessage, target, audience); + } +} + +async function getPushTarget(playerType: number): Promise<{ target: SDK_PUSH_TARGET_TYPE, audiences: string[] }> { + let uids: number[] = []; + switch(playerType) { + case SDK_PUSH_MSG_PLAYER_TYPE.HAS_GUILD: + { uids = await getHasGuildPlayers(); break; } + case SDK_PUSH_MSG_PLAYER_TYPE.HAS_LEAGUE: + { uids = await getHasLeaguePlayers(); break; } + case SDK_PUSH_MSG_PLAYER_TYPE.AFK: + { uids = await getAfkPlayers(); break; } + case SDK_PUSH_MSG_PLAYER_TYPE.HAS_LEAGUE: + { uids = await getActivePlayers(); break; } + } + let len = uids.length; + if(len == 0) return null; + if(len == 1) return { target: SDK_PUSH_TARGET_TYPE.SINGLE, audiences: [uids.join()] }; + let audiences: string[] = []; + for(let i = 0; i < Math.ceil(len/200); i++) { + audiences.push(uids.slice(i * 200, i * 200 + 200 - 1).join()) + } + return { target: SDK_PUSH_TARGET_TYPE.LIST, audiences }; +} + +// 有军团且24小时内登录过但当前未在线的玩家 +async function getHasGuildPlayers() { + let uids: number[] = [], players: RoleType[] = []; + let createdAt: Date; + while(!createdAt || players.length > 0) { + players = await RoleModel.findHasGuildPlayers(createdAt); + if(players.length == 0) break; + createdAt = players[players.length -1].createdAt; + players.forEach(player => { + if(player.userInfo && player.userInfo.channelInfo && player.quitTime != player.loginTime) uids.push(player.userInfo.channelInfo.uid); + }); + } + return uids; +} + +// 有联军且48小时内登录过但当前未在线的玩家 +async function getHasLeaguePlayers() { + let leagues = await GVGLeagueModel.findActiveLeagueMembers(); + let uids: number[] = [], roleIds: string[] = []; + for(let { members } of leagues) roleIds.push(...members.map(member => member.roleId)); + let len = roleIds.length; + for(let i = 0; i < Math.ceil(len/1000); i++) { + let curRoleIds = roleIds.slice(i * 1000, i * 1000 + 1000 - 1); + let players = await RoleModel.findByRoleIds(curRoleIds, 'userInfo.channelInfo'); + players.forEach(player => { + if(player.userInfo && player.userInfo.channelInfo && player.quitTime != player.loginTime) uids.push(player.userInfo.channelInfo.uid); + }); + } + return uids; +} + +// 至少48小时未上线且等级>=20级的玩家 +async function getAfkPlayers() { + let uids: number[] = [], players: RoleType[] = []; + let createdAt: Date; + while(!createdAt || players.length > 0) { + players = await RoleModel.findAfkPlayers(createdAt); + if(players.length == 0) break; + createdAt = players[players.length -1].createdAt; + players.forEach(player => { + if(player.userInfo && player.userInfo.channelInfo && player.quitTime != player.loginTime) uids.push(player.userInfo.channelInfo.uid); + }); + } + return uids; +} + +// 24小时内登录过但当前未在线的玩家 +async function getActivePlayers() { + let uids: number[] = [], players: RoleType[] = []; + let createdAt: Date; + while(!createdAt || players.length > 0) { + players = await RoleModel.findActivePlayers(createdAt); + if(players.length == 0) break; + createdAt = players[players.length -1].createdAt; + players.forEach(player => { + if(player.userInfo && player.userInfo.channelInfo && player.quitTime != player.loginTime) uids.push(player.userInfo.channelInfo.uid); + }); + } + return uids; } \ No newline at end of file diff --git a/game-server/app/services/pvpService.ts b/game-server/app/services/pvpService.ts index c6363cbc7..bd8c8d713 100644 --- a/game-server/app/services/pvpService.ts +++ b/game-server/app/services/pvpService.ts @@ -229,7 +229,8 @@ async function generPlayerOppHis(pvpdefense: PvpDefenseType, roleId: string, pos let h = defenseHeroes.find(cur => cur.actorId == dbHero.hid); // 阵容里是否有这个武将 let hs = heroScores.find(cur => cur.hid == dbHero.hid); // 这个武将是否有这个得分 if (!!h) { - let mapWarJson = gameData.warJson.get(warId); + let dicWar = gameData.war.get(warId); + let mapWarJson = gameData.warJson.get(dicWar.dispatchJsonId); let warJson = mapWarJson.find(cur => cur.dataId == h.dataId); if (warJson && warJson.relation == 2) { let heroInfo = new PvpHeroInfo(); diff --git a/game-server/app/services/sdkService.ts b/game-server/app/services/sdkService.ts index 481c24abe..aa7e8be18 100644 --- a/game-server/app/services/sdkService.ts +++ b/game-server/app/services/sdkService.ts @@ -1,12 +1,12 @@ /********** 37sdk **********/ import { RoleModel, RoleType } from "../db/Role"; -import { Chat37Params, CheckGuild37Params, CheckName37Params, GetWordParam } from "../domain/sdk"; +import { Chat37Params, CheckGuild37Params, CheckName37Params, GetWordParam, PushMsg37Param } from "../domain/sdk"; import { sendMailByContent, sendMailToGuildByContent } from './mailService'; import { NAMEPLATE } from '../pubUtils/dicParam'; -import { CHANNEL_PREFIX, FILENAME, getSdkChannelId, MAIL_TYPE, PUSH_ROUTE, REDIS_KEY, SDK_37_ADDR, SDK_37_CONST, SDK_TA_CONST, STATUS, TA_EVENT, TA_USERSET_TYPE, THINKING_DATA_MODE, THINKING_DATA_MODE_LIST } from "../consts"; +import { CHANNEL_PREFIX, FILENAME, getSdkChannelId, MAIL_TYPE, PUSH_ROUTE, REDIS_KEY, SDK_37_ADDR, SDK_37_CONST, SDK_PUSH_TARGET_TYPE, SDK_TA_CONST, STATUS, TA_EVENT, TA_USERSET_TYPE, THINKING_DATA_MODE, THINKING_DATA_MODE_LIST } from "../consts"; import { UserModel } from "../db/User"; -import { request37CheckChat, request37GetWord, request37Post } from "../pubUtils/httpUtil"; +import { request37CheckChat, request37GetWord, request37Post, request37PushMessage } from "../pubUtils/httpUtil"; import { GuildModel } from "../db/Guild"; import { getRoleOnlineInfo, updateUserInfo } from "./redisService"; import { Application, pinus } from "pinus"; @@ -21,6 +21,7 @@ import { pushGuildInfoUpdate } from "./guildService"; import { sendMessageToUserWithSuc } from "./pushService"; import { GuildLeader } from "../domain/rank"; import { initTaLoggingMode } from "./sdk/ta"; +import { DicPushMessage } from "../pubUtils/dictionary/DicPushMessage"; // 检查私聊是否合法 @@ -163,6 +164,13 @@ export async function treatGuildName(content: string) { } } +// 37接口 消息推送 +export async function pushMsg37(time: number, dic: DicPushMessage, target: SDK_PUSH_TARGET_TYPE, audience: string) { + let body = new PushMsg37Param(time); + body.setMsgInfo(dic, target, audience); + return await request37PushMessage(SDK_37_ADDR.PUSH_MSG, body, SDK_37_CONST.PUSH_KEY); +} + export function connectThinkingData(app: Application) { let ta; if(app.get('env') != 'development') { diff --git a/game-server/app/services/timeTaskService.ts b/game-server/app/services/timeTaskService.ts index 593baf6bb..1d93ec3d1 100644 --- a/game-server/app/services/timeTaskService.ts +++ b/game-server/app/services/timeTaskService.ts @@ -5,7 +5,7 @@ import { nowSeconds, getTimeFun, getSeconds } from '../pubUtils/timeUtil'; import { getTodayGuildActivity, gameData } from '../pubUtils/data'; import { pvpSeasonEnd } from './pvpService'; import { getAllOnlineRoles, getAllServers, delGuildActivityRank, getServerCreateTime } from './redisService'; -import { GUILD_ACTIVITY_TYPE, REFRESH_TIME, COUNTER, AUCTION_TIME, GM_MAIL_TYPE, SERVER_TIMER, ACTIVITY_TYPE, PUSH_ROUTE, STATUS, LADDER_STATUS, LADDER_SERVER_GAP_TIME, GVG_PERIOD } from '../consts'; +import { GUILD_ACTIVITY_TYPE, REFRESH_TIME, COUNTER, AUCTION_TIME, GM_MAIL_TYPE, SERVER_TIMER, ACTIVITY_TYPE, PUSH_ROUTE, STATUS, LADDER_STATUS, LADDER_SERVER_GAP_TIME, GVG_PERIOD, SDK_PUSH_MSG_TYPE } from '../consts'; import { pinus } from 'pinus'; import { settleGuildWeekly } from './guildService'; import { SendMailFun, sendMailsByGmMail, } from './mailService'; @@ -31,7 +31,7 @@ import { ActivityModel, ActivityModelType } from '../db/Activity'; import { TimeLimitRankData } from '../domain/activityField/timeLimitRankField'; import { sendRankMail, takeSnapshot } from './activity/timeLimitRankService'; import { ActivityGroupModel } from '../db/ActivityGroup'; -import { sendMessageToServer } from './pushService'; +import { pushClientMsg, sendMessageToServer } from './pushService'; import { getRandEelm, getRandSingleEelm, resResult } from '../pubUtils/util'; import { checkPopUpConditionWhenGuildActivityEnd } from './activity/popUpShopService'; import { pushRefreshTime } from './connectorService'; @@ -113,6 +113,9 @@ export async function init() { // gvg每周日 initGVGConfigSchedule(); + + // 定时推送消息 + initPushMsgSchedule(); } // 每日刷新 @@ -323,6 +326,7 @@ export async function guildActivityStart(dicGuildActivity?: DicGuildActivity) { for (let serverId of servers) { await sendGuildActivityStatus(serverId); } + pushClientMsg(SDK_PUSH_MSG_TYPE.GUILD_ACTIVITY_START); return true; } @@ -963,6 +967,9 @@ export async function gvgBattleStartSchedule() { if(gvgBattleCatapultJob) gvgBattleCatapultJob.cancel(); gvgBattleCatapultJob = scheduleJob('gvgBattleCatapult', `*/${GVG.GVG_CATAPULT_TIME} * * * * *`, gvgBattleCatapult); + setTimeout(() => { + pushClientMsg(SDK_PUSH_MSG_TYPE.GVG_BATTLE_START); + }, GVG.GVG_GUARD_START_TIME * 1000); } // 每隔5秒的积分计算定时器 @@ -994,4 +1001,14 @@ export async function gvgBattleEndSchedule() { } } -// —————————————— gvg end —————————————— // \ No newline at end of file +// —————————————— gvg end —————————————— // + +async function initPushMsgSchedule() { + scheduleJob('sendAfkPlayers', '0 0 19 * * ?', async () => { + pushClientMsg(SDK_PUSH_MSG_TYPE.AFK_ATTENTION); + pushClientMsg(SDK_PUSH_MSG_TYPE.AP_DINNER); + }); + scheduleJob('sendAfkPlayers', '0 0 12 * * ?', async () => { + pushClientMsg(SDK_PUSH_MSG_TYPE.AP_LUNCH); + }); +} \ No newline at end of file diff --git a/shared/consts/constModules/httpConst.ts b/shared/consts/constModules/httpConst.ts index baecd5838..df9ca1339 100644 --- a/shared/consts/constModules/httpConst.ts +++ b/shared/consts/constModules/httpConst.ts @@ -18,6 +18,7 @@ export enum SDK_37_ADDR { CHECK_NAME = 'http://api.gamechat.37.com/checkName.php', CHECK_UNION = 'http://api.gamechat.37.com/checkUnion.php', GET_WORD = 'http://api.gamechat.37.com/getWord.php', + PUSH_MSG = 'https://pushdata.37.com/index.php?c=pushmessage&a=push_message', } export enum SDK_37_CONST { GAME_ID = 165, // 研发使用的GAME_ID @@ -29,6 +30,8 @@ export enum SDK_37_CONST { CHAT_KEY = '3PerD)H!JdTC_pEUnZl8vXKgasj5;7Q~', // 聊天KEY IOS_PID = 'zyz', BACKEND_KEY = 'zyzbackend2022', + PUSH_KEY = '7mV3FzL7drRxTPkoBPkcRXpeTapG2jk9', // 推送消息KEY + PUSH_GAME_ID = 814, // 推送消息的GAME_ID } export enum SDK_TA_CONST { @@ -37,4 +40,26 @@ export enum SDK_TA_CONST { LOG_PATH = '/zyz_logs/ta', } -export const WJX_KEY = "f98551ef-4c7a-4ae2-abda-b7cca2684fe6" \ No newline at end of file +export const WJX_KEY = "f98551ef-4c7a-4ae2-abda-b7cca2684fe6" + +export enum SDK_PUSH_TARGET_TYPE { + SINGLE = 'single', // 单个玩家 + LIST = 'list', // 一批玩家 + ALL = 'all', // 所有玩家 +} + +export enum SDK_PUSH_MSG_TYPE { + GUILD_ACTIVITY_START = 'guildActivityStart', // 军团活动开始 + GVG_BATTLE_START = 'gvgBattleStart', // 逐鹿中原激战期开启 + AFK_ATTENTION = 'afkAttention', // 玩家两天未登录 + AP_MAX = 'apMax', // 体力满了 + AP_LUNCH = 'apLunch', // 领取午饭 + AP_DINNER = 'apDinner', // 领取晚饭 +} + +export enum SDK_PUSH_MSG_PLAYER_TYPE { + HAS_GUILD = 1, // 有军团且24小时内登录过但当前未在线的玩家 + HAS_LEAGUE = 2, // 有联军且48小时内登录过但当前未在线的玩家 + AFK = 3, // 至少48小时未上线且等级>=20级的玩家 + ACTIVE_PLAYER = 4, // 24小时内登录过但当前未在线的玩家 +} \ No newline at end of file diff --git a/shared/consts/constModules/sysConst.ts b/shared/consts/constModules/sysConst.ts index b14682cbd..d3d13a519 100644 --- a/shared/consts/constModules/sysConst.ts +++ b/shared/consts/constModules/sysConst.ts @@ -621,6 +621,7 @@ export const FILENAME = { DIC_GVG_BATTLE_RANK_REWARD: 'dic_zyz_GVGBattleRankReward', DIC_GK_GVGBATTLE: 'dic_zyz_gk_GVGBattle', DIC_GVG_VESTIGE_PLAYER_RANK: 'dic_zyz_GVGVestigePlayerRank', + DIC_PUSH_MESSAGE: 'dic_zyz_pushMessage', } export const WAR_RELATE_TABLES = [ diff --git a/shared/db/GVGLeague.ts b/shared/db/GVGLeague.ts index 61e3b50c4..b72be4411 100644 --- a/shared/db/GVGLeague.ts +++ b/shared/db/GVGLeague.ts @@ -144,6 +144,14 @@ export default class GVGLeague extends BaseModel { return leagues; } + public static async findActiveLeagueMembers() { + const leagues: { members: Member[] }[] = await GVGLeagueModel.aggregate([ + { $match: { status: 1 } }, + { $project: { members: 1 } } + ]); + return leagues; + } + public static async quitGuild(leagueCode: string, guild: GuildType) { const { code, memberCnt, members } = guild; const league: GVGLeagueType = await GVGLeagueModel.findOneAndUpdate({ leagueCode, status: 1 }, diff --git a/shared/db/Role.ts b/shared/db/Role.ts index 5645dd55c..5dd3795d8 100644 --- a/shared/db/Role.ts +++ b/shared/db/Role.ts @@ -111,6 +111,8 @@ export class Teraph { @index({ topLineupCe: 1, updatedAt: 1 }) @index({ ce: -1 }) @index({ 'userInfo.uid': 1, serverId: 1 }) +@index({ loginTime: 1 }) +@index({ createdAt: 1 }) export default class Role extends BaseModel { @@ -343,6 +345,10 @@ export default class Role extends BaseModel { @prop({ required: false }) fixedIpLocation: string; + // 是否发送过推送 + @prop({ required: false }) + hasPushMsg: boolean; + public static async findAllByUid(uid: number, getters = false, virtuals = true) { const role: RoleType[] = await RoleModel.find({ 'userInfo.uid': uid }).select('roleId roleName serverId head frame spine heads frames spines lv updatedAt').lean({ getters, virtuals }); return role; @@ -817,6 +823,25 @@ export default class Role extends BaseModel { let rec: RoleType = await RoleModel.findOneAndUpdate({ roleId }, { $push: { receivedWarIds: { $each: ids } } }, { new: true }).lean(); return rec; } + + public static async findHasGuildPlayers(createdAt?: Date) { + let filter = createdAt? { createdAt: { $gt: createdAt } }: {}; + let roles: RoleType[] = await RoleModel.find({ loginTime: { $gte: nowSeconds() - 24 * 60 * 60 }, ...filter, hasGuild: true }).sort({ createdAt: 1 }).select('userInfo.channelInfo createdAt').lean(); + return roles; + } + + public static async findAfkPlayers(createdAt?: Date) { + let filter = createdAt? { createdAt: { $gt: createdAt } }: {}; + let roles: RoleType[] = await RoleModel.find({ loginTime: { $lt: nowSeconds() - 48 * 60 * 60 }, lv: { $gte: 20 }, hasPushMsg: { $exists: false }, ...filter }).sort({ createdAt }).select('userInfo.channelInfo createdAt').lean(); + await RoleModel.updateMany({ _id: roles.map(cur => cur._id) }, { $set: { hasPushMsg: true } }); + return roles; + } + + public static async findActivePlayers(createdAt?: Date) { + let filter = createdAt? { createdAt: { $gt: createdAt } }: {}; + let roles: RoleType[] = await RoleModel.find({ loginTime: { $gte: nowSeconds() - 24 * 60 * 60 }, ...filter }).sort({ createdAt: 1 }).select('userInfo.channelInfo createdAt').lean(); + return roles; + } } export const RoleModel = getModelForClass(Role); diff --git a/shared/domain/sdk.ts b/shared/domain/sdk.ts index e0eafc705..cfd6bcd4f 100644 --- a/shared/domain/sdk.ts +++ b/shared/domain/sdk.ts @@ -1,7 +1,8 @@ import { prop } from "@typegoose/typegoose"; -import { SDK_37_CONST } from "../consts"; +import { SDK_37_CONST, SDK_PUSH_TARGET_TYPE } from "../consts"; import { RoleType } from "../db/Role"; import { UserType } from "../db/User"; +import { DicPushMessage } from "../pubUtils/dictionary/DicPushMessage"; import { nowSeconds } from "../pubUtils/timeUtil"; @@ -378,4 +379,44 @@ export class IOSRefundParam { return { appid, uid, game_id, sid, actor_id, order_id, order_no, money, game_coin, product_id, time, ext } } +} + +export class PushMsg37Param { + notify_id: string; // 消息唯一标号,毫秒时间戳 + game_id: number; // 游戏id + c_game_id: number; // 子游戏id + title: string; // 消息标题 + text: string; // 消息正文 + target: SDK_PUSH_TARGET_TYPE; // 推送目标类型 + audience: string; // 用户uid,最多200个 + click_type: string; // 点击通知后续动作 + url: string; // 网页地址 + intent: string; // 打开特定页面 + time: string; // 当前请求时间 + sign: string; // 签名 + source: number; // 推送来源表 + type: string; // 推送内容 + + constructor(time: number) { + this.notify_id = time.toString(); + this.game_id = SDK_37_CONST.PUSH_GAME_ID; + this.c_game_id = SDK_37_CONST.FX_C_GAME_ID; + this.time = Math.floor(time/1000).toString(); + this.source = 0; // 0: 游戏内 + } + + public setMsgInfo(dic: DicPushMessage, target: SDK_PUSH_TARGET_TYPE, audience: string) { + this.title = dic.title; + this.text = dic.description; + this.target = target; + this.audience = audience; + this.click_type = dic.clickType; + if(dic.url != '&') this.url = dic.url; + if(dic.intent != '&') this.intent = dic.intent; + this.type = dic.pushMsgType; + } + + public setSign(sign: string) { + this.sign = sign; + } } \ No newline at end of file diff --git a/shared/pubUtils/data.ts b/shared/pubUtils/data.ts index a6dc89f8f..9449d2d84 100644 --- a/shared/pubUtils/data.ts +++ b/shared/pubUtils/data.ts @@ -135,6 +135,7 @@ import { DicGVGVestigeLeagueRank, dicGVGVestigeLeagueRank, loadGVGVestigeLeagueR import { DicGVGVestigePlayerRank, dicGVGVestigePlayerRank, loadGVGVestigePlayerRank } from "./dictionary/DicGVGVestigePlayerRank"; import { dicGVGAreaPoint, loadGVGAreaPoint, dicGVGPointsByAreaId } from "./dictionary/DicGVGAreaPoint"; import { DicGVGBattleRankReward, dicGVGBattleRankReward, loadGVGBattleRankReward } from './dictionary/DicGVGBattleRankReward'; +import { dicPushMessage, loadPushMessage } from './dictionary/DicPushMessage'; export const gameData = { daily: dicDaily, @@ -343,6 +344,7 @@ export const gameData = { gvgTeamDurability: new Map(), gvgPointByAreaId: dicGVGPointsByAreaId, gvgReviveGold: new Map(), + dicPushMessage: dicPushMessage }; // 在此提供一些原先在gamedata中提供的方法,以便更方便获取gameData数据 @@ -1486,6 +1488,7 @@ function loadDatas() { loadGVGVestigePlayerRank(); loadGVGAreaPoint(); loadGVGBattleRankReward(); + loadPushMessage(); } // 重载dicParam diff --git a/shared/pubUtils/dictionary/DicPushMessage.ts b/shared/pubUtils/dictionary/DicPushMessage.ts new file mode 100644 index 000000000..f59735140 --- /dev/null +++ b/shared/pubUtils/dictionary/DicPushMessage.ts @@ -0,0 +1,24 @@ +import { readFileAndParse } from '../util' +import { FILENAME } from '../../consts' + +export interface DicPushMessage { + id: number; + pushMsgType: string; // 推送类型标签,PUSH_MSG_TYPE + title: string; // 推送标题,30字以内 + description: string; // 推送正文,100字以内,其他同上 + playerType: number; // 推送玩家的类型,PLAYER_TYPE + clickType: string; // 点击通知之后的后续动作 + url: string; // 玩家点击通知之后,如果clickType选了url填写网址,长度<=1024 + intent: string; // 玩家点击通知之后,如果clickType选了intent填写应用特定页面,需要找客户端定制,长度<=1024 +} + +export const dicPushMessage = new Map(); +export function loadPushMessage() { + dicPushMessage.clear(); + let arr = readFileAndParse(FILENAME.DIC_PUSH_MESSAGE); + + arr.forEach(o => { + dicPushMessage.set(o.pushMsgType, o); + }); + arr = undefined; +} diff --git a/shared/pubUtils/dictionary/DicServerConst.ts b/shared/pubUtils/dictionary/DicServerConst.ts index fe6fb7d07..c8db7ec27 100644 --- a/shared/pubUtils/dictionary/DicServerConst.ts +++ b/shared/pubUtils/dictionary/DicServerConst.ts @@ -53,6 +53,8 @@ export interface DicServerConst { readonly SKIP_ENCODE: number; // 是否返利 readonly NEED_REBATE: number; + // 推送 + readonly PUSH_MSG: number; } export const dicServerConst: DicServerConst = {} as DicServerConst; diff --git a/shared/pubUtils/httpUtil.ts b/shared/pubUtils/httpUtil.ts index 9e69d07ea..6c609062b 100644 --- a/shared/pubUtils/httpUtil.ts +++ b/shared/pubUtils/httpUtil.ts @@ -1,10 +1,10 @@ import * as request from "request-promise"; import { RequestError } from "request-promise/errors"; import { BANTU_VID_ADDR, BANTU_VID_APP_KEY, HTTP_METHOD } from '../consts'; -import { checkVidObjSign, get37CheckChatMd5Sign, get37Md5SignA, get37Md5SignB, getVidObjSign } from "./sdkUtil"; +import { checkVidObjSign, get37CheckChatMd5Sign, get37Md5SignA, get37Md5SignB, get37PushMsgMd5Sign, getVidObjSign } from "./sdkUtil"; import { STATUS } from '../consts' import { resResult } from "./util"; -import { Chat37Params, CheckGuild37Params, CheckName37Params, GetWordParam } from "../domain/sdk"; +import { Chat37Params, CheckGuild37Params, CheckName37Params, GetWordParam, PushMsg37Param } from "../domain/sdk"; // 通用,请求http export async function httpRequest(url: string, method: string, body: any, headers?: any, timeout = 150) { @@ -77,6 +77,42 @@ export async function httpRequestForm(url: string, method: string, form: any, ti } } +export async function httpRequestFormData(url: string, method: string, form: any, timeout = 150, printRes = true) { + console.log(`httpRequest*********: ${url}, ${method}, ${JSON.stringify(form)}`) + + let options = { + url, + method, + headers: { + 'content-type': 'multipart/form-data;charset=utf-8' + }, + timeout, + JSON: true + } + if(method == HTTP_METHOD.GET) { + options['qs'] = form; + } else if (method == HTTP_METHOD.POST) { + options['form'] = form; + } + + try { + let res = await request(options); + console.log('*****request result*****'); + if (printRes) { + console.log(JSON.stringify(res)); + } + return res; + } catch (e) { + console.error('******', e); + let code = (e).cause?.code; + if(code == 'ESOCKETTIMEDOUT') { + return resResult(STATUS.REQUEST_TIME_OUT); + } else { + return resResult(STATUS.REQUEST_TIME_OUT); + } + } +} + /************** 厚土防沉迷接口 **************/ /** * 在线报告 暂时不使用 @@ -189,3 +225,13 @@ export async function request37GetWord(url: string, body: GetWordParam, key: str let result = await httpRequestForm(url, HTTP_METHOD.GET, body, 5 * 60 * 1000, false); return result; } + +export async function request37PushMessage(url: string, body: PushMsg37Param, key: string) { + body.setSign(get37PushMsgMd5Sign(body, key)); + + let result = await httpRequestFormData(url, HTTP_METHOD.POST, body, 3 * 60 * 1000); + if(result != 1 && result.code != STATUS.REQUEST_TIME_OUT.code) { + return false + } + return true; +} \ No newline at end of file diff --git a/shared/pubUtils/sdkUtil.ts b/shared/pubUtils/sdkUtil.ts index 6ba3f51b5..5e1f5b812 100644 --- a/shared/pubUtils/sdkUtil.ts +++ b/shared/pubUtils/sdkUtil.ts @@ -1,7 +1,7 @@ import { DEBUG_PRICE, REDIS_KEY, SDK_37_ADDR, SDK_37_CONST, WJX_KEY } from '../consts'; import { request37 } from './httpUtil'; import { nowSeconds } from './timeUtil'; -import { LoginValidataReturn37, Chat37Params, GetServerListParam } from '../domain/sdk'; +import { LoginValidataReturn37, Chat37Params, GetServerListParam, PushMsg37Param } from '../domain/sdk'; import * as crypto from 'crypto' import { gameData } from './data'; @@ -57,6 +57,18 @@ export function get37CheckChatMd5Sign(body: Chat37Params, key: string) { return sign; } +export function get37PushMsgMd5Sign(body: PushMsg37Param, key: string) { + let { notify_id = '', game_id = '', c_game_id = '', title = '', text = '', target = '', click_type = '', time = '' } = body; + let str = encodeUtf8(`${notify_id}${game_id}${c_game_id}${title}${text}${target}${click_type}${key}${time}`); + let sign = md5(str); + console.log('** origin str', body, str, sign); + return sign; +} + +function encodeUtf8(str: string) { + return Buffer.from(str, 'utf8').toString(); +} + export function get37Md5SignB(body: any, key: string) { return getMd5ObjSign(body, '', (str) => `${str}${key}`); } @@ -120,7 +132,6 @@ export function getChannelId(channelType: string, uid: number|string) { return `${channelType}_${uid}`; } - /********* 厚土防沉迷 *********/ export function getVidObjSign(body: any) { @@ -177,4 +188,8 @@ export function isSkipEncode(isDevelop = false) { export function needRebate() { return gameData.serverConst.NEED_REBATE == 1; +} + +export function needPushMsg() { + return gameData.serverConst.PUSH_MSG == 1; } \ No newline at end of file diff --git a/shared/resource/jsons/dic_zyz_pushMessage.json b/shared/resource/jsons/dic_zyz_pushMessage.json new file mode 100644 index 000000000..21d5ce8e9 --- /dev/null +++ b/shared/resource/jsons/dic_zyz_pushMessage.json @@ -0,0 +1,52 @@ +[ + { + "id": 1, + "pushMsgType": "guildActivityStart", + "title": "英杰传", + "description": "军团活动开启", + "playerType": 1, + "clickType": "startapp", + "url": "&", + "intent": "&" + }, + { + "id": 2, + "pushMsgType": "gvgBattleStart", + "title": "英杰传", + "description": "逐鹿中原激战期开启", + "playerType": 2, + "clickType": "startapp", + "url": "&", + "intent": "&" + }, + { + "id": 3, + "pushMsgType": "afkAttention", + "title": "英杰传", + "description": "两天未登录", + "playerType": 3, + "clickType": "startapp", + "url": "&", + "intent": "&" + }, + { + "id": 4, + "pushMsgType": "apLunch", + "title": "英杰传", + "description": "领午饭体力", + "playerType": 4, + "clickType": "startapp", + "url": "&", + "intent": "&" + }, + { + "id": 5, + "pushMsgType": "apDinner", + "title": "英杰传", + "description": "领晚饭体力", + "playerType": 4, + "clickType": "startapp", + "url": "&", + "intent": "&" + } +] \ No newline at end of file diff --git a/shared/resource/jsons/server_const.json b/shared/resource/jsons/server_const.json index 13b2f65f3..6ddeeb841 100644 --- a/shared/resource/jsons/server_const.json +++ b/shared/resource/jsons/server_const.json @@ -35,5 +35,6 @@ "CHECK_WORD": 1, "CAN_PAY": 1, "SKIP_ENCODE": 0, - "NEED_REBATE": 0 + "NEED_REBATE": 0, + "PUSH_MSG": 0 } \ No newline at end of file