Revert "Revert " feat(消息): 添加推送消息sdk""

This reverts commit 918cc3415f.
This commit is contained in:
luying
2023-03-21 21:04:12 +08:00
parent 9115794b82
commit 5e15568f71
17 changed files with 406 additions and 16 deletions

View File

@@ -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"
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小时内登录过但当前未在线的玩家
}

View File

@@ -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 = [

View File

@@ -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 },

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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<number, number>(),
gvgPointByAreaId: dicGVGPointsByAreaId,
gvgReviveGold: new Map<number|'max', number>(),
dicPushMessage: dicPushMessage
};
// 在此提供一些原先在gamedata中提供的方法以便更方便获取gameData数据
@@ -1486,6 +1488,7 @@ function loadDatas() {
loadGVGVestigePlayerRank();
loadGVGAreaPoint();
loadGVGBattleRankReward();
loadPushMessage();
}
// 重载dicParam

View File

@@ -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<string, DicPushMessage>();
export function loadPushMessage() {
dicPushMessage.clear();
let arr = readFileAndParse(FILENAME.DIC_PUSH_MESSAGE);
arr.forEach(o => {
dicPushMessage.set(o.pushMsgType, o);
});
arr = undefined;
}

View File

@@ -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;

View File

@@ -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 = (<RequestError>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;
}

View File

@@ -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;
}

View File

@@ -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": "&"
}
]

View File

@@ -35,5 +35,6 @@
"CHECK_WORD": 1,
"CAN_PAY": 1,
"SKIP_ENCODE": 0,
"NEED_REBATE": 0
"NEED_REBATE": 0,
"PUSH_MSG": 0
}