import { ApiCall } from "tsrpc"; import { ReqReceive, ResReceive } from '../../../shared/protocols/event/payForDiamond/PtlReceive'; import { playerCanReceive } from './ApiCanReceive'; import { PublicShared } from "../../../shared/public/public"; import { PlayerFun } from "../../../public/player"; type diamondWeightGroup = { weight: number; numrange: [ min: number, max: number]; }; const hasGotKeyPrefix = 'payForDiamond:hasGot:'; // 已领取额度 const hasGotLockKey = "payForDiamond:lock"; // 更新锁, TTL 1s const showOffListKeyPrefix = 'payForDiamond:ShowOff:'; // 需要炫耀的玩家列表。 其中需要包含信息:玩家区服, 玩家玩家名称, 领到的数量 /** * @param groups 各分组及其权重 */ function randomWithWeight(groups: diamondWeightGroup[]) { let maxAmount: number; let totalWeights = 0; for (const group of groups) { totalWeights += group.weight; if (!maxAmount) { maxAmount = group.numrange[1]; } else { maxAmount = Math.max(maxAmount, group.numrange[1]); } } const randomValue = Math.random() * totalWeights; // 随机值落在[0, totalWeights) 之间 let currSum = 0; for (const group of groups) { if (currSum <= randomValue && randomValue < currSum + group.weight) { return { group, maxAmount }; } else { currSum += group.weight; } } const length = groups.length; return { group: groups[length - 1], maxAmount }; } /** * 计算玩家分得的钻石数量 * @param remaining * @param group * @param maxAmount */ function calcDiamondGot(remaining: number, group: diamondWeightGroup, maxAmount: number) { const [min, max] = group.numrange; const randomAmount = Math.floor(Math.random() * (max - min)) + min + 1; // max 值应能够取到, 故 +1 // 剩余数额小于组内随机得到的值, 全给吧 if (randomAmount > remaining) { return remaining; } // 随机值大于最大值, 取最大值 if (randomAmount > maxAmount) { return maxAmount; } return randomAmount; } export default async function (call: ApiCall) { const canReceiveResult = await playerCanReceive(call); if (canReceiveResult) { // 该值不存在的情况已被函数直接返回 error, 无需再处理 if (!canReceiveResult.result) { return call.succ({ amount: 0, timesRemaining: 0, showOff: false }); } const activityData = canReceiveResult.activityInfo.data; // 更新 redis 领取记录之前先加锁, 防止多领 const lockResult = await G.crossioredis.setnx(hasGotLockKey, 1); if (lockResult) { await G.crossioredis.expire(hasGotLockKey, 1); // 设置 ttl 避免死锁 const activityId = call.req.activityId; const hasReceivedKey = hasGotKeyPrefix + activityId; const hasReceivedStr = await G.crossioredis.get(hasReceivedKey); const hasReceived = hasReceivedStr? parseInt(hasReceivedStr) : 0; const remaining = activityData['totalmoney'] - hasReceived; if (remaining <= 0) { return call.succ({ amount: 0 }); } else { const { group, maxAmount } = randomWithWeight(activityData['groupConf']['base']['arr']); const gotAmount = calcDiamondGot(remaining, group, maxAmount); await G.crossioredis.incrby(hasReceivedKey, Math.abs(gotAmount)); // 添加已领取的额度 await G.crossioredis.del(hasGotLockKey); // 移除锁 await PlayerFun.sendPrize(call, [{ 'a': 'attr', 't': 'rmbmoney', 'n': gotAmount }]); const showOff = gotAmount >= activityData['groupConf']['base']['loglimit']; call.succ({ // 领取核心逻辑完成, 请求可以返回了 amount: gotAmount, timesRemaining: 0, showOff, }); // 添加玩家领取记录 const zeroTime = PublicShared.getToDayZeroTime(); const setObj = {}; setObj[zeroTime] = gotAmount; await G.mongodb.cEvent('payForDiamond').updateOne({ uid: call.uid, type: 'payForDiamond' }, { $set: setObj }, {upsert: true}); // 炫耀 if (showOff) { const msg = JSON.stringify({ name: call.conn.gud.name, gotAmount, serverID: call.conn.gud.sid }); const showOffListKey = showOffListKeyPrefix + activityId; await G.crossioredis.lpush(showOffListKey, msg); await G.crossioredis.ltrim(showOffListKey, 0, 49); // 限制列表保存 50 条消息, 避免无限增长 } } } else { return call.succ({ amount: 0 }); } } }