优化限流,不同群分离

This commit is contained in:
liuwei
2025-11-19 09:46:42 +08:00
parent cbda64f6e7
commit ac7e983d19

View File

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