From 40774138d2694b9eabe89cf938cc03f9310bf7c0 Mon Sep 17 00:00:00 2001 From: chenkai Date: Fri, 22 Dec 2023 18:27:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B0=86=E6=B4=BB=E5=8A=A8=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E8=B7=A8=E6=9C=8D=E6=B4=BB=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/payForDiamond/ApiCanReceive.ts | 14 +++- src/api_s2c/event/payForDiamond/ApiReceive.ts | 84 +++++++++++-------- src/global.ts | 2 + src/ioredis.ts | 5 +- .../event/payForDiamond/PtlReceive.ts | 4 +- src/shared/protocols/serviceProto.ts | 6 +- 6 files changed, 72 insertions(+), 43 deletions(-) diff --git a/src/api_s2c/event/payForDiamond/ApiCanReceive.ts b/src/api_s2c/event/payForDiamond/ApiCanReceive.ts index feb4f1a..e356c2f 100644 --- a/src/api_s2c/event/payForDiamond/ApiCanReceive.ts +++ b/src/api_s2c/event/payForDiamond/ApiCanReceive.ts @@ -4,13 +4,21 @@ import { ReqCanReceive, ResCanReceive } from '../../../shared/protocols/event/pa import { PublicShared } from "../../../shared/public/public"; import { ActionLog } from "../../../public/actionLog/actionLog"; +const hasGotKeyPrefix = 'payForDiamond:hasGot:'; // 已领取额度 +const showOffListKeyPrefix = 'payForDiamond:ShowOff:'; // 需要炫耀的玩家列表。 其中需要包含信息:玩家区服, 玩家玩家名称, 领到的数量 + export async function playerCanReceive(call: ApiCall) { - const activityInfo = await HuoDongFun.getHdidInfo(call, call.req.activityId); + const activityId = call.req.activityId; + const activityInfo = await HuoDongFun.getHdidInfo(call, activityId); if (!activityInfo) { return call.error('No such activity'); } - const remaining = activityInfo.data['totalmoney']; - const showOffList = activityInfo.data['showOffList'] || []; + const hasReceivedKey = hasGotKeyPrefix + activityId; + const hasReceivedStr = await G.iorediscross.get(hasReceivedKey); + const hasReceived = hasReceivedStr? parseInt(hasReceivedStr) : 0; + const remaining = activityInfo.data['totalmoney'] - hasReceived; + const showOffResult = await G.iorediscross.lrange(showOffListKeyPrefix + activityId, 0, -1); + const showOffList = showOffResult.map(result => JSON.parse(result)); const zeroTime = PublicShared.getToDayZeroTime(); const vipScore = await ActionLog.getDayLog(call.uid, 'pay'); const price = activityInfo.data['price']; diff --git a/src/api_s2c/event/payForDiamond/ApiReceive.ts b/src/api_s2c/event/payForDiamond/ApiReceive.ts index 62c51fd..3343409 100644 --- a/src/api_s2c/event/payForDiamond/ApiReceive.ts +++ b/src/api_s2c/event/payForDiamond/ApiReceive.ts @@ -2,7 +2,6 @@ import { ApiCall } from "tsrpc"; import { ReqReceive, ResReceive } from '../../../shared/protocols/event/payForDiamond/PtlReceive'; import { playerCanReceive } from './ApiCanReceive'; import { PublicShared } from "../../../shared/public/public"; -import { ChatFun } from "../../../public/chat"; import { PlayerFun } from "../../../public/player"; type diamondWeightGroup = { @@ -10,6 +9,9 @@ type diamondWeightGroup = { numrange: [ min: number, max: number]; }; +const hasGotKeyPrefix = 'payForDiamond:hasGot:'; // 已领取额度 +const hasGotLockKey = "payForDiamond:lock"; // 更新锁, TTL 1s +const showOffListKeyPrefix = 'payForDiamond:ShowOff:'; // 需要炫耀的玩家列表。 其中需要包含信息:玩家区服, 玩家玩家名称, 领到的数量 /** * @param groups 各分组及其权重 */ @@ -59,7 +61,7 @@ function calcDiamondGot(remaining: number, group: diamondWeightGroup, maxAmount: export default async function (call: ApiCall) { const canReceiveResult = await playerCanReceive(call); - if (canReceiveResult) { + if (canReceiveResult) { // 该值不存在的情况已被函数直接返回 error, 无需再处理 if (!canReceiveResult.result) { return call.succ({ amount: 0, @@ -68,40 +70,52 @@ export default async function (call: ApiCall) { }); } const activityData = canReceiveResult.activityInfo.data; - const remaining = activityData['totalmoney']; - const { group, maxAmount } = randomWithWeight(activityData['groupConf']['base']['arr']); - const gotAmount = calcDiamondGot(remaining, group, maxAmount); - // 减去余额 - const filter = { - hdid: call.req.activityId, - $expr: { $gte: ['$data.totalmoney', gotAmount ]}, - }; - const updateDoc = { - $inc: { 'data.totalmoney': -1 * Math.abs(gotAmount) } - } - const updateResult = await G.mongodb.collection('hdinfo').updateOne(filter, updateDoc); - // 更新成功 - if (updateResult.modifiedCount) { - const showOff = gotAmount >= activityData['groupConf']['base']['loglimit']; - await PlayerFun.sendPrize(call, [{ 'a': 'attr', 't': 'rmbmoney', 'n': gotAmount }]); - // 请求返回 - 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}); - // 记录玩家日志. 仅保留最近 10 条 - if (showOff) { - await G.mongodb.collection('hdinfo').updateOne({ hdid: call.req.activityId, }, { $push: { 'data.showOffList': { $each: [{ name: call.conn.gud.name, gotAmount }], $slice: -10 }}}); + // 更新 redis 领取记录之前先加锁, 防止多领 + const lockResult = await G.iorediscross.setnx(hasGotLockKey, 1); + if (lockResult) { + await G.iorediscross.expire(hasGotLockKey, 1); // 设置 ttl 避免死锁 + const activityId = call.req.activityId; + const hasReceivedKey = hasGotKeyPrefix + activityId; + const hasReceivedStr = await G.iorediscross.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.iorediscross.incrby(hasReceivedKey, -1 * Math.abs(gotAmount)); // 添加已领取的额度 + await G.iorediscross.del(); // 移除锁 + 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.iorediscross.lpush(showOffListKey, msg); + await G.ioredis.ltrim(showOffListKey, 0, 49); // 限制列表保存 50 条消息, 避免无限增长 + } } + } else { + return call.succ({ + amount: 0 + }); } - } } \ No newline at end of file diff --git a/src/global.ts b/src/global.ts index 30fa869..e8e35a5 100644 --- a/src/global.ts +++ b/src/global.ts @@ -92,6 +92,8 @@ class _G { redis: redisJsonFun; /**ioredis连接对象 */ ioredis: Redis; + /** 跨服 ioredis 连接对象 */ + iorediscross: Redis; /**mongodb连接对象 */ mongodb: _mongodb; /**crossmongodb连接对象 */ diff --git a/src/ioredis.ts b/src/ioredis.ts index a304d58..00d7f52 100644 --- a/src/ioredis.ts +++ b/src/ioredis.ts @@ -16,4 +16,7 @@ export async function initIORedis() { G.ioredis = new Redis(G.argv.serverType == 'cross' ? G.config.crossRedisUrl : G.config.redisUrl,{ keyPrefix: preKey, }); -} \ No newline at end of file + G.iorediscross = new Redis(G.config.crossRedisUrl,{ + keyPrefix: "cross_", + }); +} diff --git a/src/shared/protocols/event/payForDiamond/PtlReceive.ts b/src/shared/protocols/event/payForDiamond/PtlReceive.ts index 6fe6681..cbcda79 100644 --- a/src/shared/protocols/event/payForDiamond/PtlReceive.ts +++ b/src/shared/protocols/event/payForDiamond/PtlReceive.ts @@ -4,6 +4,6 @@ export type ReqReceive = { export type ResReceive = { amount: number, - timesRemaining: number; - showOff: boolean; + timesRemaining?: number; + showOff?: boolean; }; diff --git a/src/shared/protocols/serviceProto.ts b/src/shared/protocols/serviceProto.ts index fa32088..2a66905 100644 --- a/src/shared/protocols/serviceProto.ts +++ b/src/shared/protocols/serviceProto.ts @@ -10188,14 +10188,16 @@ export const serviceProto: ServiceProto = { "name": "timesRemaining", "type": { "type": "Number" - } + }, + "optional": true }, { "id": 2, "name": "showOff", "type": { "type": "Boolean" - } + }, + "optional": true } ] },