优化限流,不同群分离
This commit is contained in:
@@ -103,21 +103,63 @@ class XiuxianRedisDB:
|
||||
except Exception as e:
|
||||
logger.error(f"失效玩家缓存失败: {e}")
|
||||
|
||||
def set_rate_limit(self, user_id: str, cmd: str, seconds: int) -> bool:
|
||||
def set_rate_limit(self, user_id: str, group_id: str, cmd: str, seconds: int) -> bool:
|
||||
"""设置限流,支持自然日重置(当seconds >= 86400时)"""
|
||||
try:
|
||||
with self.get_redis() as r:
|
||||
key = f"xiuxian:rate_limit:user:{user_id}:cmd:{cmd}"
|
||||
r.setex(key, seconds, "1")
|
||||
# 如果是24小时或更长的限流(如签到),使用基于日期的键
|
||||
if seconds >= 86400:
|
||||
# 使用自然日:键包含日期,过期时间设为到明天0点的秒数
|
||||
now = datetime.now(timezone.utc)
|
||||
# 获取明天的0点(UTC)
|
||||
tomorrow = (now + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
# 计算到明天0点的秒数
|
||||
expire_seconds = int((tomorrow - now).total_seconds())
|
||||
# 使用日期作为键的一部分,包含group_id
|
||||
date_str = now.strftime("%Y-%m-%d")
|
||||
key = f"xiuxian:rate_limit:group:{group_id}:user:{user_id}:cmd:{cmd}:date:{date_str}"
|
||||
r.setex(key, expire_seconds, "1")
|
||||
else:
|
||||
# 短时间限流,使用原来的逻辑,包含group_id
|
||||
key = f"xiuxian:rate_limit:group:{group_id}:user:{user_id}:cmd:{cmd}"
|
||||
r.setex(key, seconds, "1")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"设置限流失败: {e}")
|
||||
return False
|
||||
|
||||
def check_rate_limited(self, user_id: str, cmd: str) -> bool:
|
||||
def check_rate_limited(self, user_id: str, group_id: str, cmd: str) -> bool:
|
||||
"""检查限流,支持自然日重置(当限流时间 >= 86400秒时)"""
|
||||
try:
|
||||
with self.get_redis() as r:
|
||||
key = f"xiuxian:rate_limit:user:{user_id}:cmd:{cmd}"
|
||||
return r.exists(key) == 1
|
||||
# 检查是否是长时间限流(如签到)
|
||||
# 先尝试基于日期的键(新格式,包含group_id)
|
||||
now = datetime.now(timezone.utc)
|
||||
date_str = now.strftime("%Y-%m-%d")
|
||||
date_key = f"xiuxian:rate_limit:group:{group_id}:user:{user_id}:cmd:{cmd}:date:{date_str}"
|
||||
|
||||
# 如果基于日期的键存在,说明今天已限流
|
||||
if r.exists(date_key) == 1:
|
||||
return True
|
||||
|
||||
# 兼容旧格式:检查是否有旧的限流键(不带group_id和日期)
|
||||
old_key_no_group = f"xiuxian:rate_limit:user:{user_id}:cmd:{cmd}"
|
||||
old_key_with_date = f"xiuxian:rate_limit:user:{user_id}:cmd:{cmd}:date:{date_str}"
|
||||
|
||||
# 检查旧格式(不带group_id,带日期)
|
||||
if r.exists(old_key_with_date) == 1:
|
||||
return True
|
||||
|
||||
# 检查旧格式(不带group_id,不带日期)
|
||||
if r.exists(old_key_no_group) == 1:
|
||||
# 如果旧键存在,检查是否过期(TTL > 0表示未过期)
|
||||
ttl = r.ttl(old_key_no_group)
|
||||
if ttl > 0:
|
||||
return True
|
||||
# 如果已过期,删除旧键
|
||||
r.delete(old_key_no_group)
|
||||
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"检查限流失败: {e}")
|
||||
return False
|
||||
@@ -424,7 +466,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5)
|
||||
return False, "Redis未初始化"
|
||||
|
||||
if self.redis_db.check_rate_limited(sender, cmd):
|
||||
if self.redis_db.check_rate_limited(sender, roomid or "", cmd):
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), "⚠️ 操作过于频繁,请稍候再试", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5)
|
||||
@@ -473,10 +515,10 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
|
||||
return False, "未知命令"
|
||||
|
||||
def _rate_set(self, user_id: str, cmd: str):
|
||||
def _rate_set(self, user_id: str, group_id: str, cmd: str):
|
||||
"""设置用户维度 Redis 冷却键,用于防骚扰与防封。"""
|
||||
seconds = self.seconds_rl.get(cmd, 3)
|
||||
self.redis_db.set_rate_limit(user_id, cmd, seconds)
|
||||
self.redis_db.set_rate_limit(user_id, group_id, cmd, seconds)
|
||||
|
||||
def _check_status_update(self, player: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""状态机自动流转:过期的 Unstable_Qi/Injured 恢复为 Idle。"""
|
||||
@@ -531,7 +573,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
except Exception as e:
|
||||
logger.warning(f"创建玩家数据库记录失败: {e}, user_id={sender}")
|
||||
self._save_player(player)
|
||||
self._rate_set(sender, "注册修仙")
|
||||
self._rate_set(sender, roomid or "", "注册修仙")
|
||||
# 初始化境界排行榜分值
|
||||
self.redis_db.leaderboard_realm_add(sender, float(self._realm_score(player["realm"])) )
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), f"✅ 注册成功,道号:{dao_name}\n灵根:{root_name}", sender)
|
||||
@@ -603,7 +645,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
stones_gain = pts // rate
|
||||
player["spirit_stone"] = int(player.get("spirit_stone", 0)) + stones_gain
|
||||
self._save_player(player)
|
||||
self._rate_set(sender, "积分购石")
|
||||
self._rate_set(sender, roomid or "", "积分购石")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 兑换成功,消耗积分{pts},获得灵石{stones_gain}({rate}积分=1灵石)", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -633,7 +675,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), msg, sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 20)
|
||||
self._rate_set(sender, "我的状态")
|
||||
self._rate_set(sender, roomid or "", "我的状态")
|
||||
return True, "状态展示"
|
||||
|
||||
async def _cmd_cultivate(self, bot: WechatAPIClient, sender: str, roomid: str) -> Tuple[bool, str]:
|
||||
@@ -664,7 +706,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
player["status"] = "Cultivating"
|
||||
player["last_cultivate_time"] = datetime.now(timezone.utc).isoformat()
|
||||
self._save_player(player)
|
||||
self._rate_set(sender, "闭关")
|
||||
self._rate_set(sender, roomid or "", "闭关")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), "✅ 已进入闭关,期间安全不可被劫掠", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -704,7 +746,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
# 自动层级提升(不跨瓶颈)
|
||||
self._auto_layer_up(sender, player)
|
||||
self.redis_db.leaderboard_add(sender, float(player["cultivation_points"]))
|
||||
self._rate_set(sender, "出关")
|
||||
self._rate_set(sender, roomid or "", "出关")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), f"✅ 出关成功,获得修为:{gain}({duration_hours:.1f}小时)\n当前修为:{player['cultivation_points']}\n状态:气息不稳 {self.unstable_qi_minutes}分钟", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 20)
|
||||
@@ -737,7 +779,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
# 自动层级提升(不跨瓶颈)
|
||||
self._auto_layer_up(sender, player)
|
||||
self.redis_db.leaderboard_add(sender, float(player["cultivation_points"]))
|
||||
self._rate_set(sender, "聚灵")
|
||||
self._rate_set(sender, roomid or "", "聚灵")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), f"✅ 聚灵成功,消耗灵石{qty},获得修为{qty * 10}", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -754,7 +796,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), "\n".join(lines), sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 20)
|
||||
self._rate_set(sender, "排行榜")
|
||||
self._rate_set(sender, roomid or "", "排行榜")
|
||||
return True, "排行榜"
|
||||
|
||||
def _get_player(self, user_id: str, group_id: str) -> Optional[Dict[str, Any]]:
|
||||
@@ -867,7 +909,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
# self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5)
|
||||
return False, "未注册"
|
||||
player = self._check_status_update(player)
|
||||
if self.redis_db.check_rate_limited(sender, "签到"):
|
||||
if self.redis_db.check_rate_limited(sender, roomid or "", "签到"):
|
||||
# client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "今日已签到,请明日再来", sender)
|
||||
# if self.revoke:
|
||||
# self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5)
|
||||
@@ -875,7 +917,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
reward = 50
|
||||
player["spirit_stone"] = int(player.get("spirit_stone", 0)) + reward
|
||||
self._save_player(player)
|
||||
self._rate_set(sender, "签到")
|
||||
self._rate_set(sender, roomid or "", "签到")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 签到成功,获得灵石{reward}", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -888,7 +930,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "\n".join(lines), sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 20)
|
||||
self._rate_set(sender, "坊市")
|
||||
self._rate_set(sender, roomid or "", "坊市")
|
||||
return True, "坊市"
|
||||
|
||||
async def _cmd_buy(self, bot: WechatAPIClient, sender: str, roomid: str, content: str) -> Tuple[bool, str]:
|
||||
@@ -937,7 +979,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
except Exception:
|
||||
pass
|
||||
self._save_player(player)
|
||||
self._rate_set(sender, "购买")
|
||||
self._rate_set(sender, roomid or "", "购买")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 购买成功,{item_name} × {qty}", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -966,7 +1008,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "\n".join(lines), sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 20)
|
||||
self._rate_set(sender, "背包")
|
||||
self._rate_set(sender, roomid or "", "背包")
|
||||
return True, "背包"
|
||||
|
||||
async def _cmd_breakthrough(self, bot: WechatAPIClient, sender: str, roomid: str) -> Tuple[bool, str]:
|
||||
@@ -1055,7 +1097,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
# 成功,更新境界并排行榜
|
||||
self._set_realm(sender, player, pill_conf["target"])
|
||||
self.redis_db.leaderboard_add(sender, float(player["cultivation_points"]))
|
||||
self._rate_set(sender, "突破")
|
||||
self._rate_set(sender, roomid or "", "突破")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 突破成功,晋升至{pill_conf['target']}", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -1063,7 +1105,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
else:
|
||||
# 失败时也要保存玩家数据
|
||||
self._save_player(player)
|
||||
self._rate_set(sender, "突破")
|
||||
self._rate_set(sender, roomid or "", "突破")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "❌ 突破失败", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5)
|
||||
@@ -1116,7 +1158,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
if roll < hard_conf["rate"]:
|
||||
self._set_realm(sender, player, hard_conf["target"])
|
||||
self.redis_db.leaderboard_add(sender, float(player["cultivation_points"]))
|
||||
self._rate_set(sender, "强行突破")
|
||||
self._rate_set(sender, roomid or "", "强行突破")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 强行突破成功,晋升至{hard_conf['target']}", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -1124,7 +1166,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
else:
|
||||
# 失败时也要保存玩家数据
|
||||
self._save_player(player)
|
||||
self._rate_set(sender, "强行突破")
|
||||
self._rate_set(sender, roomid or "", "强行突破")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "❌ 强行突破失败,灵气反噬!", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5)
|
||||
@@ -1196,7 +1238,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
pass
|
||||
self._save_player(defender)
|
||||
self._save_player(attacker)
|
||||
self._rate_set(sender, "劫掠")
|
||||
self._rate_set(sender, roomid or "", "劫掠")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 劫掠成功,获得灵石{gain}", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -1206,7 +1248,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
self.revoke.add_message_to_revoke(roomid, g_client_msg_id, g_create_time, g_new_msg_id, 10)
|
||||
return True, "劫掠成功"
|
||||
else:
|
||||
self._rate_set(sender, "劫掠")
|
||||
self._rate_set(sender, roomid or "", "劫掠")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "❌ 劫掠失败", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5)
|
||||
@@ -1256,7 +1298,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
# 使用_save_player确保同时保存到Redis和MariaDB
|
||||
self._save_player(giver)
|
||||
self._save_player(receiver)
|
||||
self._rate_set(sender, "赠与")
|
||||
self._rate_set(sender, roomid or "", "赠与")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 已向 {target} 赠与灵石 {qty}", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -1307,7 +1349,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
self.redis_db.invalidate_player(target, receiver.get("group_id", ""))
|
||||
self._save_player(giver)
|
||||
self._save_player(receiver)
|
||||
self._rate_set(sender, "赠送")
|
||||
self._rate_set(sender, roomid or "", "赠送")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 已向 {target} 赠送 {item_name} × {qty}", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -1350,7 +1392,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
except Exception:
|
||||
pass
|
||||
self._save_player(player)
|
||||
self._rate_set(sender, "创建门派")
|
||||
self._rate_set(sender, roomid or "", "创建门派")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 门派已创建:{name}", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -1385,7 +1427,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
except Exception:
|
||||
pass
|
||||
self._save_player(player)
|
||||
self._rate_set(sender, "加入门派")
|
||||
self._rate_set(sender, roomid or "", "加入门派")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, f"✅ 已加入门派:{name}", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
@@ -1406,7 +1448,7 @@ class XiuxianPlugin(MessagePluginInterface):
|
||||
except Exception:
|
||||
pass
|
||||
self._save_player(player)
|
||||
self._rate_set(sender, "退出门派")
|
||||
self._rate_set(sender, roomid or "", "退出门派")
|
||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(roomid or sender, "✅ 已退出门派", sender)
|
||||
if self.revoke:
|
||||
self.revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 10)
|
||||
|
||||
Reference in New Issue
Block a user