diff --git a/game-server/app/servers/activity/handler/bindPhoneHandler.ts b/game-server/app/servers/activity/handler/bindPhoneHandler.ts index a775858d6..d34d69e10 100644 --- a/game-server/app/servers/activity/handler/bindPhoneHandler.ts +++ b/game-server/app/servers/activity/handler/bindPhoneHandler.ts @@ -6,8 +6,10 @@ import { DailyItem } from '../../../domain/activityField/dailyChallengesField'; import { addReward, stringToRewardParam } from '../../../services/activity/giftPackageService'; import { RewardParam } from '../../../domain/activityField/rewardField'; import { ActivityDailyChallengesModel } from '../../../db/ActivityDailyChallenges'; -import { getBindPhoneData, getBindPhoneDataShow } from '../../../services/activity/bindPhoneService'; +import { getBindPhoneData, getAllSnsLinkDataShow, getPublicAccountData } from '../../../services/activity/bindPhoneService'; import { ActivityBindPhoneRewardModel } from '../../../db/ActivityBindPhoneReward'; +import { ActivityPublicAccountCodeModel } from '../../../db/ActivityPublicAccountCode'; +import { UserModel } from '../../../db/User'; export default function (app: Application) { @@ -25,7 +27,7 @@ export class BindPhoneHandler { const serverId = session.get('serverId'); const uid = session.get('userid'); - let playerData = await getBindPhoneDataShow(activityId, roleId, serverId, uid); + let playerData = await getAllSnsLinkDataShow(activityId, roleId, serverId, uid); return resResult(STATUS.SUCCESS, { playerData }) @@ -98,9 +100,30 @@ export class BindPhoneHandler { * @memberof BindPhoneHandler */ async receiveGiftCode(msg: { activityId: number }, session: BackendSession) { - return resResult(STATUS.SUCCESS); - } + const { activityId } = msg; + const roleId = session.get('roleId'); + const serverId = session.get('serverId'); + const sid = session.get('sid'); + const roleName = session.get('roleName'); + const uid = session.get('userid'); + + let user = await UserModel.findUserByUid(uid); + let playerData = await getPublicAccountData(activityId, roleId, serverId, user); + let data = playerData?.wxPublicAccount; + if(!data) return resResult(STATUS.ACTIVITY_ID_ERROR); + if(data.status == BIND_PHONE_STATUS.WAIT_BIND) return resResult(STATUS.ACTIVITY_PUBLIC_ACCOUNT_WAIT); + if(data.status == BIND_PHONE_STATUS.RECEIVED) return resResult(STATUS.ACTIVITY_PUBLIC_ACCOUNT_RECEIVED); + await ActivityPublicAccountCodeModel.receive(activityId, user.channelId); + let rewardArray = stringToRewardParam(data.rewards) + let { goods, addHeros } = await addReward(roleId, roleName, sid, serverId, rewardArray, ITEM_CHANGE_REASON.ACT_BIND_PHONE); + + return resResult(STATUS.SUCCESS, { + activityId, + status: BIND_PHONE_STATUS.RECEIVED, + goods, addHeros + }); + } /** * 前往论坛,用于前往论坛任务 diff --git a/game-server/app/servers/activity/remote/activityRemote.ts b/game-server/app/servers/activity/remote/activityRemote.ts index 53793066b..42da44918 100644 --- a/game-server/app/servers/activity/remote/activityRemote.ts +++ b/game-server/app/servers/activity/remote/activityRemote.ts @@ -12,6 +12,7 @@ import { saveActivityMemory } from '../../../services/log/memoryLogService'; import { setApiIsClose } from '../../../services/chatService'; import { setHiddenData } from '../../../services/dataService'; import { setKvToMemory } from '../../../services/pushService'; +import { sendPublicAccountGift } from '../../../services/activity/bindPhoneService'; export default function (app: Application) { new HandlerService(app, {}); @@ -174,4 +175,12 @@ export class ActivityRemote { errlogger.error(`remote ${__filename} \n ${e.stack}`); } } + + public async sendPublicAccountGift(message: string) { + try { + await sendPublicAccountGift(message); + } catch(e) { + errlogger.error(`remote ${__filename} \n ${e.stack}`); + } + } } \ No newline at end of file diff --git a/game-server/app/servers/role/remote/roleRemote.ts b/game-server/app/servers/role/remote/roleRemote.ts index 12f5e4dce..3af4f9214 100644 --- a/game-server/app/servers/role/remote/roleRemote.ts +++ b/game-server/app/servers/role/remote/roleRemote.ts @@ -3,7 +3,7 @@ import { Application, ChannelService, HandlerService, } from 'pinus'; import { reloadResources } from '../../../pubUtils/data'; import { RankFirstModel, RankFirstType } from '../../../db/RankFirst'; import { PVPConfigModel, PVPConfigType } from '../../../db/PvpConfig'; -import { treatRoleName, taflush, sendSurveyMail } from '../../../services/sdkService'; +import { treatRoleName, taflush, sendSurveyMail, sendGiftCodeMail } from '../../../services/sdkService'; import { getServerMainten, setServerMainten, stopServerMainten } from '../../../services/gmService'; import { errlogger } from '../../../util/logger'; import { setApiIsClose } from '../../../services/chatService'; @@ -136,6 +136,15 @@ export class RoleRemote { } } + + public async sendGiftCodeMail(message: string) { + try { + await sendGiftCodeMail(message); + } catch(e) { + errlogger.error(`remote ${__filename} \n ${e.stack}`); + } + } + public async setApiIsClose(isClose: boolean) { try { setApiIsClose(isClose); diff --git a/game-server/app/services/activity/activityService.ts b/game-server/app/services/activity/activityService.ts index e2e50fe0f..9467c880f 100644 --- a/game-server/app/services/activity/activityService.ts +++ b/game-server/app/services/activity/activityService.ts @@ -40,7 +40,7 @@ import { getGuideGachaData } from './gachaService'; import { getPopNoticeData } from './popNoticeService'; import { _getActivities, _getActivitiesByServerId, _getActivitiesByType, _getActivityById } from './activityRemoteService'; import { getGroupShopDataShow } from './groupShopService'; -import { getBindPhoneDataShow } from './bindPhoneService'; +import { getAllSnsLinkDataShow } from './bindPhoneService'; import { getPlayerForgeDataShow } from './forgeService'; import { getPlayerMiniGameDataShow } from './miniGameService'; import { getWeeklyFundDataShow } from './weeklyFundService'; @@ -233,7 +233,7 @@ export async function getActivity(serverId: number, roleId: string, uid: number, } case ACTIVITY_TYPE.BIND_PHONE: { - activityData = await getBindPhoneDataShow(activityId, roleId, serverId, uid); + activityData = await getAllSnsLinkDataShow(activityId, roleId, serverId, uid); break } case ACTIVITY_TYPE.FORGE: @@ -266,7 +266,7 @@ export async function getActivity(serverId: number, roleId: string, uid: number, } return activityData; } catch(e) { - console.log('activity error', activityId, activityType); + console.log('activity error', e, activityId, activityType); return null; } } diff --git a/game-server/app/services/activity/bindPhoneService.ts b/game-server/app/services/activity/bindPhoneService.ts index ead7c070a..94b5e5bb7 100644 --- a/game-server/app/services/activity/bindPhoneService.ts +++ b/game-server/app/services/activity/bindPhoneService.ts @@ -1,19 +1,57 @@ -import { ACTIVITY_TYPE } from '../../consts'; -import { ActivityModel, ActivityModelType } from '../../db/Activity'; +import { isNumber } from 'underscore'; +import { ACTIVITY_TYPE, BIND_PHONE_STATUS, PUBLIC_ACCOUNT_GIFT, PUSH_ROUTE, SDK_37_CONST, SNS_LINK_TYPE } from '../../consts'; +import { ActivityModelType } from '../../db/Activity'; import { ActivityBindPhoneRewardModel } from '../../db/ActivityBindPhoneReward'; -import { ActivityDailyChallengesModel, ActivityDailyChallengesModelType } from '../../db/ActivityDailyChallenges'; +import { ActivityPublicAccountCodeModel } from '../../db/ActivityPublicAccountCode'; import { LinkModel } from '../../db/Link'; -import { RoleModel } from '../../db/Role'; -import { ServerlistModel } from '../../db/Serverlist'; -import { UserModel } from '../../db/User'; +import { UserModel, UserType } from '../../db/User'; import { BindPhoneData } from '../../domain/activityField/bindPhoneField'; -import { DailyChallengesData } from '../../domain/activityField/dailyChallengesField'; -import { getRoleCreateTime, getServerCreateTime } from '../redisService'; -import { getActivitiesByType, getActivityById } from './activityService'; +import { aes128Encrypt } from '../../pubUtils/util'; +import { getOnlineRoleByUserCode, getRedis, getRoleCreateTime, getServerCreateTime } from '../redisService'; +import { getActivityById } from './activityService'; +import { sendMessageToUserWithSuc } from '../pushService'; /** - * 获取活动数据 + * 绑定手机相关数据 + * + * @param {number} serverId 区Id + * @param {number} activityId 活动Id + * @param {string} roleId 角色Id + * + */ +export async function getAllSnsLinkData(activityId: number, roleId: string, serverId: number, uid: number) { + + let activityData: ActivityModelType = await getActivityById(activityId); + if(!activityData || activityData.type != ACTIVITY_TYPE.BIND_PHONE) return null; + + let createTime = await getRoleCreateTime(roleId); + let serverTime = await getServerCreateTime(serverId); + let playerData = new BindPhoneData(activityData, createTime, serverTime); + + let links = await LinkModel.findByServerId(serverId); + playerData.setLinks(links); + + let user: UserType; + if(playerData.bindPhone) { + if(!user) user = await UserModel.findUserByUid(uid); + let receiveRec = await ActivityBindPhoneRewardModel.findByUid(activityId, uid); + playerData.bindPhone.setBindPhoneStatus(user, receiveRec); + } + if(playerData.wxPublicAccount) { + if(!user) user = await UserModel.findUserByUid(uid); + if(!user.channelInfo) return null; + let giftCode = encodeGiftCode(roleId, serverId, user.channelInfo.uid, user.channelInfo.platformAppid, user.channelInfo.childGameId, activityId); + playerData.wxPublicAccount.setEncodeGiftCode(giftCode); + let playerRecord = await ActivityPublicAccountCodeModel.findByChannel(activityId, user.channelId); + if(playerRecord) playerData.wxPublicAccount.setPlayerRecord(playerRecord); + } + + return playerData; +} + +/** + * 绑定手机相关数据 * * @param {number} serverId 区Id * @param {number} activityId 活动Id @@ -32,10 +70,42 @@ export async function getBindPhoneData(activityId: number, roleId: string, serve let links = await LinkModel.findByServerId(serverId); playerData.setLinks(links); + let user: UserType; if(playerData.bindPhone) { - let user = await UserModel.findUserByUid(uid); + if(!user) user = await UserModel.findUserByUid(uid); let receiveRec = await ActivityBindPhoneRewardModel.findByUid(activityId, uid); - playerData.setBindPhoneStatus(user, receiveRec); + playerData.bindPhone.setBindPhoneStatus(user, receiveRec); + } + + return playerData; +} + +/** + * 微信公众号 + * + * @param {number} serverId 区Id + * @param {number} activityId 活动Id + * @param {string} roleId 角色Id + * + */ +export async function getPublicAccountData(activityId: number, roleId: string, serverId: number, user: UserType) { + + let activityData: ActivityModelType = await getActivityById(activityId); + if(!activityData || activityData.type != ACTIVITY_TYPE.BIND_PHONE) return null; + + let createTime = await getRoleCreateTime(roleId); + let serverTime = await getServerCreateTime(serverId); + let playerData = new BindPhoneData(activityData, createTime, serverTime); + + let links = await LinkModel.findByServerId(serverId); + playerData.setLinks(links); + + if(playerData.wxPublicAccount) { + if(!user.channelInfo) return null; + let giftCode = encodeGiftCode(roleId, serverId, user.channelInfo.uid, user.channelInfo.platformAppid, user.channelInfo.childGameId, activityId); + playerData.wxPublicAccount.setEncodeGiftCode(giftCode); + let playerRecord = await ActivityPublicAccountCodeModel.findByChannel(activityId, user.channelId); + if(playerRecord) playerData.wxPublicAccount.setPlayerRecord(playerRecord); } return playerData; @@ -50,12 +120,28 @@ export async function getBindPhoneData(activityId: number, roleId: string, serve * @param {string} roleId 角色Id * */ -export async function getBindPhoneDataShow(activityId: number, roleId: string, serverId: number, uid: number) { - let playerData = await getBindPhoneData(activityId, roleId, serverId, uid); +export async function getAllSnsLinkDataShow(activityId: number, roleId: string, serverId: number, uid: number) { + let playerData = await getAllSnsLinkData(activityId, roleId, serverId, uid); if(playerData && playerData.canShow && playerData.canShow()) { return playerData.getShowResult(); } return null } +function encodeGiftCode(roleId: string, serverId: number, uid: number, pid: string, gid: string, activityId: number) { + let encodeResult = aes128Encrypt(`${roleId}|${serverId}|${uid}|${pid}|${gid}|${PUBLIC_ACCOUNT_GIFT}${activityId}|${SDK_37_CONST.GIFT_GAME_ID}`, SDK_37_CONST.PUBLIC_GIFT_KEY); + return `FS${encodeResult}`; +} +export async function sendPublicAccountGift(message: string) { + let [_activityId, userCode, channelId] = message?.split('|')||[]; + let activityId = parseInt(_activityId); + if(!isNumber(activityId) || !userCode || !channelId) return; + + let onlineRoleId = await getOnlineRoleByUserCode(userCode); + console.log('##### sendPublicAccountGift', message, userCode, onlineRoleId); + if (!!onlineRoleId) { // 在线,推送消息 + let playerRecord = await ActivityPublicAccountCodeModel.findByChannel(activityId, channelId); + await sendMessageToUserWithSuc(onlineRoleId, PUSH_ROUTE.PUBLIC_ACCOUNT_GIFT, { activityId, type: SNS_LINK_TYPE.WX_PUBLIC_ACCOUNT, status: playerRecord.hasReceived? BIND_PHONE_STATUS.HAS_BIND: BIND_PHONE_STATUS.RECEIVED }); + } +} \ No newline at end of file diff --git a/game-server/app/services/redisService.ts b/game-server/app/services/redisService.ts index 2d1aef70d..01f574ed6 100644 --- a/game-server/app/services/redisService.ts +++ b/game-server/app/services/redisService.ts @@ -659,7 +659,9 @@ export async function redisSubScribe() { let treatGuildChannel = getRedisSubChannel(REDIS_KEY.TREAT_GUILD_CHANNEL, env); let surveyChannel = getRedisSubChannel(REDIS_KEY.SURVEY_CHANNEL, env); let userChannel = getRedisSubChannel(REDIS_KEY.USER_CHANNEL, env); - await redisClientPub().subscribeAsync(payChannel, treatRoleChannel, treatGuildChannel, refundChannel, surveyChannel, userChannel); + let publicAccountChannel = getRedisSubChannel(REDIS_KEY.PUBLIC_ACCOUNT_GIFT, env); + let sendGiftCodeChannel = getRedisSubChannel(REDIS_KEY.SEND_GIFT_CODE, env); + await redisClientPub().subscribeAsync(payChannel, treatRoleChannel, treatGuildChannel, refundChannel, surveyChannel, userChannel, publicAccountChannel, sendGiftCodeChannel); redisClientPub().on('message', (channel, message) => { console.log('**********redisSubScribe*******') console.log('channel: ', channel, payChannel); @@ -687,6 +689,15 @@ export async function redisSubScribe() { } else if(channel == userChannel) { let [sid, roleId] = (message||'').split('|'); if(!!sid && !!roleId) pinus.app.rpc.connector.connectorRemote.remoteLogin.toServer(sid, roleId); + } else if (channel == publicAccountChannel) { + let servers = pinus.app.getServersByType('activity'); + let server = getRandSingleEelm(servers); + pinus.app.rpc.activity.activityRemote.sendPublicAccountGift.toServer(server.id, message); + } else if (channel == sendGiftCodeChannel) { + let servers = pinus.app.getServersByType('role'); + let server = getRandSingleEelm(servers); + pinus.app.rpc.role.roleRemote.sendGiftCodeMail.toServer(server.id, message); + } }); } diff --git a/game-server/app/services/sdkService.ts b/game-server/app/services/sdkService.ts index aa7e8be18..8a96807ba 100644 --- a/game-server/app/services/sdkService.ts +++ b/game-server/app/services/sdkService.ts @@ -22,6 +22,8 @@ import { sendMessageToUserWithSuc } from "./pushService"; import { GuildLeader } from "../domain/rank"; import { initTaLoggingMode } from "./sdk/ta"; import { DicPushMessage } from "../pubUtils/dictionary/DicPushMessage"; +import { GiftCodeModel } from "../db/GiftCode"; +import { GiftCodeDetailModel } from "../db/GiftCodeDetail"; // 检查私聊是否合法 @@ -319,4 +321,18 @@ export async function sendSurveyMail(code: string) { await sendMailByContent(MAIL_TYPE.SEND_MAIL, rec.roleId, { goods: survey.reward, params: [survey.mailContent] }); await sendMessageToUserWithSuc(rec.roleId, PUSH_ROUTE.DELETE_SURVEY, { code: survey.code }); rec = await SurveyRecModel.send(rec.code); +} + +export async function sendGiftCodeMail(message: string) { + console.log('***** sendSurveyMail', message); + let [roleId, _giftId] = message.split('|'); + let giftId = parseInt(_giftId); + if(isNaN(giftId) || !roleId) return false; + let giftCode = await GiftCodeModel.findByGiftId(giftId); + if(!giftCode) { + console.log('***** sendSurveyMail giftId not found', giftId); + return false; + } + + await sendMailByContent(MAIL_TYPE.SEND_MAIL, roleId, { goods: giftCode.goods, params: [''] }); } \ No newline at end of file diff --git a/shared/consts/constModules/chatConst.ts b/shared/consts/constModules/chatConst.ts index 2e42e2440..75dc14511 100644 --- a/shared/consts/constModules/chatConst.ts +++ b/shared/consts/constModules/chatConst.ts @@ -209,4 +209,5 @@ export const PUSH_ROUTE = { GVG_CONTRIBUTE_UPDATE: 'onGVGContributeUpdate', // 贡献更新 GVG_TARGET_CITY_UPDATE: 'onGVGTargetCitiesUpdate', // 目标城池更新 GVG_NOTICE_UPDATE: 'onGVGNoticeUpdate', // 管理信息更新 + PUBLIC_ACCOUNT_GIFT: 'onPublicAccountGift', // 公众号发送 } \ No newline at end of file diff --git a/shared/consts/constModules/httpConst.ts b/shared/consts/constModules/httpConst.ts index df9ca1339..6a4e7f05e 100644 --- a/shared/consts/constModules/httpConst.ts +++ b/shared/consts/constModules/httpConst.ts @@ -30,8 +30,10 @@ export enum SDK_37_CONST { CHAT_KEY = '3PerD)H!JdTC_pEUnZl8vXKgasj5;7Q~', // 聊天KEY IOS_PID = 'zyz', BACKEND_KEY = 'zyzbackend2022', + PUBLIC_GIFT_KEY = 'zyzbackend202304', PUSH_KEY = '7mV3FzL7drRxTPkoBPkcRXpeTapG2jk9', // 推送消息KEY PUSH_GAME_ID = 814, // 推送消息的GAME_ID + GIFT_GAME_ID = 814, // 游戏id } export enum SDK_TA_CONST { @@ -62,4 +64,6 @@ export enum SDK_PUSH_MSG_PLAYER_TYPE { HAS_LEAGUE = 2, // 有联军且48小时内登录过但当前未在线的玩家 AFK = 3, // 至少48小时未上线且等级>=20级的玩家 ACTIVE_PLAYER = 4, // 24小时内登录过但当前未在线的玩家 -} \ No newline at end of file +} + +export const PUBLIC_ACCOUNT_GIFT = 'YJZPUBACC'; \ No newline at end of file diff --git a/shared/consts/constModules/sysConst.ts b/shared/consts/constModules/sysConst.ts index 22103ccb0..4dc8d1686 100644 --- a/shared/consts/constModules/sysConst.ts +++ b/shared/consts/constModules/sysConst.ts @@ -268,6 +268,8 @@ export enum REDIS_KEY { GVG_SEND_REWARD ='gvgSendReward', // gvg发放奖励 GVG_SPINE_CNT ='gvgSpineCnt', // gvg spine的下发数量 ACTIVITY_MINI_GAME ='miniGame', // 活动小游戏排行榜 + PUBLIC_ACCOUNT_GIFT = 'pubAccGiftChannel', // 公众号频道 + SEND_GIFT_CODE = 'sendGiftCodeChannel', // 礼包码频道 } // 各排行榜对应hash的key diff --git a/shared/consts/statusCode.ts b/shared/consts/statusCode.ts index ac5530b19..d9dc04d9f 100644 --- a/shared/consts/statusCode.ts +++ b/shared/consts/statusCode.ts @@ -669,6 +669,8 @@ export const STATUS = { ACTIVITY_MONTHLY_FUND_NOT_FOUND: { code: 50061, simStr: '未找到该配置' }, ACTIVITY_MONTHLY_FUND_LOCK: { code: 50062, simStr: '本次签到暂未解锁' }, ACTIVITY_MONTHLY_FUND_HAS_SIGN: { code: 60063, simStr: '本次签到奖励已领取' }, + ACTIVITY_PUBLIC_ACCOUNT_WAIT: { code: 60064, simStr: '请在公众号输入口令' }, + ACTIVITY_PUBLIC_ACCOUNT_RECEIVED: { code: 60065, simStr: '已领取' }, // GM后台相关状态 60000 - 69999 GM_ERR_PASSWORD: { code: 60001, simStr: '账号或密码错误' }, @@ -779,4 +781,18 @@ export const BACKEND_37_CODE = { TIME_IS_EXPIRED: { code: -3, simStr: '请求超时(连接已过期)' }, USER_NOT_FOUND: { code: -4, simStr: '非法账号' }, LOGIN_FAIL: { code: -5, simStr: '登录失败' }, +} + +export const SDK_37_SEND_GIFT_RESULT_CODE = { + SUCCESS: { code: 1, simStr: '成功' }, + WRONG_PARAMS: { code: 10001, simStr: '参数异常' }, + ROLE_NOT_FOUND: { code: 10002, simStr: '角色不存在' }, + SIGN_ERR: { code: 10003, simStr: '签名错误' }, + INTERNAL_ERR: { code: 10004, simStr: '处理异常' }, + GIFT_NOT_FOUND: { code: 20002, simStr: '礼包id不存在' }, + ORDER_DUPLICATE: { code: 20003, simStr: '订单号重复' }, + USE_CNT_MAX: { code: 20004, simStr: '超过使用次数' }, + GIFT_CODE_HAS_EXPIRED: { code: 20005, simStr: '礼包码已过期' }, + GIFT_CODE_NOT_START: { code: 20006, simStr: '礼包码暂未开启' }, + GIFT_CHANNEL_ERR: { code: 20007, simStr: '礼包不可在此渠道使用' }, } \ No newline at end of file diff --git a/shared/db/ActivityPublicAccountCode.ts b/shared/db/ActivityPublicAccountCode.ts new file mode 100644 index 000000000..55123c035 --- /dev/null +++ b/shared/db/ActivityPublicAccountCode.ts @@ -0,0 +1,49 @@ +import BaseModel from './BaseModel'; +import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoose'; + + +/** + * 自助商店 +*/ +@index({ activityId: 1, channelId: 1 }) + +export default class Activity_Public_Account_Code extends BaseModel { + @prop({ required: true }) + activityId: number; // 对应礼包码 + + @prop({ required: true }) + channelId: string; // 37平台id + + @prop({ required: true }) + serverId: number; // 服 + + @prop({ required: true }) + roleId: string; // 玩家id + + @prop({ required: true }) + orderId: string; // 收到回调的订单号 + + @prop({ required: true }) + hasReceived: boolean; // 是否领取 + + public static async findByChannel(activityId: number, channelId: string) { + let result: ActivityPublicAccountCodeType = await ActivityPublicAccountCodeModel.findOne({ activityId, channelId }).lean(); + return result; + } + + public static async sendGift(activityId: number, channelId: string, serverId: number, roleId: string, orderId: string) { + let result: ActivityPublicAccountCodeType = await ActivityPublicAccountCodeModel.findOneAndUpdate({ activityId, channelId }, { $set: { serverId, roleId, orderId }, $setOnInsert: { hasReceived: false } }, { new: true, upsert: true }).lean(); + return result; + } + + public static async receive(activityId: number, channelId: string) { + let result: ActivityPublicAccountCodeType = await ActivityPublicAccountCodeModel.findOneAndUpdate({ activityId, channelId, hasReceived: false }, { $set: { hasReceived: true }}, { new: true }).lean(); + return result; + } + +} + +export const ActivityPublicAccountCodeModel = getModelForClass(Activity_Public_Account_Code); + +export interface ActivityPublicAccountCodeType extends Pick, keyof Activity_Public_Account_Code> { } +export type ActivityPublicAccountCodeTypeParam = Partial; // 将所有字段变成可选项 \ No newline at end of file diff --git a/shared/db/GiftCode.ts b/shared/db/GiftCode.ts index 04db58d7d..2a6d3b598 100644 --- a/shared/db/GiftCode.ts +++ b/shared/db/GiftCode.ts @@ -52,6 +52,9 @@ export default class GiftCode extends BaseModel { @prop({ required: true, default: 0 }) usedNum: number; // 使用次数 + @prop({ required: true, default: 0 }) + type: number; // 0-游戏内领取 1-公众号口令活动领取 2-邮件领取 + @prop({ required: true, default: true }) isEnable: boolean; // 是否可以使用 diff --git a/shared/db/GiftCodeDetail.ts b/shared/db/GiftCodeDetail.ts index 5088bb76c..709581001 100644 --- a/shared/db/GiftCodeDetail.ts +++ b/shared/db/GiftCodeDetail.ts @@ -16,6 +16,9 @@ class RoleRecord { @prop({ required: true, default: 0 }) serverId: number; + @prop({ required: false, default: '' }) + orderId?: string; + @prop({ required: true, default: 0 }) time: number; } @@ -70,9 +73,9 @@ export default class GiftCodeDetail extends BaseModel { return result; } - public static async increaseUsedNum(code: string, roleId: string, roleName: string, serverId: number) { + public static async increaseUsedNum(code: string, roleId: string, roleName: string, serverId: number, orderId?: string) { let result: GiftCodeDetailType = await GiftCodeDetailModel.findOneAndUpdate({ code }, { - $inc: { usedNum: 1 }, $push: { roleIds: roleId, record: { roleId, roleName, serverId, time: nowSeconds() } } + $inc: { usedNum: 1 }, $push: { roleIds: roleId, record: { roleId, roleName, serverId, time: nowSeconds(), orderId } } }, { new: true }).lean(); return result; } diff --git a/shared/db/User.ts b/shared/db/User.ts index 6c09c9c26..cc995c869 100644 --- a/shared/db/User.ts +++ b/shared/db/User.ts @@ -246,7 +246,7 @@ export default class User extends BaseModel { } public static async findUserByChannel(channelId: string) { - const user: UserType = await UserModel.findOne({ channelId }).select('uid token serverType auth tel userCode pkgName ip deviceId').lean({ getters: true }); + const user: UserType = await UserModel.findOne({ channelId }).select('uid token serverType auth tel userCode pkgName ip deviceId channelId').lean({ getters: true }); return user; } diff --git a/shared/domain/activityField/bindPhoneField.ts b/shared/domain/activityField/bindPhoneField.ts index de4e69427..7474a3664 100644 --- a/shared/domain/activityField/bindPhoneField.ts +++ b/shared/domain/activityField/bindPhoneField.ts @@ -5,6 +5,7 @@ import { ActivityBindPhoneRewardType } from '../../db/ActivityBindPhoneReward'; import { LinkType } from '../../db/Link'; import { UserType } from '../../db/User'; import { ActivityBase } from './activityField'; +import { ActivityPublicAccountCodeType } from '../../db/ActivityPublicAccountCode'; // 数据库 interface PageDataInDb { @@ -12,7 +13,6 @@ interface PageDataInDb { pageName: string; type: number; rewards: string; - giftCode: string; } interface BindPhoneDataInDb { @@ -49,18 +49,21 @@ class BindPhonePage extends PageData { super(pageData); this.rewards = pageData.rewards; } + + public setBindPhoneStatus(user: UserType, data: ActivityBindPhoneRewardType) { + if(user && user.channelInfo && user.channelInfo.is_phone_bind == 1) this.status = BIND_PHONE_STATUS.HAS_BIND; + if(data) this.status = data.status; + } } class WXPublicAccountPage extends PageData { qrCodeLink: string; // 公众号二维码 rewards: string; // 口令能有的奖励 - originGiftCode: string; // 加密后的口令 giftCode: string; // 加密后的口令 status: number = BIND_PHONE_STATUS.WAIT_BIND; // 状态 0-未使用 1-可领取 2-已领取 constructor(pageData: PageDataInDb) { super(pageData); - this.originGiftCode = pageData.giftCode; this.rewards = pageData.rewards; } @@ -72,6 +75,11 @@ class WXPublicAccountPage extends PageData { this.giftCode = giftCode; } + public setPlayerRecord(playerRecord: ActivityPublicAccountCodeType) { + if(!playerRecord) return; + this.status = playerRecord.hasReceived? BIND_PHONE_STATUS.HAS_BIND: BIND_PHONE_STATUS.RECEIVED; + } + public getShowResult() { return pick(this, ['pageIndex', 'pageName', 'type', 'qrCodeLink', 'rewards', 'giftCode', 'status']); } @@ -133,15 +141,8 @@ export class BindPhoneData extends ActivityBase { } } - public setBindPhoneStatus(user: UserType, data: ActivityBindPhoneRewardType) { - if(this.bindPhone) { - if(user && user.channelInfo && user.channelInfo.is_phone_bind == 1) this.bindPhone.status = BIND_PHONE_STATUS.HAS_BIND; - if(data) this.bindPhone.status = data.status; - } - } - public getShowResult() { - let pages: (WXGroupPage|BindPhonePage|WXPublicAccountPage|BBSPage)[] = []; + let pages: any[] = []; if(this.wxGroup) pages.push(this.wxGroup); if(this.bindPhone) pages.push(this.bindPhone); if(this.wxPublicAccount) pages.push(this.wxPublicAccount.getShowResult()); diff --git a/shared/domain/sdk.ts b/shared/domain/sdk.ts index 5e2b7f97e..e3cb96017 100644 --- a/shared/domain/sdk.ts +++ b/shared/domain/sdk.ts @@ -419,4 +419,33 @@ export class PushMsg37Param { public setSign(sign: string) { this.sign = sign; } +} + +export class SendGiftCodeParam { + user_id: number; // 用户id + server_id: number; // 区服id + role_id: string; // 角色id + gift_id: string; // 礼包id + order_id: string; // 订单号 + time: number; // 时间戳 + sign: string; // 37给的签名 + + constructor(data: any) { + this.user_id = data.user_id; + this.server_id = data.server_id; + this.role_id = data.role_id; + this.gift_id = data.gift_id; + this.order_id = data.order_id; + this.time = data.time; + this.sign = data.sign; + } + + checkParams() { + return this.user_id != undefined && this.server_id != undefined && this.gift_id != undefined && this.order_id != undefined && this.time != undefined && this.sign != undefined + } + + getBody() { + let { user_id, server_id, role_id, gift_id, order_id, time } = this; + return { user_id, server_id, role_id, gift_id, order_id, time } + } } \ No newline at end of file diff --git a/shared/pubUtils/util.ts b/shared/pubUtils/util.ts index 8552bd414..aace6f771 100644 --- a/shared/pubUtils/util.ts +++ b/shared/pubUtils/util.ts @@ -49,6 +49,28 @@ export function aesDecryptcfb(data, key, iv) { } } +export function aes128Encrypt(input: string, key = '') { + const cipher = crypto.createCipheriv('aes-128-ecb', key, null); + let crypted = cipher.update(pkcs5_pad(input, 16), 'utf8', 'base64'); + crypted += cipher.final('base64'); + console.log('****aesEncryptcfb', 'input: ', input, 'crypted:', crypted) + return crypted; +} + +function pkcs5_pad(text: string, blocksize: number) { + const pad = blocksize - (text.length % blocksize); + return text + String.fromCharCode(pad).repeat(pad); +} + +export function aes128Decrypt(data: string, key = '') { + const decipher = crypto.createDecipheriv('aes-128-ecb', key, null); + let crypted = decipher.update(data, 'base64', 'utf8'); + crypted += decipher.final('utf8'); + const padding = crypted.charCodeAt(crypted.length - 1); + // console.log('****aesEncryptcfb', data, crypted) + return crypted.slice(0, -padding);; +} + export function genCode(len) { const chars = '123456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijklmnopqrstuvwxyz'; const charArr = chars.split(''); diff --git a/web-server/app/controller/sdk.ts b/web-server/app/controller/sdk.ts index 41f76a7ed..6cb66fab1 100644 --- a/web-server/app/controller/sdk.ts +++ b/web-server/app/controller/sdk.ts @@ -1,5 +1,5 @@ import { Controller } from 'egg'; -import { GetGuildInfoByUserParam, GetServerListParam, GuildNameCallBackParam, IOSRefundParam, PayCallback37Data, RoleNameCallBackParam } from '../domain/sdk'; +import { GetGuildInfoByUserParam, GetServerListParam, GuildNameCallBackParam, IOSRefundParam, PayCallback37Data, RoleNameCallBackParam, SendGiftCodeParam } from '../domain/sdk'; export default class SdkController extends Controller { @@ -61,4 +61,11 @@ export default class SdkController extends Controller { ctx.body = await ctx.service.wjx.wjxCallback(ctx.request.body); return; } + + public async sendGiftCode() { + const { ctx } = this; + const params = new SendGiftCodeParam(ctx.request.body); + ctx.body = await ctx.service.sdk.sendGiftCode(params); + return; + } } diff --git a/web-server/app/router.ts b/web-server/app/router.ts index 476e51629..1848f43ce 100644 --- a/web-server/app/router.ts +++ b/web-server/app/router.ts @@ -42,6 +42,7 @@ export default (app: Application) => { router.get('/cb/getserverlist', controller.sdk.getServerList); router.post('/cb/getserverlist', controller.sdk.getServerList); router.post('/cb/refundioscallback', controller.sdk.refundIOSCallback); + router.post('/cb/sendgiftcode', controller.sdk.sendGiftCode); // 问卷星回调 router.post('/cb/wjx', controller.sdk.wjxCallback) diff --git a/web-server/app/service/Sdk.ts b/web-server/app/service/Sdk.ts index c4826b7f7..9b206bcea 100644 --- a/web-server/app/service/Sdk.ts +++ b/web-server/app/service/Sdk.ts @@ -1,9 +1,9 @@ import { Service } from 'egg'; -import { REDIS_KEY, PAY_37_CALLBACK_CODE, SDK_37_CONST, ORDER_STATE, SDK_37_TREAT_CODE, SERVER_STATUS, SDK_37_REFUND_CODE } from '@consts'; -import { GetGuildInfoByUserParam, GetServerListParam, GuildNameCallBackParam, IOSRefundParam, PayCallback37Data, RoleNameCallBackParam } from '../domain/sdk'; +import { REDIS_KEY, PAY_37_CALLBACK_CODE, SDK_37_CONST, ORDER_STATE, SDK_37_TREAT_CODE, SERVER_STATUS, SDK_37_REFUND_CODE, SDK_37_SEND_GIFT_RESULT_CODE, PUBLIC_ACCOUNT_GIFT, GIFT_GENERATE_TYPE } from '@consts'; +import { GetGuildInfoByUserParam, GetServerListParam, GuildNameCallBackParam, IOSRefundParam, PayCallback37Data, RoleNameCallBackParam, SendGiftCodeParam } from '../domain/sdk'; import { RedisClient } from 'redis'; -import { checkParamPrice, get37GetServerMd5Sign, get37Md5SignA, get37Md5SignB, getChannelId, getRedisSubChannel } from '../pubUtils/sdkUtil'; +import { checkParamPrice, get37GetServerMd5Sign, get37Md5SignA, get37Md5SignB, getChannelId, getRedisSubChannel, md5 } from '../pubUtils/sdkUtil'; import { UserOrderModel } from '@db/UserOrder'; import { nowSeconds } from 'app/pubUtils/timeUtil'; import { RoleModel } from '@db/Role'; @@ -15,6 +15,9 @@ import { GuildModel } from '@db/Guild'; import { ServerlistModel } from '@db/Serverlist'; import moment = require('moment'); import { RegionModel } from '@db/Region'; +import { ActivityPublicAccountCodeModel } from '@db/ActivityPublicAccountCode'; +import { GiftCodeDetailModel } from '@db/GiftCodeDetail'; +import { GiftCodeModel } from '@db/GiftCode'; /** * Test Service @@ -200,7 +203,7 @@ export default class Sdk extends Service { return ctx.service.utils.resResult(SDK_37_REFUND_CODE.SUCCESS, ''); } - public check37WordsSign(params: RoleNameCallBackParam|GuildNameCallBackParam|GetGuildInfoByUserParam) { + public check37WordsSign(params: RoleNameCallBackParam|GuildNameCallBackParam|GetGuildInfoByUserParam|SendGiftCodeParam) { let sign = get37Md5SignB(params.getBody(), SDK_37_CONST.CHAT_KEY); console.log('******37Sign', sign); return sign == params.sign; @@ -418,4 +421,85 @@ export default class Sdk extends Service { ta.track(event); } + public async sendGiftCode(params: SendGiftCodeParam) { + const { ctx } = this; + try { + let validateResult = this.sendGiftValidata(params); + if(validateResult.code != SDK_37_SEND_GIFT_RESULT_CODE.SUCCESS.code) return validateResult; + + let channelId = getChannelId('37', params.user_id); + console.log(channelId); + let user = await UserModel.findUserByChannel(channelId); + if(!user) { + console.error('用户名违规处理, 未找到玩家账号', channelId); + return resResult(SDK_37_SEND_GIFT_RESULT_CODE.ORDER_DUPLICATE); + } + + if(params.gift_id.startsWith(PUBLIC_ACCOUNT_GIFT)) { + let activityId = parseInt(params.gift_id.replace(PUBLIC_ACCOUNT_GIFT, '')); + if(!activityId) return resResult(SDK_37_SEND_GIFT_RESULT_CODE.GIFT_NOT_FOUND); + + let data = await ActivityPublicAccountCodeModel.findByChannel(activityId, channelId); + if(data) return resResult(SDK_37_SEND_GIFT_RESULT_CODE.ORDER_DUPLICATE); + + await ActivityPublicAccountCodeModel.sendGift(activityId, channelId, params.server_id, params.role_id, params.order_id); + await ctx.service.utils.pushPubAccountGiftChannel(activityId, user.userCode, user.channelId); + + } else { + + let giftCodeDetail = await GiftCodeDetailModel.findByCode(params.gift_id); + if(!giftCodeDetail) { + return resResult(SDK_37_SEND_GIFT_RESULT_CODE.GIFT_NOT_FOUND); + } + let giftCode = await GiftCodeModel.findByGiftId(giftCodeDetail.giftId); + if(!giftCode) { + return resResult(SDK_37_SEND_GIFT_RESULT_CODE.GIFT_NOT_FOUND); + } + if(giftCode.generateType == GIFT_GENERATE_TYPE.ONE_TO_ONE) { // 一人一条,单条不能被多人使用 + if(giftCodeDetail.usedNum > 0) return resResult(SDK_37_SEND_GIFT_RESULT_CODE.USE_CNT_MAX); + } + if(!giftCode.isEnable) return resResult(SDK_37_SEND_GIFT_RESULT_CODE.GIFT_CODE_HAS_EXPIRED); + if (giftCode.beginTime > nowSeconds()) return resResult(SDK_37_SEND_GIFT_RESULT_CODE.GIFT_CODE_NOT_START); + if (giftCode.endTime < nowSeconds()) return resResult(SDK_37_SEND_GIFT_RESULT_CODE.GIFT_CODE_HAS_EXPIRED); + + if(giftCode.channel.indexOf('all') == -1 && giftCode.channel.indexOf(user.channelInfo?.platformAppid) == -1) { + return resResult(SDK_37_SEND_GIFT_RESULT_CODE.GIFT_CHANNEL_ERR); + } + + let role = await RoleModel.findByUid(user.uid, params.server_id); + if(!role) return resResult(SDK_37_SEND_GIFT_RESULT_CODE.ROLE_NOT_FOUND); + + if (giftCodeDetail.roleIds.indexOf(role.roleId) != -1) { + return resResult(SDK_37_SEND_GIFT_RESULT_CODE.ORDER_DUPLICATE); + } + + let checkHasUse = await GiftCodeDetailModel.checkHasUsed(role.roleId, giftCodeDetail.giftId); + if(checkHasUse) { + return resResult(SDK_37_SEND_GIFT_RESULT_CODE.ORDER_DUPLICATE); + } + + await GiftCodeDetailModel.increaseUsedNum(giftCodeDetail.code, role.roleId, role.roleName, role.serverId, params.order_id); + await GiftCodeModel.increaseUsedNum(giftCode.id); + + await ctx.service.utils.pushGiftCodeChannel(role.roleId, giftCode.id); + } + + return resResult(SDK_37_SEND_GIFT_RESULT_CODE.SUCCESS, []); + } catch(e) { + console.error('sendGiftCode err', e); + return ctx.service.utils.resResult(SDK_37_SEND_GIFT_RESULT_CODE.INTERNAL_ERR, ''); + } + } + + public sendGiftValidata(params: SendGiftCodeParam) { + if(!params.checkParams()) return resResult(SDK_37_SEND_GIFT_RESULT_CODE.WRONG_PARAMS); + + let { user_id, server_id, role_id, gift_id, order_id, time } = params; + let str = `${user_id}${server_id}${role_id}${gift_id}${order_id}${time}${SDK_37_CONST.CHAT_KEY}`; + let sign = md5(str); + console.log('#### str: ', str, 'sign', sign); + if(sign != params.sign) return resResult(SDK_37_SEND_GIFT_RESULT_CODE.SIGN_ERR); + + return resResult(SDK_37_SEND_GIFT_RESULT_CODE.SUCCESS); + } } diff --git a/web-server/app/service/Utils.ts b/web-server/app/service/Utils.ts index b704aca8f..27d706e99 100644 --- a/web-server/app/service/Utils.ts +++ b/web-server/app/service/Utils.ts @@ -77,6 +77,28 @@ export default class Utils extends Service { } } + public async pushPubAccountGiftChannel(activityId: number, userCode: string, channelId: string) { + let redisClient: RedisClient = this.ctx.app.context.redisClient; + + try { + let name = getRedisSubChannel(REDIS_KEY.PUBLIC_ACCOUNT_GIFT, this.ctx.app.config.env); + await redisClient.publishAsync(name, `${activityId}|${userCode}|${channelId}`); + } catch(e) { + console.error('pushPubAccountGiftChannel', e); + } + } + + public async pushGiftCodeChannel(roleId: string, giftCode: string) { + let redisClient: RedisClient = this.ctx.app.context.redisClient; + + try { + let name = getRedisSubChannel(REDIS_KEY.SEND_GIFT_CODE, this.ctx.app.config.env); + await redisClient.publishAsync(name, `${roleId}|${giftCode}`); + } catch(e) { + console.error('pushPubAccountGiftChannel', e); + } + } + // 检测是否可以登录 public async validateCanLogin() { if(gameData.serverConst.CLOSE_LOGIN == 1) return false;