From 257433a1153bc2e084c5c7eebf7acff609daa5a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=91=E8=90=8C=E8=8A=BD?= <3205788256@qq.com> Date: Tue, 22 Jul 2025 07:57:33 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E4=BA=86=E5=A4=A7=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=95=B0=E6=8D=AE=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MainGame.gd | 52 +- MainGame.tscn | 6 +- Network/TCPNetworkManager.gd | 6 +- Script/BigPanel/LoginPanel.gd | 2 +- Script/BigPanel/PlayerStorePanel.gd | 12 +- .../SmallPanel/GlobalServerBroadcastPanel.gd | 47 +- Server/MongoDB迁移说明.md | 170 -- Server/{QQEmailSend.py => QQEmailSendAPI.py} | 107 +- Server/SMYMongoDBAPI.py | 1003 ++++----- Server/TCPGameServer.py | 271 ++- .../__pycache__/QQEmailSend.cpython-313.pyc | Bin 19702 -> 22796 bytes .../QQEmailSendAPI.cpython-313.pyc | Bin 0 -> 22799 bytes .../__pycache__/SMYMongoDBAPI.cpython-313.pyc | Bin 33024 -> 41064 bytes .../__pycache__/TCPGameServer.cpython-313.pyc | Bin 356177 -> 357906 bytes Server/chat/.gitkeep | 2 - Server/chat/2025-07-06.log | 8 - Server/chat/2025-07-09.log | 1 - Server/chat/2025-07-21.log | 5 - Server/config/crop_data.json | 86 - Server/config/daily_checkin_config.json | 82 - .../config/initial_player_data_template.json | 85 - Server/config/item_config.json | 26 - Server/config/lucky_draw_config.json | 113 - Server/config/new_player_config.json | 30 - Server/config/online_gift_config.json | 96 - Server/config/pet_data.json | 1466 ------------- Server/config/scare_crow_config.json | 8 - Server/config/stamina_config.json | 23 - Server/config/verification_codes.json | 1 - Server/config/wisdom_tree_data.json | 48 - Server/game_saves/2143323382.json | 22 +- Server/game_saves/2804775686.json | 1950 ++--------------- .../test_initial_player_template_migration.py | 155 ++ Server/test_optimized_config_api.py | 139 ++ server/game_saves/3205788256.json | 55 +- 35 files changed, 1304 insertions(+), 4773 deletions(-) delete mode 100644 Server/MongoDB迁移说明.md rename Server/{QQEmailSend.py => QQEmailSendAPI.py} (75%) create mode 100644 Server/__pycache__/QQEmailSendAPI.cpython-313.pyc delete mode 100644 Server/chat/.gitkeep delete mode 100644 Server/chat/2025-07-06.log delete mode 100644 Server/chat/2025-07-09.log delete mode 100644 Server/chat/2025-07-21.log delete mode 100644 Server/config/crop_data.json delete mode 100644 Server/config/daily_checkin_config.json delete mode 100644 Server/config/initial_player_data_template.json delete mode 100644 Server/config/item_config.json delete mode 100644 Server/config/lucky_draw_config.json delete mode 100644 Server/config/new_player_config.json delete mode 100644 Server/config/online_gift_config.json delete mode 100644 Server/config/pet_data.json delete mode 100644 Server/config/scare_crow_config.json delete mode 100644 Server/config/stamina_config.json delete mode 100644 Server/config/verification_codes.json delete mode 100644 Server/config/wisdom_tree_data.json create mode 100644 Server/test_initial_player_template_migration.py create mode 100644 Server/test_optimized_config_api.py diff --git a/MainGame.gd b/MainGame.gd index 95dd9a2..8af966e 100644 --- a/MainGame.gd +++ b/MainGame.gd @@ -2205,6 +2205,9 @@ func _handle_new_player_gift_response(data): func _handle_global_broadcast_message(data: Dictionary): # 将消息传递给大喇叭面板处理 global_server_broadcast_panel.receive_broadcast_message(data) + + # 立即更新主界面大喇叭显示 + update_broadcast_display_from_message(data) # 处理全服大喇叭发送响应 func _handle_global_broadcast_response(data: Dictionary): @@ -2224,25 +2227,39 @@ func _handle_broadcast_history_response(data: Dictionary): global_server_broadcast_panel.receive_history_messages(data) # 更新主界面大喇叭显示为最新消息 - if global_server_broadcast: - var latest_message = global_server_broadcast_panel.get_latest_message() - if latest_message != "全服大喇叭": - global_server_broadcast.text = latest_message - print("主界面大喇叭已更新为: ", latest_message) - else: - global_server_broadcast.text = "全服大喇叭" + update_broadcast_display_from_panel() + +# 从消息数据直接更新大喇叭显示 +func update_broadcast_display_from_message(data: Dictionary): + if global_server_broadcast: + var username = data.get("username", "匿名") + var player_name = data.get("玩家昵称", "") + var content = data.get("content", "") + + # 优先显示玩家昵称 + var display_name = player_name if player_name != "" else username + global_server_broadcast.text = display_name + ": " + content + print("主界面大喇叭已更新为: ", global_server_broadcast.text) + +# 从面板获取最新消息更新大喇叭显示 +func update_broadcast_display_from_panel(): + if global_server_broadcast and global_server_broadcast_panel: + var latest_message = global_server_broadcast_panel.get_latest_message() + if latest_message != "暂无消息" and latest_message != "全服大喇叭": + global_server_broadcast.text = latest_message + print("主界面大喇叭已更新为: ", latest_message) + else: + global_server_broadcast.text = "全服大喇叭" # 初始化大喇叭显示 func _init_broadcast_display(): if global_server_broadcast and global_server_broadcast_panel: - # 先设置为空 - global_server_broadcast.text = "" + # 设置默认文本 + global_server_broadcast.text = "全服大喇叭" - # 直接从本地文件加载历史消息 + # 先从本地加载,然后请求服务器最新消息 _load_broadcast_from_local() - - # 无论是否有本地消息,都请求服务器获取最新消息 _request_latest_broadcast_message() # 从本地文件加载大喇叭消息 @@ -2269,20 +2286,21 @@ func _load_broadcast_from_local(): var content = latest.get("content", "") global_server_broadcast.text = display_name + ": " + content - - # 请求服务器获取最新的一条大喇叭消息 func _request_latest_broadcast_message(): - # 请求最近1天的消息,只获取最新的一条 + if tcp_network_manager_panel and tcp_network_manager_panel.is_connected_to_server(): + # 请求最近1天的消息 var success = tcp_network_manager_panel.send_message({ "type": "request_broadcast_history", "days": 1, - "limit": 1, # 只要最新的一条 + "limit": 10, # 获取最近10条,客户端取最新的 "timestamp": Time.get_unix_time_from_system() }) if not success: print("请求最新大喇叭消息失败") + else: + print("未连接到服务器,无法请求最新大喇叭消息") # 请求服务器历史消息用于刷新显示 func _request_server_history_for_refresh(): @@ -3361,4 +3379,4 @@ func _on_item_store_pressed() -> void: func _on_pet_store_pressed() -> void: pet_store_panel.show() - pass + pass diff --git a/MainGame.tscn b/MainGame.tscn index 385fc98..b8fb95d 100644 --- a/MainGame.tscn +++ b/MainGame.tscn @@ -270,7 +270,9 @@ glow_intensity = 1.0 glow_strength = 0.75 glow_bloom = 1.0 glow_blend_mode = 1 -fog_enabled = true +fog_mode = 1 +fog_sun_scatter = 1.0 +fog_density = 1.0 [node name="main" type="Node"] script = ExtResource("1_v3yaj") @@ -877,7 +879,7 @@ theme_override_constants/shadow_offset_y = 4 theme_override_constants/outline_size = 15 theme_override_constants/shadow_outline_size = 15 theme_override_font_sizes/font_size = 50 -text = "宠物背包2" +text = "宠物背包" horizontal_alignment = 1 [node name="QuitButton" type="Button" parent="UI/BigPanel/PetBagPanel"] diff --git a/Network/TCPNetworkManager.gd b/Network/TCPNetworkManager.gd index f44a680..e9ce6fe 100644 --- a/Network/TCPNetworkManager.gd +++ b/Network/TCPNetworkManager.gd @@ -456,7 +456,7 @@ func _on_data_received(data): # 添加商品到小卖部响应 elif action_type == "add_product_to_store": if success: - main_game.login_data["玩家小卖部"] = updated_data["玩家小卖部"] + main_game.login_data["小卖部配置"] = updated_data["小卖部配置"] main_game.crop_warehouse = updated_data["作物仓库"] # 更新UI @@ -472,7 +472,7 @@ func _on_data_received(data): # 下架小卖部商品响应 elif action_type == "remove_store_product": if success: - main_game.login_data["玩家小卖部"] = updated_data["玩家小卖部"] + main_game.login_data["小卖部配置"] = updated_data["小卖部配置"] main_game.crop_warehouse = updated_data["作物仓库"] # 更新UI @@ -510,7 +510,7 @@ func _on_data_received(data): elif action_type == "buy_store_booth": if success: main_game.money = updated_data["钱币"] - main_game.login_data["小卖部格子数"] = updated_data["小卖部格子数"] + main_game.login_data["小卖部配置"] = updated_data["小卖部配置"] # 更新UI main_game._update_ui() diff --git a/Script/BigPanel/LoginPanel.gd b/Script/BigPanel/LoginPanel.gd index 595d3ad..492c627 100644 --- a/Script/BigPanel/LoginPanel.gd +++ b/Script/BigPanel/LoginPanel.gd @@ -415,7 +415,7 @@ func _display_version_info(): func _handle_login_success(user_data: Dictionary): # 保存登录数据到主游戏 main_game.login_data = user_data.duplicate() - main_game.remaining_likes = user_data.get("今日剩余点赞次数", 10) + main_game.remaining_likes = user_data.get("点赞系统", {}).get("今日剩余点赞次数", 10) # 更新主游戏数据 main_game.experience = user_data.get("经验值", 0) diff --git a/Script/BigPanel/PlayerStorePanel.gd b/Script/BigPanel/PlayerStorePanel.gd index cd2c3a4..fedea84 100644 --- a/Script/BigPanel/PlayerStorePanel.gd +++ b/Script/BigPanel/PlayerStorePanel.gd @@ -50,13 +50,15 @@ func update_player_store_ui(): # 获取小卖部数据 if main_game.is_visiting_mode: # 访问模式:显示被访问玩家的小卖部 - player_store_data = main_game.visited_player_data.get("玩家小卖部", []) - max_store_slots = main_game.visited_player_data.get("小卖部格子数", 10) + var store_config = main_game.visited_player_data.get("小卖部配置", {"商品列表": [], "格子数": 10}) + player_store_data = store_config.get("商品列表", []) + max_store_slots = store_config.get("格子数", 10) buy_product_booth_button.hide() # 访问模式下隐藏购买格子按钮 else: # 正常模式:显示自己的小卖部 - player_store_data = main_game.login_data.get("玩家小卖部", []) - max_store_slots = main_game.login_data.get("小卖部格子数", 10) + var store_config = main_game.login_data.get("小卖部配置", {"商品列表": [], "格子数": 10}) + player_store_data = store_config.get("商品列表", []) + max_store_slots = store_config.get("格子数", 10) buy_product_booth_button.show() # 正常模式下显示购买格子按钮 # 创建商品按钮 @@ -187,7 +189,7 @@ func _on_product_buy_selected(product_data: Dictionary, slot_index: int): batch_buy_popup.show_buy_popup( product_name, product_price, - "玩家小卖部商品", + "小卖部商品", "store_product", # 特殊类型标识 _on_confirm_buy_store_product, _on_cancel_buy_store_product diff --git a/Script/SmallPanel/GlobalServerBroadcastPanel.gd b/Script/SmallPanel/GlobalServerBroadcastPanel.gd index 964ae5a..a7aa1e9 100644 --- a/Script/SmallPanel/GlobalServerBroadcastPanel.gd +++ b/Script/SmallPanel/GlobalServerBroadcastPanel.gd @@ -85,8 +85,8 @@ func send_broadcast_message(): else: Toast.show("消息发送失败", Color.RED, 2.0, 1.0) -# 接收全服大喇叭消息 -func receive_broadcast_message(data: Dictionary): +# 统一的消息处理函数 +func _add_message_to_history(data: Dictionary): var username = data.get("username", "匿名") var player_name = data.get("玩家昵称", "") var content = data.get("content", "") @@ -109,17 +109,12 @@ func receive_broadcast_message(data: Dictionary): "display_name": display_name } - # 按时间戳插入到正确位置(保持时间顺序) - var inserted = false - for i in range(message_history.size()): - if message_history[i].get("timestamp", 0) > timestamp: - message_history.insert(i, message_record) - inserted = true - break - - # 如果没有找到插入位置,说明是最新消息,添加到末尾 - if not inserted: - message_history.append(message_record) + message_history.append(message_record) + +# 接收全服大喇叭消息 +func receive_broadcast_message(data: Dictionary): + # 使用统一的处理函数 + _add_message_to_history(data) # 保持消息历史记录在限制范围内 if message_history.size() > max_message_history: @@ -129,10 +124,6 @@ func receive_broadcast_message(data: Dictionary): if visible: update_message_display() - # 更新主界面的大喇叭显示 - if main_game and main_game.has_method("update_broadcast_display"): - main_game.update_broadcast_display(display_name + ": " + content) - # 保存到本地 save_chat_history() @@ -229,25 +220,19 @@ func receive_history_messages(data: Dictionary): print("大喇叭面板收到历史消息: ", messages.size(), " 条") if messages.size() > 0: - # 清空当前消息历史 + # 清空当前历史记录,重新加载 message_history.clear() - # 添加服务器返回的历史消息 + # 处理每条消息 for msg in messages: - var message_record = { - "username": msg.get("username", "匿名"), - "玩家昵称": msg.get("玩家昵称", ""), - "content": msg.get("content", ""), - "timestamp": msg.get("timestamp", 0), - "time_str": msg.get("time_str", ""), - "display_name": msg.get("display_name", msg.get("username", "匿名")) - } - message_history.append(message_record) - print("添加消息: ", message_record.display_name, ": ", message_record.content) + _add_message_to_history(msg) - # 按时间戳排序消息历史(从旧到新) + # 按时间戳排序 message_history.sort_custom(func(a, b): return a.get("timestamp", 0) < b.get("timestamp", 0)) - print("排序后消息历史大小: ", message_history.size()) + + # 保持消息历史记录在限制范围内 + if message_history.size() > max_message_history: + message_history = message_history.slice(-max_message_history) # 更新显示 update_message_display() diff --git a/Server/MongoDB迁移说明.md b/Server/MongoDB迁移说明.md deleted file mode 100644 index 3f5e345..0000000 --- a/Server/MongoDB迁移说明.md +++ /dev/null @@ -1,170 +0,0 @@ -# 萌芽农场 MongoDB 迁移说明 - -## 概述 - -本文档描述了萌芽农场项目从JSON配置文件迁移到MongoDB数据库的过程。目前已完成每日签到配置的迁移,为后续其他配置的迁移奠定了基础。 - -## 迁移内容 - -### 1. 已完成的迁移 - -#### 每日签到配置 (daily_checkin_config.json) -- **原位置**: `Server/config/daily_checkin_config.json` -- **新位置**: MongoDB数据库 `mengyafarm.gameconfig` 集合 -- **文档ID**: `687cce278e77ba00a7414ba2` -- **状态**: ✅ 已完成迁移 - -### 2. 数据库配置 - -#### 测试环境 -- **地址**: `localhost:27017` -- **数据库**: `mengyafarm` -- **集合**: `gameconfig` - -#### 生产环境 -- **地址**: `192.168.31.233:27017` -- **数据库**: `mengyafarm` -- **集合**: `gameconfig` - -## 技术实现 - -### 1. MongoDB API (SMYMongoDBAPI.py) - -创建了专门的MongoDB API类,提供以下功能: - -#### 核心功能 -- 数据库连接管理(测试/生产环境) -- 游戏配置的读取和更新 -- 通用文档操作(增删改查) -- 错误处理和日志记录 - -#### 主要方法 -```python -# 配置管理 -get_daily_checkin_config() # 获取每日签到配置 -update_daily_checkin_config() # 更新每日签到配置 -get_game_config(config_type) # 获取通用游戏配置 -set_game_config(config_type, data) # 设置通用游戏配置 - -# 通用操作 -insert_document(collection, doc) # 插入文档 -find_documents(collection, query) # 查找文档 -update_document(collection, query, update) # 更新文档 -delete_document(collection, query) # 删除文档 -``` - -### 2. 服务器集成 (TCPGameServer.py) - -#### 修改内容 -- 添加MongoDB API导入和初始化 -- 修改 `_load_daily_check_in_config()` 方法,优先使用MongoDB -- 添加 `_update_daily_checkin_config_to_mongodb()` 方法 -- 实现优雅降级:MongoDB失败时自动回退到JSON文件 - -#### 配置加载策略 -1. **优先**: 尝试从MongoDB获取配置 -2. **备选**: 从JSON文件加载配置 -3. **兜底**: 使用默认配置 - -## 测试验证 - -### 1. MongoDB API测试 -运行 `python SMYMongoDBAPI.py` 进行基础功能测试 - -### 2. 迁移功能测试 -运行 `python test_mongodb_migration.py` 进行完整迁移测试 - -### 3. 服务器集成测试 -运行 `python test_server_mongodb.py` 进行服务器集成测试 - -## 使用说明 - -### 1. 环境配置 - -#### 测试环境 -```python -api = SMYMongoDBAPI("test") # 连接到 localhost:27017 -``` - -#### 生产环境 -```python -api = SMYMongoDBAPI("production") # 连接到 192.168.31.233:27017 -``` - -### 2. 获取配置 -```python -# 获取每日签到配置 -config = api.get_daily_checkin_config() - -# 获取通用游戏配置 -config = api.get_game_config("config_type") -``` - -### 3. 更新配置 -```python -# 更新每日签到配置 -success = api.update_daily_checkin_config(new_config) - -# 设置通用游戏配置 -success = api.set_game_config("config_type", config_data) -``` - -## 后续迁移计划 - -### 1. 待迁移的配置文件 -- [ ] `item_config.json` - 道具配置 -- [ ] `pet_data.json` - 宠物配置 -- [ ] 其他游戏配置文件 - -### 2. 迁移步骤 -1. 将JSON文件导入到MongoDB -2. 修改对应的加载方法 -3. 添加更新方法 -4. 编写测试用例 -5. 验证功能正常 - -### 3. 迁移原则 -- **渐进式迁移**: 一次迁移一个配置文件 -- **向后兼容**: 保持JSON文件作为备选方案 -- **充分测试**: 每个迁移都要有完整的测试覆盖 -- **文档更新**: 及时更新相关文档 - -## 注意事项 - -### 1. 数据安全 -- 定期备份MongoDB数据 -- 重要配置修改前先备份 -- 测试环境验证后再应用到生产环境 - -### 2. 性能考虑 -- MongoDB连接池管理 -- 配置缓存策略 -- 错误重试机制 - -### 3. 监控和日志 -- 记录配置加载来源 -- 监控MongoDB连接状态 -- 记录配置更新操作 - -## 故障排除 - -### 1. MongoDB连接失败 -- 检查MongoDB服务是否启动 -- 验证连接地址和端口 -- 检查网络连接 - -### 2. 配置加载失败 -- 检查MongoDB中是否存在对应文档 -- 验证文档格式是否正确 -- 查看服务器日志获取详细错误信息 - -### 3. 配置更新失败 -- 检查MongoDB权限 -- 验证更新数据格式 -- 确认文档ID是否正确 - -## 总结 - -本次迁移成功实现了每日签到配置从JSON文件到MongoDB的迁移,建立了完整的MongoDB API框架,为后续其他配置的迁移提供了可靠的基础。迁移过程采用了渐进式和向后兼容的策略,确保了系统的稳定性和可靠性。 - -通过测试验证,MongoDB迁移功能运行正常,服务器能够正确使用MongoDB中的配置数据,同时保持了JSON文件的备选方案,为后续的全面迁移奠定了坚实的基础。 \ No newline at end of file diff --git a/Server/QQEmailSend.py b/Server/QQEmailSendAPI.py similarity index 75% rename from Server/QQEmailSend.py rename to Server/QQEmailSendAPI.py index 66994bf..7ea63cf 100644 --- a/Server/QQEmailSend.py +++ b/Server/QQEmailSendAPI.py @@ -195,7 +195,7 @@ class EmailVerification: @staticmethod def save_verification_code(qq_number, verification_code, expiry_time=300, code_type="register"): """ - 保存验证码到缓存文件 + 保存验证码到MongoDB(优先)或缓存文件(备用) 参数: qq_number (str): QQ号 @@ -208,6 +208,21 @@ class EmailVerification: """ import time + # 优先尝试使用MongoDB + try: + from SMYMongoDBAPI import SMYMongoDBAPI + mongo_api = SMYMongoDBAPI("test") + if mongo_api.is_connected(): + success = mongo_api.save_verification_code(qq_number, verification_code, expiry_time, code_type) + if success: + print(f"[验证码系统-MongoDB] 为QQ {qq_number} 保存{code_type}验证码: {verification_code}") + return True + else: + print(f"[验证码系统-MongoDB] 保存失败,尝试使用JSON文件") + except Exception as e: + print(f"[验证码系统-MongoDB] MongoDB保存失败: {str(e)},尝试使用JSON文件") + + # MongoDB失败,使用JSON文件备用 # 创建目录(如果不存在) os.makedirs(os.path.dirname(VERIFICATION_CACHE_FILE), exist_ok=True) @@ -239,7 +254,7 @@ class EmailVerification: with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file: json.dump(verification_data, file, indent=2, ensure_ascii=False) - print(f"[验证码系统] 为QQ {qq_number} 保存{code_type}验证码: {verification_code}, 过期时间: {expire_at}") + print(f"[验证码系统-JSON] 为QQ {qq_number} 保存{code_type}验证码: {verification_code}, 过期时间: {expire_at}") return True except Exception as e: print(f"保存验证码失败: {str(e)}") @@ -248,7 +263,7 @@ class EmailVerification: @staticmethod def verify_code(qq_number, input_code, code_type="register"): """ - 验证用户输入的验证码 + 验证用户输入的验证码(优先使用MongoDB) 参数: qq_number (str): QQ号 @@ -261,9 +276,21 @@ class EmailVerification: """ import time + # 优先尝试使用MongoDB + try: + from SMYMongoDBAPI import SMYMongoDBAPI + mongo_api = SMYMongoDBAPI("test") + if mongo_api.is_connected(): + success, message = mongo_api.verify_verification_code(qq_number, input_code, code_type) + print(f"[验证码系统-MongoDB] QQ {qq_number} 验证结果: {success}, 消息: {message}") + return success, message + except Exception as e: + print(f"[验证码系统-MongoDB] MongoDB验证失败: {str(e)},尝试使用JSON文件") + + # MongoDB失败,使用JSON文件备用 # 检查缓存文件是否存在 if not os.path.exists(VERIFICATION_CACHE_FILE): - print(f"[验证码系统] QQ {qq_number} 验证失败: 缓存文件不存在") + print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 缓存文件不存在") return False, "验证码不存在或已过期" # 读取验证码数据 @@ -271,12 +298,12 @@ class EmailVerification: with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file: verification_data = json.load(file) except Exception as e: - print(f"[验证码系统] 读取验证码文件失败: {str(e)}") + print(f"[验证码系统-JSON] 读取验证码文件失败: {str(e)}") return False, "验证码数据损坏" # 检查该QQ号是否有验证码 if qq_number not in verification_data: - print(f"[验证码系统] QQ {qq_number} 验证失败: 没有找到验证码记录") + print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 没有找到验证码记录") return False, "验证码不存在,请重新获取" # 获取存储的验证码信息 @@ -287,16 +314,16 @@ class EmailVerification: is_used = code_info.get("used", False) created_at = code_info.get("created_at", 0) - print(f"[验证码系统] QQ {qq_number} 验证码详情: 存储码={stored_code}, 输入码={input_code}, 类型={stored_code_type}, 已使用={is_used}, 创建时间={created_at}") + print(f"[验证码系统-JSON] QQ {qq_number} 验证码详情: 存储码={stored_code}, 输入码={input_code}, 类型={stored_code_type}, 已使用={is_used}, 创建时间={created_at}") # 检查验证码类型是否匹配 if stored_code_type != code_type: - print(f"[验证码系统] QQ {qq_number} 验证失败: 验证码类型不匹配,存储类型={stored_code_type}, 请求类型={code_type}") + print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 验证码类型不匹配,存储类型={stored_code_type}, 请求类型={code_type}") return False, f"验证码类型不匹配,请重新获取{code_type}验证码" # 检查验证码是否已被使用 if is_used: - print(f"[验证码系统] QQ {qq_number} 验证失败: 验证码已被使用") + print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 验证码已被使用") return False, "验证码已被使用,请重新获取" # 检查验证码是否过期 @@ -306,7 +333,7 @@ class EmailVerification: del verification_data[qq_number] with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file: json.dump(verification_data, file, indent=2, ensure_ascii=False) - print(f"[验证码系统] QQ {qq_number} 验证失败: 验证码已过期") + print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 验证码已过期") return False, "验证码已过期,请重新获取" # 验证码比较(不区分大小写) @@ -318,22 +345,34 @@ class EmailVerification: try: with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file: json.dump(verification_data, file, indent=2, ensure_ascii=False) - print(f"[验证码系统] QQ {qq_number} 验证成功: 验证码已标记为已使用") + print(f"[验证码系统-JSON] QQ {qq_number} 验证成功: 验证码已标记为已使用") return True, "验证码正确" except Exception as e: - print(f"[验证码系统] 标记验证码已使用时失败: {str(e)}") + print(f"[验证码系统-JSON] 标记验证码已使用时失败: {str(e)}") return True, "验证码正确" # 即使标记失败,验证还是成功的 else: - print(f"[验证码系统] QQ {qq_number} 验证失败: 验证码不匹配") + print(f"[验证码系统-JSON] QQ {qq_number} 验证失败: 验证码不匹配") return False, "验证码错误" @staticmethod def clean_expired_codes(): """ - 清理过期的验证码和已使用的验证码 + 清理过期的验证码和已使用的验证码(优先使用MongoDB) """ import time + # 优先尝试使用MongoDB + try: + from SMYMongoDBAPI import SMYMongoDBAPI + mongo_api = SMYMongoDBAPI("test") + if mongo_api.is_connected(): + expired_count = mongo_api.clean_expired_verification_codes() + print(f"[验证码系统-MongoDB] 清理完成,删除了 {expired_count} 个过期验证码") + return expired_count + except Exception as e: + print(f"[验证码系统-MongoDB] MongoDB清理失败: {str(e)},尝试使用JSON文件") + + # MongoDB失败,使用JSON文件备用 if not os.path.exists(VERIFICATION_CACHE_FILE): return @@ -355,12 +394,12 @@ class EmailVerification: # 过期的验证码 if current_time > expire_at: should_remove = True - print(f"[验证码清理] 移除过期验证码: QQ {qq_number}") + print(f"[验证码清理-JSON] 移除过期验证码: QQ {qq_number}") # 已使用超过1小时的验证码 elif is_used and used_at > 0 and (current_time - used_at) > 3600: should_remove = True - print(f"[验证码清理] 移除已使用的验证码: QQ {qq_number}") + print(f"[验证码清理-JSON] 移除已使用的验证码: QQ {qq_number}") if should_remove: removed_keys.append(qq_number) @@ -373,7 +412,7 @@ class EmailVerification: if removed_keys: with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file: json.dump(verification_data, file, indent=2, ensure_ascii=False) - print(f"[验证码清理] 共清理了 {len(removed_keys)} 个验证码") + print(f"[验证码清理-JSON] 共清理了 {len(removed_keys)} 个验证码") except Exception as e: print(f"清理验证码失败: {str(e)}") @@ -381,7 +420,7 @@ class EmailVerification: @staticmethod def get_verification_status(qq_number): """ - 获取验证码状态(用于调试) + 获取验证码状态(优先使用MongoDB) 参数: qq_number (str): QQ号 @@ -391,6 +430,33 @@ class EmailVerification: """ import time + # 优先尝试使用MongoDB + try: + from SMYMongoDBAPI import SMYMongoDBAPI + mongo_api = SMYMongoDBAPI("test") + if mongo_api.is_connected(): + verification_codes = mongo_api.get_verification_codes() + if verification_codes and qq_number in verification_codes: + code_info = verification_codes[qq_number] + current_time = time.time() + + return { + "status": "found", + "code": code_info.get("code", ""), + "code_type": code_info.get("code_type", "unknown"), + "used": code_info.get("used", False), + "expired": current_time > code_info.get("expire_at", 0), + "created_at": code_info.get("created_at", 0), + "expire_at": code_info.get("expire_at", 0), + "used_at": code_info.get("used_at", 0), + "source": "mongodb" + } + else: + return {"status": "no_code", "source": "mongodb"} + except Exception as e: + print(f"[验证码系统-MongoDB] MongoDB状态查询失败: {str(e)},尝试使用JSON文件") + + # MongoDB失败,使用JSON文件备用 if not os.path.exists(VERIFICATION_CACHE_FILE): return {"status": "no_cache_file"} @@ -412,7 +478,8 @@ class EmailVerification: "expired": current_time > code_info.get("expire_at", 0), "created_at": code_info.get("created_at", 0), "expire_at": code_info.get("expire_at", 0), - "used_at": code_info.get("used_at", 0) + "used_at": code_info.get("used_at", 0), + "source": "json" } except Exception as e: @@ -440,4 +507,4 @@ if __name__ == "__main__": # 测试验证 test_input = input("请输入收到的验证码: ") verify_success, verify_message = EmailVerification.verify_code(test_qq, test_input) - print(f"验证结果: {verify_success}, 消息: {verify_message}") \ No newline at end of file + print(f"验证结果: {verify_success}, 消息: {verify_message}") \ No newline at end of file diff --git a/Server/SMYMongoDBAPI.py b/Server/SMYMongoDBAPI.py index 016baab..79991f2 100644 --- a/Server/SMYMongoDBAPI.py +++ b/Server/SMYMongoDBAPI.py @@ -10,7 +10,7 @@ import pymongo import json from typing import Dict, Any, Optional, List import logging -from datetime import datetime +from datetime import datetime, timedelta from bson import ObjectId class SMYMongoDBAPI: @@ -107,9 +107,81 @@ class SMYMongoDBAPI: # ========================= 游戏配置管理 ========================= + def _get_config_by_id(self, object_id: str, config_name: str) -> Optional[Dict[str, Any]]: + """ + 通用方法:根据ObjectId获取配置 + + Args: + object_id: MongoDB文档ID + config_name: 配置名称(用于日志) + + Returns: + Dict: 配置数据,如果未找到返回None + """ + try: + collection = self.get_collection("gameconfig") + + # 使用ObjectId查找文档 + oid = ObjectId(object_id) + result = collection.find_one({"_id": oid}) + + if result: + # 移除MongoDB的_id字段和updated_at字段 + if "_id" in result: + del result["_id"] + if "updated_at" in result: + del result["updated_at"] + + self.logger.info(f"成功获取{config_name}") + return result + else: + self.logger.warning(f"未找到{config_name}") + return None + + except Exception as e: + self.logger.error(f"获取{config_name}失败: {e}") + return None + + def _update_config_by_id(self, object_id: str, config_data: Dict[str, Any], config_name: str) -> bool: + """ + 通用方法:根据ObjectId更新配置 + + Args: + object_id: MongoDB文档ID + config_data: 配置数据 + config_name: 配置名称(用于日志) + + Returns: + bool: 是否成功 + """ + try: + collection = self.get_collection("gameconfig") + + # 使用ObjectId更新文档 + oid = ObjectId(object_id) + + # 添加更新时间 + update_data = { + "updated_at": datetime.now(), + **config_data + } + + result = collection.replace_one({"_id": oid}, update_data) + + if result.acknowledged and result.matched_count > 0: + self.logger.info(f"成功更新{config_name}") + return True + else: + self.logger.error(f"更新{config_name}失败") + return False + + except Exception as e: + self.logger.error(f"更新{config_name}异常: {e}") + return False + def get_game_config(self, config_type: str) -> Optional[Dict[str, Any]]: """ - 获取游戏配置 + 获取游戏配置(通过config_type) Args: config_type: 配置类型,如 "daily_checkin" @@ -177,623 +249,438 @@ class SMYMongoDBAPI: return False + # ===================== 配置系统常量 ===================== + CONFIG_IDS = { + "daily_checkin": "687cce278e77ba00a7414ba2", + "lucky_draw": "687cd52e8e77ba00a7414ba3", + "new_player": "687cdbd78e77ba00a7414ba4", + "wisdom_tree": "687cdfbe8e77ba00a7414ba5", + "online_gift": "687ce7678e77ba00a7414ba6", + "scare_crow": "687cea258e77ba00a7414ba8", + "item": "687cf17c8e77ba00a7414baa", + "pet": "687cf59b8e77ba00a7414bab", + "stamina": "687cefba8e77ba00a7414ba9", + "crop_data": "687cfb3d8e77ba00a7414bac", + "initial_player_data": "687e2f3f8e77ba00a7414bb0", + "verification_codes": "687e35078e77ba00a7414bb1" + } + #=====================每日签到系统====================== def get_daily_checkin_config(self) -> Optional[Dict[str, Any]]: - """ - 获取每日签到配置 - - Returns: - Dict: 每日签到配置数据 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID查找 - object_id = ObjectId("687cce278e77ba00a7414ba2") - result = collection.find_one({"_id": object_id}) - - if result: - # 移除MongoDB的_id字段 - if "_id" in result: - del result["_id"] - - self.logger.info("成功获取每日签到配置") - return result - else: - self.logger.warning("未找到每日签到配置") - return None - - except Exception as e: - self.logger.error(f"获取每日签到配置失败: {e}") - return None + """获取每日签到配置""" + return self._get_config_by_id(self.CONFIG_IDS["daily_checkin"], "每日签到配置") def update_daily_checkin_config(self, config_data: Dict[str, Any]) -> bool: - """ - 更新每日签到配置 - - Args: - config_data: 配置数据 - - Returns: - bool: 是否成功 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID更新 - object_id = ObjectId("687cce278e77ba00a7414ba2") - - # 添加更新时间 - update_data = { - "updated_at": datetime.now(), - **config_data - } - - result = collection.replace_one({"_id": object_id}, update_data) - - if result.acknowledged and result.matched_count > 0: - self.logger.info("成功更新每日签到配置") - return True - else: - self.logger.error("更新每日签到配置失败") - return False - - except Exception as e: - self.logger.error(f"更新每日签到配置异常: {e}") - return False + """更新每日签到配置""" + return self._update_config_by_id(self.CONFIG_IDS["daily_checkin"], config_data, "每日签到配置") #=====================每日签到系统====================== #=====================幸运抽奖系统====================== def get_lucky_draw_config(self) -> Optional[Dict[str, Any]]: - """ - 获取幸运抽奖配置 - - Returns: - Dict: 幸运抽奖配置数据 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID查找 - object_id = ObjectId("687cd52e8e77ba00a7414ba3") - result = collection.find_one({"_id": object_id}) - - if result: - # 移除MongoDB的_id字段和updated_at字段 - if "_id" in result: - del result["_id"] - if "updated_at" in result: - del result["updated_at"] - - self.logger.info("成功获取幸运抽奖配置") - return result - else: - self.logger.warning("未找到幸运抽奖配置") - return None - - except Exception as e: - self.logger.error(f"获取幸运抽奖配置失败: {e}") - return None + """获取幸运抽奖配置""" + return self._get_config_by_id(self.CONFIG_IDS["lucky_draw"], "幸运抽奖配置") def update_lucky_draw_config(self, config_data: Dict[str, Any]) -> bool: - """ - 更新幸运抽奖配置 - - Args: - config_data: 配置数据 - - Returns: - bool: 是否成功 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID更新 - object_id = ObjectId("687cd52e8e77ba00a7414ba3") - - # 添加更新时间 - update_data = { - "updated_at": datetime.now(), - **config_data - } - - result = collection.replace_one({"_id": object_id}, update_data) - - if result.acknowledged and result.matched_count > 0: - self.logger.info("成功更新幸运抽奖配置") - return True - else: - self.logger.error("更新幸运抽奖配置失败") - return False - - except Exception as e: - self.logger.error(f"更新幸运抽奖配置异常: {e}") - return False + """更新幸运抽奖配置""" + return self._update_config_by_id(self.CONFIG_IDS["lucky_draw"], config_data, "幸运抽奖配置") #=====================幸运抽奖系统====================== #=====================新手大礼包系统====================== def get_new_player_config(self) -> Optional[Dict[str, Any]]: - """ - 获取新手大礼包配置 - - Returns: - Dict: 新手大礼包配置数据 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID查找 - object_id = ObjectId("687cdbd78e77ba00a7414ba4") - result = collection.find_one({"_id": object_id}) - - if result: - # 移除MongoDB的_id字段和updated_at字段 - if "_id" in result: - del result["_id"] - if "updated_at" in result: - del result["updated_at"] - - self.logger.info("成功获取新手大礼包配置") - return result - else: - self.logger.warning("未找到新手大礼包配置") - return None - - except Exception as e: - self.logger.error(f"获取新手大礼包配置失败: {e}") - return None + """获取新手大礼包配置""" + return self._get_config_by_id(self.CONFIG_IDS["new_player"], "新手大礼包配置") def update_new_player_config(self, config_data: Dict[str, Any]) -> bool: - """ - 更新新手大礼包配置 - - Args: - config_data: 配置数据 - - Returns: - bool: 是否成功 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID更新 - object_id = ObjectId("687cdbd78e77ba00a7414ba4") - - # 添加更新时间 - update_data = { - "updated_at": datetime.now(), - **config_data - } - - result = collection.replace_one({"_id": object_id}, update_data) - - if result.acknowledged and result.matched_count > 0: - self.logger.info("成功更新新手大礼包配置") - return True - else: - self.logger.error("更新新手大礼包配置失败") - return False - - except Exception as e: - self.logger.error(f"更新新手大礼包配置异常: {e}") - return False + """更新新手大礼包配置""" + return self._update_config_by_id(self.CONFIG_IDS["new_player"], config_data, "新手大礼包配置") #=====================新手大礼包系统====================== #=====================智慧树系统====================== def get_wisdom_tree_config(self) -> Optional[Dict[str, Any]]: - """ - 获取智慧树配置 - - Returns: - Dict: 智慧树配置数据 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID查找 - object_id = ObjectId("687cdfbe8e77ba00a7414ba5") - result = collection.find_one({"_id": object_id}) - - if result: - # 移除MongoDB的_id字段和updated_at字段 - if "_id" in result: - del result["_id"] - if "updated_at" in result: - del result["updated_at"] - - self.logger.info("成功获取智慧树配置") - return result - else: - self.logger.warning("未找到智慧树配置") - return None - - except Exception as e: - self.logger.error(f"获取智慧树配置失败: {e}") - return None + """获取智慧树配置""" + return self._get_config_by_id(self.CONFIG_IDS["wisdom_tree"], "智慧树配置") def update_wisdom_tree_config(self, config_data: Dict[str, Any]) -> bool: - """ - 更新智慧树配置 - - Args: - config_data: 配置数据 - - Returns: - bool: 是否成功 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID更新 - object_id = ObjectId("687cdfbe8e77ba00a7414ba5") - - # 添加更新时间 - update_data = { - "updated_at": datetime.now(), - **config_data - } - - result = collection.replace_one({"_id": object_id}, update_data) - - if result.acknowledged and result.matched_count > 0: - self.logger.info("成功更新智慧树配置") - return True - else: - self.logger.error("更新智慧树配置失败") - return False - - except Exception as e: - self.logger.error(f"更新智慧树配置异常: {e}") - return False + """更新智慧树配置""" + return self._update_config_by_id(self.CONFIG_IDS["wisdom_tree"], config_data, "智慧树配置") #=====================智慧树系统====================== #=====================稻草人系统====================== def get_scare_crow_config(self) -> Optional[Dict[str, Any]]: - """ - 获取稻草人配置 - - Returns: - Dict: 稻草人配置数据 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID查找 - object_id = ObjectId("687cea258e77ba00a7414ba8") - result = collection.find_one({"_id": object_id}) - - if result: - # 移除MongoDB的_id字段和updated_at字段 - if "_id" in result: - del result["_id"] - if "updated_at" in result: - del result["updated_at"] - - self.logger.info("成功获取稻草人配置") - return result - else: - self.logger.warning("未找到稻草人配置") - return None - - except Exception as e: - self.logger.error(f"获取稻草人配置失败: {e}") - return None + """获取稻草人配置""" + return self._get_config_by_id(self.CONFIG_IDS["scare_crow"], "稻草人配置") def update_scare_crow_config(self, config_data: Dict[str, Any]) -> bool: - """ - 更新稻草人配置 - - Args: - config_data: 配置数据 - - Returns: - bool: 是否成功 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID更新 - object_id = ObjectId("687cea258e77ba00a7414ba8") - - # 添加更新时间 - update_data = { - "updated_at": datetime.now(), - **config_data - } - - result = collection.replace_one({"_id": object_id}, update_data) - - if result.acknowledged and result.matched_count > 0: - self.logger.info("成功更新稻草人配置") - return True - else: - self.logger.error("更新稻草人配置失败") - return False - - except Exception as e: - self.logger.error(f"更新稻草人配置异常: {e}") - return False + """更新稻草人配置""" + return self._update_config_by_id(self.CONFIG_IDS["scare_crow"], config_data, "稻草人配置") #=====================稻草人系统====================== #=====================在线礼包系统====================== def get_online_gift_config(self) -> Optional[Dict[str, Any]]: - """ - 获取在线礼包配置 - - Returns: - Dict: 在线礼包配置数据 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID查找 - object_id = ObjectId("687ce7678e77ba00a7414ba6") - result = collection.find_one({"_id": object_id}) - - if result: - # 移除MongoDB的_id字段和updated_at字段 - if "_id" in result: - del result["_id"] - if "updated_at" in result: - del result["updated_at"] - - self.logger.info("成功获取在线礼包配置") - return result - else: - self.logger.warning("未找到在线礼包配置") - return None - - except Exception as e: - self.logger.error(f"获取在线礼包配置失败: {e}") - return None + """获取在线礼包配置""" + return self._get_config_by_id(self.CONFIG_IDS["online_gift"], "在线礼包配置") def update_online_gift_config(self, config_data: Dict[str, Any]) -> bool: - """ - 更新在线礼包配置 - - Args: - config_data: 配置数据 - - Returns: - bool: 是否成功 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID更新 - object_id = ObjectId("687ce7678e77ba00a7414ba6") - - # 添加更新时间 - update_data = { - "updated_at": datetime.now(), - **config_data - } - - result = collection.replace_one({"_id": object_id}, update_data) - - if result.acknowledged and result.matched_count > 0: - self.logger.info("成功更新在线礼包配置") - return True - else: - self.logger.error("更新在线礼包配置失败") - return False - - except Exception as e: - self.logger.error(f"更新在线礼包配置异常: {e}") - return False + """更新在线礼包配置""" + return self._update_config_by_id(self.CONFIG_IDS["online_gift"], config_data, "在线礼包配置") #=====================在线礼包系统====================== #=====================道具配置系统====================== def get_item_config(self) -> Optional[Dict[str, Any]]: - """ - 获取道具配置 - - Returns: - Dict: 道具配置数据 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID查找 - object_id = ObjectId("687cf17c8e77ba00a7414baa") - result = collection.find_one({"_id": object_id}) - - if result: - # 移除MongoDB的_id字段和updated_at字段 - if "_id" in result: - del result["_id"] - if "updated_at" in result: - del result["updated_at"] - - self.logger.info("成功获取道具配置") - return result - else: - self.logger.warning("未找到道具配置") - return None - - except Exception as e: - self.logger.error(f"获取道具配置失败: {e}") - return None + """获取道具配置""" + return self._get_config_by_id(self.CONFIG_IDS["item"], "道具配置") def update_item_config(self, config_data: Dict[str, Any]) -> bool: - """ - 更新道具配置 - - Args: - config_data: 配置数据 - - Returns: - bool: 是否成功 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID更新 - object_id = ObjectId("687cf17c8e77ba00a7414baa") - - # 添加更新时间 - update_data = { - "updated_at": datetime.now(), - **config_data - } - - result = collection.replace_one({"_id": object_id}, update_data) - - if result.acknowledged and result.matched_count > 0: - self.logger.info("成功更新道具配置") - return True - else: - self.logger.error("更新道具配置失败") - return False - - except Exception as e: - self.logger.error(f"更新道具配置异常: {e}") - return False + """更新道具配置""" + return self._update_config_by_id(self.CONFIG_IDS["item"], config_data, "道具配置") #=====================道具配置系统====================== #=====================宠物配置系统====================== def get_pet_config(self) -> Optional[Dict[str, Any]]: - """ - 获取宠物配置 - - Returns: - Dict: 宠物配置数据 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID查找 - object_id = ObjectId("687cf59b8e77ba00a7414bab") - result = collection.find_one({"_id": object_id}) - - if result: - # 移除MongoDB的_id字段和updated_at字段 - if "_id" in result: - del result["_id"] - if "updated_at" in result: - del result["updated_at"] - - self.logger.info("成功获取宠物配置") - return result - else: - self.logger.warning("未找到宠物配置") - return None - - except Exception as e: - self.logger.error(f"获取宠物配置失败: {e}") - return None + """获取宠物配置""" + return self._get_config_by_id(self.CONFIG_IDS["pet"], "宠物配置") def update_pet_config(self, config_data: Dict[str, Any]) -> bool: - """ - 更新宠物配置 - - Args: - config_data: 配置数据 - - Returns: - bool: 是否成功 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID更新 - object_id = ObjectId("687cf59b8e77ba00a7414bab") - - # 添加更新时间 - update_data = { - "updated_at": datetime.now(), - **config_data - } - - result = collection.replace_one({"_id": object_id}, update_data) - - if result.acknowledged and result.matched_count > 0: - self.logger.info("成功更新宠物配置") - return True - else: - self.logger.error("更新宠物配置失败") - return False - - except Exception as e: - self.logger.error(f"更新宠物配置异常: {e}") - return False + """更新宠物配置""" + return self._update_config_by_id(self.CONFIG_IDS["pet"], config_data, "宠物配置") #=====================宠物配置系统====================== #=====================体力系统====================== def get_stamina_config(self) -> Optional[Dict[str, Any]]: - """ - 获取体力系统配置 - - Returns: - Dict: 体力系统配置数据 - """ - try: - collection = self.get_collection("gameconfig") - - # 使用已知的文档ID查找 - object_id = ObjectId("687cefba8e77ba00a7414ba9") - result = collection.find_one({"_id": object_id}) - - if result: - # 移除MongoDB的_id字段和updated_at字段 - if "_id" in result: - del result["_id"] - if "updated_at" in result: - del result["updated_at"] - - self.logger.info("成功获取体力系统配置") - return result - else: - self.logger.warning("未找到体力系统配置") - return None - - except Exception as e: - self.logger.error(f"获取体力系统配置失败: {e}") - return None + """获取体力系统配置""" + return self._get_config_by_id(self.CONFIG_IDS["stamina"], "体力系统配置") def update_stamina_config(self, config_data: Dict[str, Any]) -> bool: - """ - 更新体力系统配置 - - Args: - config_data: 配置数据 - - Returns: - bool: 是否成功 - """ + """更新体力系统配置""" + return self._update_config_by_id(self.CONFIG_IDS["stamina"], config_data, "体力系统配置") + #=====================体力系统====================== + + + #=====================作物数据系统====================== + def get_crop_data_config(self) -> Optional[Dict[str, Any]]: + """获取作物数据配置""" + return self._get_config_by_id(self.CONFIG_IDS["crop_data"], "作物数据配置") + + def update_crop_data_config(self, config_data: Dict[str, Any]) -> bool: + """更新作物数据配置""" + return self._update_config_by_id(self.CONFIG_IDS["crop_data"], config_data, "作物数据配置") + #=====================作物数据系统====================== + + + #=====================初始玩家数据模板系统====================== + def get_initial_player_data_template(self) -> Optional[Dict[str, Any]]: + """获取初始玩家数据模板""" + return self._get_config_by_id(self.CONFIG_IDS["initial_player_data"], "初始玩家数据模板") + + def update_initial_player_data_template(self, template_data: Dict[str, Any]) -> bool: + """更新初始玩家数据模板""" + return self._update_config_by_id(self.CONFIG_IDS["initial_player_data"], template_data, "初始玩家数据模板") + #=====================初始玩家数据模板系统====================== + + + #=====================验证码系统====================== + def get_verification_codes(self) -> Optional[Dict[str, Any]]: + """获取验证码数据""" + return self._get_config_by_id(self.CONFIG_IDS["verification_codes"], "验证码数据") + + def update_verification_codes(self, codes_data: Dict[str, Any]) -> bool: + """更新验证码数据""" + return self._update_config_by_id(self.CONFIG_IDS["verification_codes"], codes_data, "验证码数据") + + #=====================聊天消息系统====================== + def save_chat_message(self, username: str, player_name: str, content: str, timestamp: float = None) -> bool: + """保存聊天消息到MongoDB""" try: - collection = self.get_collection("gameconfig") + if timestamp is None: + timestamp = datetime.now().timestamp() - # 使用已知的文档ID更新 - object_id = ObjectId("687cefba8e77ba00a7414ba9") + # 获取日期字符串作为文档标识 + date_obj = datetime.fromtimestamp(timestamp) + date_str = date_obj.strftime("%Y-%m-%d") - # 添加更新时间 - update_data = { - "updated_at": datetime.now(), - **config_data + # 创建消息记录 + message_record = { + "username": username, + "player_name": player_name, + "content": content, + "timestamp": timestamp, + "time_str": date_obj.strftime("%Y年%m月%d日 %H:%M:%S") } - result = collection.replace_one({"_id": object_id}, update_data) + collection = self.get_collection("chat") - if result.acknowledged and result.matched_count > 0: - self.logger.info("成功更新体力系统配置") - return True + # 查找当天的文档 + query = {"date": date_str} + existing_doc = collection.find_one(query) + + if existing_doc: + # 如果文档存在,添加消息到messages数组 + result = collection.update_one( + query, + { + "$push": {"messages": message_record}, + "$set": {"updated_at": datetime.now()} + } + ) + success = result.acknowledged and result.modified_count > 0 else: - self.logger.error("更新体力系统配置失败") - return False + # 如果文档不存在,创建新文档 + new_doc = { + "date": date_str, + "messages": [message_record], + "created_at": datetime.now(), + "updated_at": datetime.now() + } + result = collection.insert_one(new_doc) + success = result.acknowledged + + if success: + self.logger.info(f"成功保存聊天消息: {username}({player_name}): {content[:20]}...") + else: + self.logger.error(f"保存聊天消息失败: {username}({player_name}): {content[:20]}...") + + return success + + except Exception as e: + self.logger.error(f"保存聊天消息异常: {e}") + return False + + def get_chat_history(self, days: int = 3, limit: int = 500) -> List[Dict[str, Any]]: + """获取聊天历史消息""" + try: + collection = self.get_collection("chat") + + # 计算日期范围 + end_date = datetime.now() + start_date = end_date - timedelta(days=days-1) + + # 生成日期列表 + date_list = [] + current_date = start_date + while current_date <= end_date: + date_list.append(current_date.strftime("%Y-%m-%d")) + current_date += timedelta(days=1) + + # 查询这些日期的文档 + query = {"date": {"$in": date_list}} + docs = collection.find(query).sort("date", 1) + + # 收集所有消息 + all_messages = [] + for doc in docs: + messages = doc.get("messages", []) + all_messages.extend(messages) + + # 按时间戳排序 + all_messages.sort(key=lambda x: x.get("timestamp", 0)) + + # 限制数量 + if limit > 0 and len(all_messages) > limit: + all_messages = all_messages[-limit:] + + self.logger.info(f"获取聊天历史消息成功: {len(all_messages)} 条消息(最近{days}天)") + return all_messages + + except Exception as e: + self.logger.error(f"获取聊天历史消息失败: {e}") + return [] + + def get_latest_chat_message(self) -> Optional[Dict[str, Any]]: + """获取最新的一条聊天消息""" + try: + collection = self.get_collection("chat") + + # 获取最近的文档 + latest_doc = collection.find().sort("date", -1).limit(1) + + for doc in latest_doc: + messages = doc.get("messages", []) + if messages: + # 返回最后一条消息 + latest_message = messages[-1] + self.logger.info(f"获取最新聊天消息成功: {latest_message.get('username', 'N/A')}: {latest_message.get('content', '')[:20]}...") + return latest_message + + self.logger.info("没有找到聊天消息") + return None + + except Exception as e: + self.logger.error(f"获取最新聊天消息失败: {e}") + return None + + def clean_old_chat_messages(self, keep_days: int = 30) -> int: + """清理旧的聊天消息""" + try: + collection = self.get_collection("chat") + + # 计算保留的最早日期 + cutoff_date = datetime.now() - timedelta(days=keep_days) + cutoff_date_str = cutoff_date.strftime("%Y-%m-%d") + + # 删除早于cutoff_date的文档 + query = {"date": {"$lt": cutoff_date_str}} + result = collection.delete_many(query) + + deleted_count = result.deleted_count + self.logger.info(f"清理旧聊天消息完成: 删除了 {deleted_count} 个文档({keep_days}天前的消息)") + return deleted_count + + except Exception as e: + self.logger.error(f"清理旧聊天消息失败: {e}") + return 0 + #=====================聊天消息系统====================== + + def save_verification_code(self, qq_number: str, verification_code: str, expiry_time: int = 300, code_type: str = "register") -> bool: + """保存单个验证码到MongoDB""" + import time + + try: + # 获取当前验证码数据 + codes_data = self.get_verification_codes() or {} + + # 添加新的验证码 + expire_at = time.time() + expiry_time + current_time = time.time() + + codes_data[qq_number] = { + "code": verification_code, + "expire_at": expire_at, + "code_type": code_type, + "created_at": current_time, + "used": False + } + + # 更新到MongoDB + success = self.update_verification_codes(codes_data) + if success: + self.logger.info(f"为QQ {qq_number} 保存{code_type}验证码: {verification_code}, 过期时间: {expire_at}") + return success + + except Exception as e: + self.logger.error(f"保存验证码失败: {e}") + return False + + def verify_verification_code(self, qq_number: str, input_code: str, code_type: str = "register") -> tuple[bool, str]: + """验证验证码""" + import time + + try: + # 获取验证码数据 + codes_data = self.get_verification_codes() + if not codes_data: + self.logger.warning(f"QQ {qq_number} 验证失败: 验证码数据不存在") + return False, "验证码不存在或已过期" + + # 检查该QQ号是否有验证码 + if qq_number not in codes_data: + self.logger.warning(f"QQ {qq_number} 验证失败: 没有找到验证码记录") + return False, "验证码不存在,请重新获取" + + # 获取存储的验证码信息 + code_info = codes_data[qq_number] + stored_code = code_info.get("code", "") + expire_at = code_info.get("expire_at", 0) + stored_code_type = code_info.get("code_type", "register") + is_used = code_info.get("used", False) + created_at = code_info.get("created_at", 0) + + self.logger.info(f"QQ {qq_number} 验证码详情: 存储码={stored_code}, 输入码={input_code}, 类型={stored_code_type}, 已使用={is_used}, 创建时间={created_at}") + + # 检查验证码类型是否匹配 + if stored_code_type != code_type: + self.logger.warning(f"QQ {qq_number} 验证失败: 验证码类型不匹配,存储类型={stored_code_type}, 请求类型={code_type}") + return False, f"验证码类型不匹配,请重新获取{code_type}验证码" + + # 检查验证码是否已被使用 + if is_used: + self.logger.warning(f"QQ {qq_number} 验证失败: 验证码已被使用") + return False, "验证码已被使用,请重新获取" + + # 检查验证码是否过期 + current_time = time.time() + if current_time > expire_at: + # 移除过期的验证码 + del codes_data[qq_number] + self.update_verification_codes(codes_data) + self.logger.warning(f"QQ {qq_number} 验证失败: 验证码已过期") + return False, "验证码已过期,请重新获取" + + # 验证码比较(不区分大小写) + if input_code.upper() == stored_code.upper(): + # 验证成功,标记为已使用 + codes_data[qq_number]["used"] = True + codes_data[qq_number]["used_at"] = current_time + + success = self.update_verification_codes(codes_data) + if success: + self.logger.info(f"QQ {qq_number} 验证成功: 验证码已标记为已使用") + else: + self.logger.warning(f"标记验证码已使用时失败,但验证成功") + return True, "验证码正确" + else: + self.logger.warning(f"QQ {qq_number} 验证失败: 验证码不匹配") + return False, "验证码错误" except Exception as e: - self.logger.error(f"更新体力系统配置异常: {e}") - return False - #=====================体力系统====================== + self.logger.error(f"验证验证码异常: {e}") + return False, "验证码验证失败" + + def clean_expired_verification_codes(self) -> int: + """清理过期的验证码和已使用的验证码""" + import time + + try: + codes_data = self.get_verification_codes() + if not codes_data: + return 0 + + current_time = time.time() + removed_keys = [] + + # 找出过期的验证码和已使用的验证码(超过1小时) + for qq_number, code_info in codes_data.items(): + expire_at = code_info.get("expire_at", 0) + is_used = code_info.get("used", False) + used_at = code_info.get("used_at", 0) + + should_remove = False + + # 过期的验证码 + if current_time > expire_at: + should_remove = True + self.logger.info(f"移除过期验证码: QQ {qq_number}") + + # 已使用超过1小时的验证码 + elif is_used and used_at > 0 and (current_time - used_at) > 3600: + should_remove = True + self.logger.info(f"移除已使用的验证码: QQ {qq_number}") + + if should_remove: + removed_keys.append(qq_number) + + # 移除标记的验证码 + for key in removed_keys: + del codes_data[key] + + # 保存更新后的数据 + if removed_keys: + self.update_verification_codes(codes_data) + self.logger.info(f"共清理了 {len(removed_keys)} 个验证码") + + return len(removed_keys) + + except Exception as e: + self.logger.error(f"清理验证码失败: {e}") + return 0 + #=====================验证码系统====================== # ========================= 通用数据库操作 ========================= @@ -985,7 +872,7 @@ def test_api(): else: print("✗ 获取稻草人配置失败") - # 测试获取宠物配置 + # 测试获取体力系统配置 print("\n7. 测试获取宠物配置:") pet_config = api.get_pet_config() if pet_config: @@ -998,8 +885,20 @@ def test_api(): else: print("✗ 获取宠物配置失败") + # 测试获取初始玩家数据模板 + print("\n8. 测试获取初始玩家数据模板:") + template_config = api.get_initial_player_data_template() + if template_config: + print("✓ 成功获取初始玩家数据模板") + print(f"模板包含字段: {list(template_config.keys())[:5]}...") + if "基础属性" in template_config: + basic_attrs = template_config["基础属性"] + print(f"基础属性: 等级={basic_attrs.get('等级')}, 经验={basic_attrs.get('经验')}, 金币={basic_attrs.get('金币')}") + else: + print("✗ 获取初始玩家数据模板失败") + # 测试查找所有游戏配置 - print("\n8. 测试查找游戏配置集合:") + print("\n9. 测试查找游戏配置集合:") try: configs = api.find_documents("gameconfig") print(f"找到 {len(configs)} 个配置文档") diff --git a/Server/TCPGameServer.py b/Server/TCPGameServer.py index 47f50c8..db07e93 100644 --- a/Server/TCPGameServer.py +++ b/Server/TCPGameServer.py @@ -1,5 +1,5 @@ from TCPServer import TCPServer -from SMYMongoDBAPI import SMYMongoDBAPI + import time import json import os @@ -8,6 +8,9 @@ import threading import datetime import re import random +#导入服务器外置插件模块 +from SMYMongoDBAPI import SMYMongoDBAPI #导入MongoDB数据库模块 +from QQEmailSendAPI import EmailVerification#导入QQ邮箱发送模块 """ 萌芽农场TCP游戏服务器 - 代码结构说明 @@ -30,14 +33,6 @@ import random - 批量数据保存:定时批量写入,提升性能 - 智能缓存管理:LRU策略,自动清理过期数据 -🎮 游戏功能模块: -- 用户系统:注册、登录、邮箱验证 -- 农场系统:种植、收获、浇水、施肥 -- 升级系统:经验获取、等级提升 -- 社交系统:访问农场、点赞互动 -- 奖励系统:每日签到、幸运抽奖 -- 排行系统:玩家排行榜展示 - 📊 数据存储: - 玩家数据:JSON格式存储在game_saves目录 - 配置文件:作物数据、初始模板等 @@ -205,7 +200,7 @@ class TCPGameServer(TCPServer): def start_verification_code_cleanup_timer(self): """启动验证码清理定时器""" try: - from QQEmailSend import EmailVerification + EmailVerification.clean_expired_codes() self.log('INFO', "验证码清理完成", 'SERVER') except Exception as e: @@ -433,7 +428,7 @@ class TCPGameServer(TCPServer): #加载作物配置数据(优化版本) def _load_crop_data(self): - """加载作物配置数据(带缓存优化)""" + """加载作物配置数据(优先MongoDB,带缓存优化)""" current_time = time.time() # 检查缓存是否有效 @@ -442,10 +437,26 @@ class TCPGameServer(TCPServer): return self.crop_data_cache # 缓存过期或不存在,重新加载 + # 优先尝试从MongoDB加载 + if self.use_mongodb and self.mongo_api: + try: + crop_data = self.mongo_api.get_crop_data_config() + if crop_data: + self.crop_data_cache = crop_data + self.crop_data_cache_time = current_time + self.log('INFO', "成功从MongoDB加载作物数据配置", 'SERVER') + return self.crop_data_cache + else: + self.log('WARNING', "MongoDB中未找到作物数据配置,尝试JSON文件", 'SERVER') + except Exception as e: + self.log('ERROR', f"从MongoDB加载作物数据失败: {str(e)},尝试JSON文件", 'SERVER') + + # MongoDB失败或不可用,尝试从JSON文件加载 try: with open("config/crop_data.json", 'r', encoding='utf-8') as file: self.crop_data_cache = json.load(file) self.crop_data_cache_time = current_time + self.log('INFO', "成功从JSON文件加载作物数据配置", 'SERVER') return self.crop_data_cache except Exception as e: self.log('ERROR', f"无法加载作物数据: {str(e)}", 'SERVER') @@ -1059,7 +1070,7 @@ class TCPGameServer(TCPServer): # 验证验证码 if verification_code: - from QQEmailSend import EmailVerification + success, verify_message = EmailVerification.verify_code(username, verification_code, "register") if not success: self.log('WARNING', f"QQ号 {username} 注册验证码验证失败: {verify_message}", 'SERVER') @@ -1092,54 +1103,47 @@ class TCPGameServer(TCPServer): #辅助函数-创建新用户 def _create_new_user(self, client_id, username, password, farm_name, player_name): - """创建新用户""" + """创建新用户(优先从MongoDB加载模板)""" try: - # 从模板加载初始玩家数据 - template_path = os.path.join("config", "initial_player_data_template.json") - if not os.path.exists(template_path): - return self._send_register_error(client_id, "服务器配置错误,无法注册新用户") - - with open(template_path, 'r', encoding='utf-8') as file: - player_data = json.load(file) - - # 更新玩家数据 + # 优先从MongoDB加载初始玩家数据模板 + player_data = None + if self.use_mongodb and self.mongo_api: + try: + player_data = self.mongo_api.get_initial_player_data_template() + if player_data: + self.log('INFO', "成功从MongoDB加载初始玩家数据模板", 'SERVER') + else: + self.log('WARNING', "MongoDB中未找到初始玩家数据模板,尝试从JSON文件加载", 'SERVER') + except Exception as e: + self.log('ERROR', f"从MongoDB加载初始玩家数据模板失败: {str(e)},尝试从JSON文件加载", 'SERVER') + + # MongoDB加载失败或不可用,从JSON文件加载 + if not player_data: + template_path = os.path.join("config", "initial_player_data_template.json") + if not os.path.exists(template_path): + return self._send_register_error(client_id, "服务器配置错误,无法注册新用户") + + with open(template_path, 'r', encoding='utf-8') as file: + player_data = json.load(file) + self.log('INFO', "成功从JSON文件加载初始玩家数据模板", 'SERVER') + + # 更新玩家基本信息 + current_time = datetime.datetime.now() + time_str = current_time.strftime("%Y年%m月%d日%H时%M分%S秒") + player_data.update({ "玩家账号": username, "玩家密码": password, "农场名称": farm_name or "我的农场", "玩家昵称": player_name or username, - "个人简介": "", # 新增个人简介字段,默认为空 - "经验值": player_data.get("经验值", 0), - "等级": player_data.get("等级", 1), - "钱币": player_data.get("钱币", 1000) + "个人简介": "", + "最后登录时间": time_str, + "注册时间": time_str, + "总游玩时间": player_data.get("总游玩时间", "0时0分0秒") }) - # 确保农场地块存在 - if "农场土地" not in player_data: - player_data["农场土地"] = [] - for i in range(40): - player_data["农场土地"].append({ - "crop_type": "", - "grow_time": 0, - "is_dead": False, - "is_diged": i < 5, # 默认开垦前5块地 - "is_planted": False, - "max_grow_time": 5 if i >= 5 else 3 - }) - - if "种子仓库" not in player_data: - player_data["种子仓库"] = [] - - # 更新注册时间和登录时间 - current_time = datetime.datetime.now() - time_str = current_time.strftime("%Y年%m月%d日%H时%M分%S秒") - player_data["最后登录时间"] = time_str - - # 设置新玩家的注册时间(不同于模板中的默认时间) - player_data["注册时间"] = time_str - - if "总游玩时间" not in player_data: - player_data["总游玩时间"] = "0时0分0秒" + # 确保必要字段存在 + self._ensure_player_data_fields(player_data) # 保存新用户数据 file_path = os.path.join("game_saves", f"{username}.json") @@ -1148,7 +1152,6 @@ class TCPGameServer(TCPServer): self.log('INFO', f"用户 {username} 注册成功,注册时间: {time_str},享受3天新玩家10倍生长速度奖励", 'SERVER') - # 返回成功响应 return self.send_data(client_id, { "type": "register_response", "status": "success", @@ -1159,6 +1162,29 @@ class TCPGameServer(TCPServer): self.log('ERROR', f"注册用户 {username} 时出错: {str(e)}", 'SERVER') return self._send_register_error(client_id, f"注册过程中出现错误: {str(e)}") + def _ensure_player_data_fields(self, player_data): + """确保玩家数据包含所有必要字段""" + # 确保农场地块存在 + if "农场土地" not in player_data: + player_data["农场土地"] = [] + for i in range(40): + player_data["农场土地"].append({ + "crop_type": "", + "grow_time": 0, + "is_dead": False, + "is_diged": i < 20, # 默认开垦前20块地 + "is_planted": False, + "max_grow_time": 5 if i >= 20 else 3, + "已浇水": False, + "已施肥": False, + "土地等级": 0 + }) + + # 确保基本仓库存在 + for field in ["种子仓库", "作物仓库", "道具背包", "宠物背包", "巡逻宠物", "出战宠物"]: + if field not in player_data: + player_data[field] = [] + #辅助函数-客户端版本检查 def _check_client_version(self, client_version, action_name="操作"): """检查客户端版本是否与服务端匹配""" @@ -1176,7 +1202,6 @@ class TCPGameServer(TCPServer): #处理验证码请求 def _handle_verification_code_request(self, client_id, message): """处理验证码请求""" - from QQEmailSend import EmailVerification qq_number = message.get("qq_number", "") @@ -1215,7 +1240,6 @@ class TCPGameServer(TCPServer): #处理验证码验证 def _handle_verify_code(self, client_id, message): """处理验证码验证""" - from QQEmailSend import EmailVerification qq_number = message.get("qq_number", "") input_code = message.get("code", "") @@ -1250,7 +1274,6 @@ class TCPGameServer(TCPServer): #处理忘记密码验证码请求 def _handle_forget_password_verification_code_request(self, client_id, message): """处理忘记密码验证码请求""" - from QQEmailSend import EmailVerification qq_number = message.get("qq_number", "") @@ -1298,7 +1321,6 @@ class TCPGameServer(TCPServer): #处理重置密码请求 def _handle_reset_password_request(self, client_id, message): """处理重置密码请求""" - from QQEmailSend import EmailVerification username = message.get("username", "") new_password = message.get("new_password", "") @@ -5329,20 +5351,28 @@ class TCPGameServer(TCPServer): # 扣除点赞次数 player_data["点赞系统"]["今日剩余点赞次数"] = remaining_likes - 1 + # 初始化目标玩家的点赞系统(如果不存在) + if "点赞系统" not in target_player_data: + target_player_data["点赞系统"] = { + "今日剩余点赞次数": 10, + "点赞上次刷新时间": datetime.datetime.now().strftime("%Y-%m-%d"), + "总点赞数": 0 + } + # 更新目标玩家的点赞数量 - target_player_data["点赞数"] = target_player_data.get("点赞数", 0) + 1 + target_player_data["点赞系统"]["总点赞数"] = target_player_data["点赞系统"].get("总点赞数", 0) + 1 # 保存两个玩家的数据 self.save_player_data(username, player_data) self.save_player_data(target_username, target_player_data) - self.log('INFO', f"玩家 {username} 点赞了玩家 {target_username},目标玩家点赞数:{target_player_data['点赞数']},剩余点赞次数:{player_data['点赞系统']['今日剩余点赞次数']}", 'SERVER') + self.log('INFO', f"玩家 {username} 点赞了玩家 {target_username},目标玩家点赞数:{target_player_data['点赞系统']['总点赞数']},剩余点赞次数:{player_data['点赞系统']['今日剩余点赞次数']}", 'SERVER') return self.send_data(client_id, { "type": "like_player_response", "success": True, "message": f"成功点赞玩家 {target_username}!剩余点赞次数:{player_data['点赞系统']['今日剩余点赞次数']}", - "target_likes": target_player_data["点赞数"], + "target_likes": target_player_data["点赞系统"]["总点赞数"], "remaining_likes": player_data["点赞系统"]["今日剩余点赞次数"] }) #检查并更新每日点赞次数 @@ -5356,7 +5386,8 @@ class TCPGameServer(TCPServer): if "点赞系统" not in player_data: player_data["点赞系统"] = { "今日剩余点赞次数": 10, - "点赞上次刷新时间": current_date + "点赞上次刷新时间": current_date, + "总点赞数": 0 } return True # 发生了初始化 @@ -5367,6 +5398,8 @@ class TCPGameServer(TCPServer): like_system["今日剩余点赞次数"] = 10 if "点赞上次刷新时间" not in like_system: like_system["点赞上次刷新时间"] = current_date + if "总点赞数" not in like_system: + like_system["总点赞数"] = 0 # 检查是否需要每日重置 last_refresh_date = like_system.get("点赞上次刷新时间", "") @@ -5961,7 +5994,7 @@ class TCPGameServer(TCPServer): "last_login_timestamp": last_login_timestamp, "总游玩时间": total_time_str, "total_time_seconds": total_time_seconds, - "like_num": player_data.get("点赞数", 0), + "like_num": player_data.get("点赞系统", {}).get("总点赞数", 0), "is_online": is_online } @@ -6123,9 +6156,8 @@ class TCPGameServer(TCPServer): "出战宠物": self._convert_battle_pets_to_full_data(target_player_data), "稻草人配置": target_player_data.get("稻草人配置", {}), "智慧树配置": target_player_data.get("智慧树配置", {}), - "玩家小卖部": target_player_data.get("玩家小卖部", []), # 添加小卖部数据 - "小卖部格子数": target_player_data.get("小卖部格子数", 10), # 添加小卖部格子数 - "点赞数": target_player_data.get("点赞数", 0), # 添加点赞数 + "小卖部配置": target_player_data.get("小卖部配置", {"商品列表": [], "格子数": 10}), # 添加小卖部配置 + "点赞数": target_player_data.get("点赞系统", {}).get("总点赞数", 0), # 添加点赞数 "最后登录时间": target_player_data.get("最后登录时间", "未知"), "总游玩时间": target_player_data.get("总游玩时间", "0时0分0秒"), "total_likes": target_player_data.get("total_likes", 0) @@ -6540,20 +6572,31 @@ class TCPGameServer(TCPServer): if player_data: player_name = player_data.get("玩家昵称", "") + # 获取当前时间戳 + current_timestamp = time.time() + # 创建广播消息 broadcast_message = { "type": "global_broadcast_message", "username": username, "玩家昵称": player_name, "content": content, - "timestamp": time.time() + "timestamp": current_timestamp } # 广播给所有在线用户 self.broadcast(broadcast_message) - # 保存消息到日志文件 - self._save_broadcast_message_to_log(username, player_name, content) + # 保存消息到MongoDB + if self.mongo_api and self.mongo_api.is_connected(): + success = self.mongo_api.save_chat_message(username, player_name, content, current_timestamp) + if not success: + self.log('WARNING', f"保存聊天消息到MongoDB失败,尝试保存到本地文件", 'BROADCAST') + self._save_broadcast_message_to_log(username, player_name, content) + else: + # 如果MongoDB不可用,保存到本地文件作为备份 + self.log('WARNING', f"MongoDB不可用,保存聊天消息到本地文件", 'BROADCAST') + self._save_broadcast_message_to_log(username, player_name, content) # 发送成功响应给发送者 self.send_data(client_id, { @@ -6603,10 +6646,28 @@ class TCPGameServer(TCPServer): try: days = message.get("days", 3) # 默认加载3天 + limit = message.get("limit", 500) # 默认限制500条 if days > 30: # 限制最多30天 days = 30 - messages = self._load_broadcast_history(days) + # 优先从MongoDB获取历史消息 + messages = [] + if self.mongo_api and self.mongo_api.is_connected(): + try: + messages = self.mongo_api.get_chat_history(days, limit) + # 转换数据格式以兼容客户端 + for msg in messages: + msg["玩家昵称"] = msg.get("player_name", "") + msg["display_name"] = msg.get("player_name", "") if msg.get("player_name") else msg.get("username", "匿名") + self.log('INFO', f"从MongoDB获取了 {len(messages)} 条历史消息", 'SERVER') + except Exception as e: + self.log('ERROR', f"从MongoDB获取历史消息失败: {str(e)}", 'SERVER') + messages = [] + + # 如果MongoDB获取失败或没有数据,尝试从本地文件获取 + if not messages: + self.log('INFO', f"尝试从本地文件获取历史消息", 'SERVER') + messages = self._load_broadcast_history(days) # 发送历史消息响应 response = { @@ -6733,7 +6794,7 @@ class TCPGameServer(TCPServer): self.log('ERROR', f"解析日志消息失败: {line}, 错误: {str(e)}", 'SERVER') return None - #==========================全服大喇叭消息处理========================== +#==========================全服大喇叭消息处理========================== @@ -9223,13 +9284,15 @@ class TCPGameServer(TCPServer): return self._send_action_error(client_id, "add_product_to_store", "商品价格必须大于0") # 初始化小卖部数据 - if "玩家小卖部" not in player_data: - player_data["玩家小卖部"] = [] - if "小卖部格子数" not in player_data: - player_data["小卖部格子数"] = 10 + if "小卖部配置" not in player_data: + player_data["小卖部配置"] = { + "商品列表": [], + "格子数": 10 + } - player_store = player_data["玩家小卖部"] - max_slots = player_data["小卖部格子数"] + store_config = player_data["小卖部配置"] + player_store = store_config["商品列表"] + max_slots = store_config["格子数"] # 检查小卖部格子是否已满 if len(player_store) >= max_slots: @@ -9280,7 +9343,7 @@ class TCPGameServer(TCPServer): "success": True, "message": f"成功添加 {product_count} 个 {product_name} 到小卖部", "updated_data": { - "玩家小卖部": player_data["玩家小卖部"], + "小卖部配置": player_data["小卖部配置"], "作物仓库": player_data.get("作物仓库", []) } }) @@ -9304,7 +9367,8 @@ class TCPGameServer(TCPServer): return self._send_action_error(client_id, "remove_store_product", "无效的商品槽位") # 检查小卖部数据 - player_store = player_data.get("玩家小卖部", []) + store_config = player_data.get("小卖部配置", {"商品列表": [], "格子数": 10}) + player_store = store_config.get("商品列表", []) if slot_index >= len(player_store): return self._send_action_error(client_id, "remove_store_product", "商品槽位不存在") @@ -9356,7 +9420,7 @@ class TCPGameServer(TCPServer): "success": True, "message": f"成功下架 {product_count} 个 {product_name},已返回仓库", "updated_data": { - "玩家小卖部": player_data["玩家小卖部"], + "小卖部配置": player_data["小卖部配置"], "作物仓库": player_data.get("作物仓库", []) } }) @@ -9399,7 +9463,8 @@ class TCPGameServer(TCPServer): return self._send_action_error(client_id, "buy_store_product", f"卖家 {seller_username} 不存在") # 检查卖家小卖部 - seller_store = seller_data.get("玩家小卖部", []) + seller_store_config = seller_data.get("小卖部配置", {"商品列表": [], "格子数": 10}) + seller_store = seller_store_config.get("商品列表", []) if slot_index >= len(seller_store): return self._send_action_error(client_id, "buy_store_product", "商品不存在") @@ -9498,10 +9563,14 @@ class TCPGameServer(TCPServer): return self._send_action_error(client_id, "buy_store_booth", "无效的购买费用") # 初始化小卖部数据 - if "小卖部格子数" not in player_data: - player_data["小卖部格子数"] = 10 + if "小卖部配置" not in player_data: + player_data["小卖部配置"] = { + "商品列表": [], + "格子数": 10 + } - current_slots = player_data["小卖部格子数"] + store_config = player_data["小卖部配置"] + current_slots = store_config["格子数"] # 检查是否已达上限 if current_slots >= 40: @@ -9518,21 +9587,21 @@ class TCPGameServer(TCPServer): # 执行购买 player_data["钱币"] -= cost - player_data["小卖部格子数"] += 1 + store_config["格子数"] += 1 # 保存玩家数据 self.save_player_data(username, player_data) - self.log('INFO', f"玩家 {username} 购买小卖部格子,花费 {cost} 元,格子数:{current_slots} -> {player_data['小卖部格子数']}", 'SERVER') + self.log('INFO', f"玩家 {username} 购买小卖部格子,花费 {cost} 元,格子数:{current_slots} -> {store_config['格子数']}", 'SERVER') return self.send_data(client_id, { "type": "action_response", "action_type": "buy_store_booth", "success": True, - "message": f"成功购买格子,花费 {cost} 元,当前格子数:{player_data['小卖部格子数']}", + "message": f"成功购买格子,花费 {cost} 元,当前格子数:{store_config['格子数']}", "updated_data": { "钱币": player_data["钱币"], - "小卖部格子数": player_data["小卖部格子数"] + "小卖部配置": player_data["小卖部配置"] } }) #==========================小卖部管理处理========================== @@ -9803,13 +9872,27 @@ class ConsoleCommands: print(f"❌ 玩家 {qq_number} 不存在") return - # 加载初始化模板 - try: - with open("config/initial_player_data_template.json", 'r', encoding='utf-8') as f: - template_data = json.load(f) - except Exception as e: - print(f"❌ 无法加载初始化模板: {str(e)}") - return + # 加载初始化模板(优先从MongoDB) + template_data = None + if self.server.use_mongodb and self.server.mongo_api: + try: + template_data = self.server.mongo_api.get_initial_player_data_template() + if template_data: + print("✅ 成功从MongoDB加载初始玩家数据模板") + else: + print("⚠️ MongoDB中未找到初始玩家数据模板,尝试从JSON文件加载") + except Exception as e: + print(f"⚠️ 从MongoDB加载初始玩家数据模板失败: {str(e)},尝试从JSON文件加载") + + # MongoDB加载失败或不可用,从JSON文件加载 + if not template_data: + try: + with open("config/initial_player_data_template.json", 'r', encoding='utf-8') as f: + template_data = json.load(f) + print("✅ 成功从JSON文件加载初始玩家数据模板") + except Exception as e: + print(f"❌ 无法加载初始化模板: {str(e)}") + return # 重置土地状态 if "农场土地" in template_data: diff --git a/Server/__pycache__/QQEmailSend.cpython-313.pyc b/Server/__pycache__/QQEmailSend.cpython-313.pyc index ea44ed547d08f50e14af3e2241db1a08842dd442..5cf6b2a6cd47837a8bbef6428f1d6cbcc8fa6bc7 100644 GIT binary patch delta 7592 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 T?m0PL@fC*Tbhn1f*~I@36W}Xv delta 5047 zcmbVQ4Nz3q6~6DiefxIb{{QnQKMKkU2x3q{(4c@o_%S@5EN-nKupmJn@L;1v%|??{ zo2F@#n>03QleCkWkWN!=oXOZGlavZ(Z93EK1UqKOPSTDMGo8-h;Djblr#<&AZ+Ag5 zN&A>N_w2dno_pWB_k7>EJozT|`Hv~{oXNx^cm_>J_MhMRYqLMsV`UI}4hfV%--9HL zpC;Y#;7<&$?3F0~8?|X;9mx`}Z*o@pm59V#M#VY>M$nYg*{B3b?AHV+Nq12^{-inu z|1*y(u&BWkF$Gr83S2ouBT4JGs{L(toVD6z(5f>^EZ(-djMCxO)tMENUZg~w$cTDD zFYxPF->x+?E7UQftI*x zuH_AUZ)-uQZ`s&6OJdupF&aX`1;ysohk4p{TKq)O2W5rho#XeMJ#>ET z{6IK8BKAhw`yzdZW)T{rw$soYrgo6VQA6KC$W%*Tad_r8;NPt43H8Ec6RRyXsc53i zQ=?;kZ`6T)fwt79p$j%ot)97%p9T7)j<0cZlXeDrC*6unkex0h3lP~C$)Y9PfdD(5EMja%v0!{wP7T_QoiKv6! z0$;alx#!OG#ZiyWTSI<109Fsp$iUCfAeJ4}pTb+8JyxC5^m?Sdn2G{lS{ z!6{I2qw$W{c9Dz}+)8$OL>}}M(Eu`4G(u(>)wjz=zMoedf_KSS=F_DqRTKd>>Uj3q$Tk$hS+s{I+B# zw#83QgYvYI99#0NM80+*2UfO{VAZOnhhWvBCFF`!TRMK)oTd?VF-5BI!AhDD67pg@ zCfEs3`Ywxz?4PiD0XCOAI{)Vy3zn{N&7w8tC+{fk+Ta8{P5ahPs^yn)*t4tO03emX zo;GQS-tSU-#rW1OhExUiwAI=^9$*hzY{}1~{)&e!IenpTG>7+pm?J+nM-sFa2Gj8u zWh?Q=`Chzt<43L_S@0dmd1aHB7laNujfe+91dEsP+nbFI1uO9A=CQoP#IcGo=TW|28VZi?>mKY;ft-x1CF1); z5JZpaN%y{fsTc38v`}NXqcY2Ug!DcIVuYN2e5!Jr{a@tQ7XU^HaNkyy>h_L%$I~Ns z`6BktY2%(Nj*3|=t1rbvLhcGPGP)ueMIRc9t{QC-=Yr?ABldmM#{2M@s_szUEB&wd zBPC5?f4Di)8Hn@_MFPW-;fH_?j#5p86pm2Mq-mx*NwbIUB26cKKWXl#he;#TM@chG zA0v(BVZtK%5Iw6!#*7aQ`N?w+L=GH{3>*Wuhe=$>=N7tyjB2KRq;YgA6toAQ+nSTJ z^v!la#FUKZ$DSU;M@|{97(94>>s{srFEL`@J#Fm3<=aZ=)BX6sw#}iU@&0jtWOE?m z?wz*wUGc2D#UqnF{v8%ZTyYf4k5I^7NBzZCd;6LtWe(B$Qf1LQCU=CX3w6KS9I-b| z8=J2>*596Gq4Uf68fCy`TkV(gDahSOU3N9xzNJIX6)@OTeAViTq%?>A*d1vTKIjem zBJSPO){d*5b!51y_{))D*vfY;_33EBsX#Fd(7Wxko_8Yu`kzyO?2f5}=jDolxQmtiqj?->61zGb?2&r4o%q zkhijM&K8}wB5k&7PV~-Y-@6v5_HCiTD;BVvH&N?DNYx&h4e|Zrx$b* z@7tu%hz($xm-qxG2?mi}jCieiqAt!j46D9|5WI~57t*Y8Yv)z(;$Elc&PTTS!~$V zFm9+1r-luYwD!wR5r-SH3@zcY@IWMO&$QEzZ#DSSH@(B0Z~Aa^Lu6B9xG3WGUAA`4 za>$x7UrpiDjg`Bg8bW6*b$0Zwr?!}xaBW`$`Xt&&~l32s$!@uLLp^U`eD%VXA|IUoUC^p{h-er{6PZ z6n(hGWiqK)q*(q2h-FbNzV?{E!e%f*6DH)xkihx z$8#~c<^vRvw1P8{jfqN6aIK?9Xtl!2B^PI?@RBmm9efD?xi!N$^X&Mw-#&HY?4vi% zJcrkBEX0MoibGND$Y|GSa3pGy2KRM$b?*=C>+L@fSdxZOj`Tv!5@mY_gK*FflQXMS zUgWYG<$}_HG#mxCiA?5R1Z)L+`g5M54LCRt%L|~T561Fg7WOx!c3&th`x5a z6`BiM_!>Po$#S4i8u^-a%%s~?1DS}ZKqCcl|7w5RvJ2F*4X{@IYU7I8hUi5J)P${=e+M)Su2YW)b73K z{B;)kt#5so_q%`J@4bAv5f`T>;Q7PKhj(7G5X4{bgL~v*&kdcJAWjfw!YpbgNPLT% zMH|JW*ob~fvt*-`lrny4vuvZBlrw%=vtpx?R5E^1vx-zP`PHPFd22`w^VX7Dvz&}G zE68}Wl1wnGNL{D4Esv{05!g_6lCCn$jr_xNT1Z18BbJ* zxm-XcrEQHMdDz>T-kHhO$^)$+bI1iu{^ZV_c*3mjTmV$00xCHR!#Qp1n3~tznr686 zDI5hd7wS(om>qx5AB%+lqO)Yg>?fVD8ABX-H zPv4+pz(!&~0tVC!4D>m=Y=aJ$6N2Q8c3ZcdJeXKfzI4T^)vL=_th{^Ap0X}iKdo~g z?6U3OyQ62&i7#@#5tE;tDu{0{YJOl2|a4H_d`vqjBK^0Uib}P&ojQz%ZIqcHqYQr7wI_2j0e+kRYor5 z)U)2HmmixRend?wp;S@@POC{uYlS7yNpnfi%s^S)b`pk#R^g~w2V5`(B(&6R@9QBG zkem)2H#W{_G~T=i-gQ--4X$q2AnRU#=3AD;<5 z|48Sxp{K7s^>OI%nb4UJJIzcvom;llVUx|!eCYqOfdjPCYIQgrgH|h@#1C8<7iPfX z7l3n!n9&m1rg3F@FmL&|azQY2p;tPtObe!Gd8N<9-+&}UYnKdP;YR^)W-MQTkvc)- z5FPyZStMqWS-c`VW=>fLKYA9TP0A67ydiCPv@J5Tq!=OuIgnB$RvHy6o)s&@Sc{b3 z7GR5vP%_Kgu+CABh2KzSQBp9JL`y^#X({Az$~Q|a@a2?u;4EOCHs+wBOqS_(?S5qP5wpSz3tIZ`!2yQgGVSe<8jl=@N2!E})Yb6Va9#JmQ zfg1?(ChKz54dG_g-Uz>TP{)yPB2_ z^w}KFA7q}vp3>E{w4Q_$M7-5SE6i+NX;BF+uI*~|V5hRx4sVSN`M-YjBHN0!#ec); zT3lj;u7It4^1~0;7Sp2Qtsb0L!i;PsAY;8Lfy8A_B1mHPHd?yN<#5sp-1+((J7}f3 zW$Qgwvw0JV6P?5vOw0OQy$&a{3(`v5QE~I4RW`TPJxDs7z0e|f4brN*{ay9}JT7So z#F$iMGG>pTJ$O)7xSfh{|I?~2u9s;=zuoP&_1ft;TVJ1*%ju@&;VoKZcjM*`8!`Kk zc)HOESS#xw9Omq3(dm(0G%6|&(z_SKEQifB$)HM~OwBp9Z$#lU1X3%#4OepthPO@> zuJjkK990Bz?)Eke)gRe>H7jp;oo`hjYYlvFIMQ-8x0uP13ps|G{Mwwq%L!fjRb$bx z`;Gmt?Dwex#Z{vX0po@t#VP5rxM1!Q%DB{5={Hs%i~EN87>c_oCz8@95_7*y%pES6 z$Se2fmHRe)(E4`kcw+6vy+K{-N$oN1sjU;)W&Z3kpLAkrgMVqmxULan5{@MdODBxW z{KjRzwG->j{`Ka-dds-(-tS~Y(gx8@B@vfI>6iJ6ed(iRV>wh~JJo(amGr>4wj-Eq z_)dd~zDlh9$4xC%`vxIarC-%1QhL){`6ISp6^*1(N#*0(imSl?gB%e!U;P2v)IAkVp8^)P; zV>KKtvkxL}8-s28c1Mti+uAI0vpDLI5t)Ut(FYV@CqZ83fSUIRlHKMA5>k>!9YKol z5u~ul+i;>rJslb@0FGY;)G6FzZqtNgLB?&xw%u?f$&!giJ8rjdbjR^|1)Dq^OR5e; zDa>P0#X6w49tNDKt`s}f7Iho0%&5n#F>5W#ScekuCa_@eF~=jiFeg+>pr$cfXi;-% z;W*OaAJ^|jqNSH0G;pkRWHH3RY0Ke!kvDTZ8zbMG4~|A$yipHF9r{6Q(Z=GpVjrmx zS`O*q85Or z{1KhX)d7xZXq!_bpr2?_!oi=^k;$d8Xt;5j%_qS@t#fK>G(a2pHP#07vS%Hhwnxv0Mqj^v`f)f8 zxf7f_L;icA!_L9}K43z^_#sji0RC)kSTMm=fk3?ia;?g6Lt)QjY8d^;5IH+}X4cZ4w;q~^;z~y!@$GkdHWm>f4`u~9kGxa$7>`q(1z14%;%+v=LLQf5| z?O~CK@u0ZM=z*z0V8;oO3b0Oqpac+yG(sv8lWz4On2pXLfHtWlaxD>PL*i~h%h@V2 zu!okq2JB7}w=h}?E$pV%w(f50PKL3kWo|$~09%jn>?OVLYKVidCeBp$qO z-1j-W!@Px{hbe{Opyz;|NYn--GYLxvK##r5sM^X+5)gc$2Sa zv^bEteq39NxGD3`xg|qQGh%s4v*>De?yzZO>v(p#uluTL^{D=n^xvdk$Q*mvZ`yup zV=${|B5RpHYZ+DEFqTC<=$LrW<$usc4Y&gj4pN?d{s;F{7=Iv`m4CMNbn8gfc-G4A z6huyiKfC-}8If0dQ~%EfT`PROzWYa=W80}somA&GDrx(;){3Aj6RI(hUH%UMXIX_e z2obh6Et@h{_?mocMt6Lcd~wUA)Qd_gr)eT>V<2tgKV~GD=7;Z;^PsiTx4L|}zv>xj zr;=8VYgY--+94S2N7tt{lo6k1YU>w?KP|3_t2f9lEmy+FrRpUR@!8^<#D;9i=hYeT zaoM1*FIHX7l|c060tto`GyX;Dh78%|6N-+~& zrfy7^Tv=^s)Jw+nGVq(E48Oq2PmqgXmd~4_R+G53)ucj!2ksCc@K9T={jP2gtN<&m zwOaRhY<=OF1go{jLAnR~98SB_1#G#o2f=w`ga>dOJwP64outy|M`c* zr8gdjpxN@cPASN9#VCE_&U;b@VH%Xt3Bu$MrZr1vg(;YHj0}&@tAsF+q1gyzYV8b-^m~uV1f_a#;y*fd!dIon?GMhD-oc&5g}`!};GEGQhoX-XiYpC00p#L}VinHHm3CX>)c1?=SD#>2h@0LF6bdTS|+p zw5)5VjdX_vj)&OIURB*|ciQ(4kn8BX<_vrp#*wx`yLFCmxvaX+)dg~gb!9w($DnvO zB7pC&6Nd;Ywf3EDKGE^LCm(w5p<&B}VKHS`{LZ%j<;FgEEWb!(k^%G0^7#oicbCIq z^$ZNy$u67QPRqL;y^cXQd(z6FJ-v2p7&}7b16UAtknwJ|5gGH>lP>u&t=fx}@qqmP z5OFh&NHk37iu}5ww^Gk%znSgVt@O%*35iGd9oaXYTKk@5bme>70;#ougjz~j%dGHO z@@{0wm_>3KmJSXx0A*2y*eS7q+>%kbC;$jcoKmyYDKpDJ$|yI>;Y$HuN?=;~1qU)V zIU{3}p^T03YZ*WvSD`Y;gEA+)jW`t@{Iszs%sRvm?W@d*`0hY(!aNpfMEwJ)k}wvS zKwQ$y)28Hnk#`=^!OJ_%$s9A|R03PaGmnwfky>={a!)P~FhI&!tYjpQMFMhHMJz^= z+q}5;GPF`6<#IkQ9V`qzNEn6e3*c3!%A8_Ojj%8l6}NeyWO2FJEe1xx;8fM93c~LQ zn_^1yWWlh683lfshmoe&$LS4bZ!CNESe>5!9d^>V5XDyj)fp+{wL?#A*%CVX0kfOT zSzugRz7QrE1IVlhocVZ0-2%Fn(9eDfn^YcY?{&Bb?PQ*D%KL&5KHc^~z<_S|J{Q@Y zH+SdS;c`WEBr)WSL$8Otr-gDx+$x#vj>Xha%Jt)?ubn?X`N=C&kDON@RB+lWkk1Lr z_TWVO%b_8m;^Hrdj)l%2hMoBOlc>Mp+v+KRv}~9#`PAs>l=tYh_g;ryWAhzU7PrW-5I{b8mF7hU9*nD(>;_}*>`V~tLdYR6`=2j!(T^IS>;p#pR`D9Xb zJN81a9O$#J&FcY%rnJY_@8~;FWvn3`w!RXh+vaqaf~e8av&Puv>T|(qkzY|!u_kXe z8Md%;j#4><+WR&fOViK389MhqOp>Wb zM?$ZC{MU~jsa`S=GYcdDJ~MP?#DfPsn?rc6O`SRqyj1Mmf}Yr6>)PE*x;)PAQm)ZG z<@mS8H~b^!lSy zKRa{nlhc!<&pB`*QP*~FT^qxc)4Fi-8q3y3YwhL@brBAo zmhSOz>^*Y!s3hlid^&N?#!jmvn>fSP)0%LAW3UhO6YR%;hXiSeor_~ej$!RVQ-aE5 z*N$BbC-25{IWm=5JY-;W6SGQYsM4l7RI%JE!1iVHBz*$Nx^rNy0B4^?WEY)UGu%t1 zmwDrX860j6wIjgHV3oTds~7T`A3?+$yke@)y${2859x8nKOjcm_MT=3YH9) z&Jg0I+23R@I<wM3fRtDWdenGlY7fY9=u$OFL6Q#OsIdyHS{w zoa0s8Tts9Rdlf_SBMHH@q9N&#xU1Ru!_A;u$*zQ8m0y`LBUP%_P8xD148>m>ir>=s z%mG8Cw=t+?r32ODrpj?`6?{%;v;EraGkqi5sgml^-idXa{p&Ud)@>QrwqfFyBP~yF z4kj=C{%S%RaMP-_!Q?b5tKz-dQPI2gzSUGlHI-cbm9`q$Xw}*u81?z@bTy0DR}-IB zt80ytOY(%;Ov$Ab5&AP_;C3P6nFK>r}JXe<(o)|_ZgDwDMjg&}*adMC%Mw=q&S=gTA7Uo`81VoIeHGzd}k z7R-v&=viSlSTry&vWc%wNS^zjVdxT6%y_XO#{qgRN*&> zS{Jjv2=Kmze&8KmKX9uW=wjEPg6F8&*~QhN_3Ix|gOf39kQ>zpX}7NdS9)(^bm^d> zFe7l8{N&}(uTMpeHLtJ5dl#1%Tw!A3YE0SSdf6FTDEX^zjii zUzjEF;EySenfUSi$o>HbdB8g8=m&;`6`WlA1k5bXPMv)J`pNg8=;=3}gK_~lu;P<2 zZejof8mZ0ZWw`VFzskK=s7fuJ? z-7Fv~4X4^>1RS_!i_wESu8}R$gG@i4zRI}ObJwjYFyqN78=emNUcPqz#N@}Hz-VnW zZ*FA=(1RlV`D+ob4qE{Z===>gLg@9_6j7OC-w%AE)wP=zkqpp+E>PN?FeU)Y855Cf z=P!hgzQ8wztqVuL9>h0N96(wH(jy0H2N5aQTW}*vtGhrCGHCC{V5!G#@1{lju47lf z3340hW)Wr*yWF(0-?rP{4FY3Y%IH!Q;73U^Z2)-K zRNquvv$bh+tF^YKwz1Ay-?XWYmNR|pCPC2&3L&(@q@{f>Pzy=BJ^cd!L3cp}xf^3O zOz*KPh52M4>Sk;ef+iDwdplwT67g(?`m@f)ccH0JRWF6*u2q|j2JR}L-%Ph)y zbUdUNQ!b2w24nWhB}nc5-wYVMkwvKVGjffpSM;^E;!kOX!##ntQg2f*A#Ea|(4SB^ zkx=YUC0U1Gh$g>@zs=c2=G;0G<=!TG`98X!jkiRnU-m^1!hJ&;N^83y?LXD43py|1ye#`t)x# z@`Y+B(XT9+Nm7DgOJd4+YUzl3^u8|>>&Em~b^4R4=Tub2;?dl(!Y_6AKp9E(BEZ0` z#f-_9Iofcs>Eaq_^aIfDKC16uX9(h9QG*y>4~ZJ_mE0u0Nf2=x#h};u{$`ViF`^Mc zPcv3DwV&0Fi7wS&Tun81Q%Uv-ZO@n59tMW?f;J}=oAgym(?2GYv%vBOdO(*#rLB0c zYc%)W9$!0^x^7%|SFoVyD_t%c-^3OF@Ldts@T&Sb>f7XcXE4+MbQ0+k2Ob3lMcia4ha?QBN%KX9ud7n1{nm9FFY< z3WD<03}Tywt$MXkB5&#$p(Mb$s*W^{ej9I;$%PPbn;;@6b%JIp3g;5{sn@^;VT)Ch zBq7S{O>Q4XW(H8NrVX_PQ4e6!6i!$S$TxPH)QHuUGj5(V26GxuCw`tZ>E;YRO~O2B zG6iMn=JaxwMGK>x&4}Z3Eb-u9U`YViXwkvAYq^m!67Bz^Y9I6um`Ti`k;~-ELrWbY z$7Rki=QGwj1r-vUGnNEyykquq;jCURvPf>lc{md-iI%vk<4~i!K}j&Iiak4ud2T{D zJ5Y!k^#C{VrgxaSjdQ*3xL+VSe5TIIXYm zhl6i+t54Wk5IOF z3w006K9Pg4=15di#D?)a$As(>8kKVD^$X8~EWFD2H)P$x-u!(psE`2E-x0&ZHiBbF zNZ4K3>qzr35mHNSpva{}tO5>~B0`Vn)X3@<$*o`6xP%fx~Dm!yP-P&HOx@Gp2mZ+yb`J3gotd z?e?H-F-)6X=>nq`pfEN3Bup6)VSt}&%T{J!__lx!aQc;Vo+=?ZdN6{Gq zhnDo(2gzgTlQFg@rX+?lr`3^-I*KPJpU2Dqn+Vp!ZPMMK?o!Eq7@*Lz|mqB8TkTGaHEVoqR!!btXfACZaO1&$)3x+3!)WpoVi~H zw&)NM=Gc|D0ZplpP#2ZdJ+8F}lkWyuKuVEc zzZCfQ)CDK|kM$4NkE{=*F7axD`s|Y}$6AILkL(HPmqC1H!P&K^*N$ZRb_FtPD7fF4 zlzFoBSn05QL>5RY^D2TVIVZOr+csS7OAMq`c-522I7+u@q;h02wPFj^wvF1do!Y*O z+U=os?WOkqWQHJW#9NSazgygfuQu^^d|Aak_}VV+#aFL*FTVDPYb5a6D_)PUgj&f> zf>3RcpgJuppjjwv+W;gWMe8xw9#Jj6YFYYbaVLJYiMQb^c{?L6u!6V%R8;jB4_w?$ zZFzvo=%A82C~e2zfpW5tEZ_IFP-t2fC+Y}l45vVwm;a3}m(rC)k4S8}#w1?r2@mN5 zR8q&dwv!zRZR&(3)33=4XtIYhCvwaEx#fY}y5~UjDzDnO>YV9s6b@#ZgFK8$O0S1a$ zqARo*gec-4Cqw~>$R9x>GKtDRG0abFXX77|$F_WtI;NzOP)m32L`{A0n zrVQfK_=JsF#HZPYjcUoK%a!22Tw-WeOUASb&85UxrlC1YGFEN?|G#IbH!39mUXZp? zEcv}y2L9ix)f>|lzt>A3*YDFAH%r|N;YEgKvE&Oe{Puw43$?mAUGW8kFH(F_qyRc! zl&UwSNxoQR*pw{!L$VC~T~HhTQGyTl58_6jR3KIX!3D3V6oFVpG)t@^wutXYtODR2 zQY!eecn`<&IVA$7NyaE7aOE1pM40@s_&8b%o?yRDFfzX9unt7Oqa zl3TLmiY36(+$WDE6yf>>3`L&<)PMq~7vZuMUb2XKz!;dnMg}65*l9qy$%{R-^Q1`- z(1G;M`1XHc#079$0j|%)3&tnWq5{E%K#{h&(4yr<6#%0xab^*d7X(c050wJeHzzUQ zx;eCxqiI>K1@cU_S`+C;Ev9r@$;0&Swn)vr`HE1+pe*Lc2>vw7`60Lph4J z2EaA31Oxh*CJM#c?O;t9Z%)09*u&6K$mJH^WHzS>XLT7O`Ai2W1k8C+d# z0zQo63rxw;(B9b9XSX@6tUj@OF2Z;JCm_DeF=+31lP^O(;K~*lm^-^!#4iD3a+6L_ z1w{5>eZ?p<5w?4rgX~=?9^Nwuoz{}}e%D?=_Pgx|+^i;V7_zd1_Y3sl*wJzBoi0yb zH(WKx)U*Us!+tjh+_TU=d3Mtg;k)U=xez}_GP?+%ej03!0QJ?3a_+WJ-)NdJZuA>B z28^4A;ux6HJk~OH7xh3tmFgVVxq^lQN>{)_dR;6?AJitzNQ+e~qtRk4{4OW5jAtuP zSH85yyD6BN?M3CCscK^3UH*l4{c&N9Uso`s3T73aZ8_aCve>sLkW~ra8Tn^RPnV9! zeX>Bt${_{!t@14kWULrcd}AyeiUT@&V<35Puwda(3!_?W8FP$vP#O1srGEg>Z)*0i zVPx4aGJPff^2V_Zfo1nZQkto)+bHvPD#JRie{ee4IJ7pXH%{n_{rch&)gSfc-xd<4 z^`aY#h_r$sNl;(-rGC-368QMLlF()S2&h{9k@;d)U~MZ^y?N{*Ds|gey6xDlS>NB> z@}tyyIv!}Q7YUFi-m(|LFd$47s_iDUEBx9OjB;@05AI%=c86;u^(%2SW6Dhzyrc^gpNXC*3jb)OtG8y=1-4fD3xeSCH6hcUm078o4kQUE^kP^^az>yvO zZ~%xuy59vLAh{)klnU6>!-Vk%dW2 z{u^fc02~kQcM)(GZQ&LM#cYpFoqg@v`JZ$6jYY6Xh>kRzc#FP1e|zb!NzH<@NFfZuOJby?vwkO!9L54r z{2B%LVjI`bsUpY22W{^nrOk~jKa>Gy5Am>VeC5XmYE6s`~Em%Op| zm9_BeKQ)2;b-}#F-zKZGQo(9JC3Pl^C|){R>CddA^mQ|ZC=4!(i5&G=r(9HW8Ko_o zks4H0cZMK!>6CF6@janu}WkiyHiy4Z)n^v;C+0M>dV- z26Cz+Sw(^DDo%Es?yCu8t_)@vq3QZ~XnI`y_c!yyLdJg9Qmg3?`D3;(ipJ8YExV|s z-4oirFSUIP80!a1Ee6UlW><^C*{Xs$h41D2Z0{ENlPkZ{Rx(MeesFidj67VcYtR#y z3{r4EQzta05TB(R8l{rYmL`J#bMbn7UD2x>MUpEyX${qqE7daak4eF1N--9%fY`AV zb>k|@*n+sma>-bU2>sM`Gtsqtle?kCm$vCZ6Pbz$Znvhj%PW{_Y!8Vt%kOmF4 z9-`*n_l5{G>tZh-O^21+BThwy2j=vA8TcPwKoHKkzg>#w;$$ZF5C)n}YPFX+L zvXHS$Uo33=1$Z-o7=XsO0_(UVU%jjParU(%1jp=eOkt7&}ao3QCs)yVW-J%%z{Xe zoiGtC8crQr&7@`WG1G{S59_nSrmy^W_?O(G<`u21W`FKs9r*_!hsfT&5?r{aBNF|Y zi2s@>_?jsDnkfI8Sny{ej`^qgGogY_LzjO@8%!>w2wgBf^N=Q}O*n+Xnb~KHPZysp zIbAZmpIWvtkkL#LhVP_SktqFJ0^RS{OZLb_Ro@cm&g{|N8MP z&Ev{VP+)2%m01-?S>sg)fzdYA`%U#1_fhve5HNN4l||n8Q}KUN#tp@vdYH;78`(9Q aL#=M4;+wuwZk$OVlnFDLgfxR$4*wUxYin2l literal 0 HcmV?d00001 diff --git a/Server/__pycache__/SMYMongoDBAPI.cpython-313.pyc b/Server/__pycache__/SMYMongoDBAPI.cpython-313.pyc index babbdcb58c1ec53b8d8f824c7b73a62d2da41aeb..0b3228abc643d1f942ecb0c797ebf31e3291a270 100644 GIT binary patch literal 41064 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 literal 33024 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 diff --git a/Server/__pycache__/TCPGameServer.cpython-313.pyc b/Server/__pycache__/TCPGameServer.cpython-313.pyc index 7f7ee6e1b22952987d8d7d924e0afb3c6c24ba87..b37fd7f607cd3db5e111112e0b452d526928d76b 100644 GIT binary patch delta 42502 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 W((LPQyVQjfS4*k`lV;0{+W!Ob;Eb05 delta 41301 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 2: # 允许在通用方法中出现 + print(f"⚠️ 发现重复代码: '{pattern}' 出现 {count} 次") + else: + print(f"✅ 代码模式 '{pattern}' 已优化") + + return True + + except Exception as e: + print(f"❌ 代码优化验证失败: {e}") + return False + +if __name__ == "__main__": + print("开始测试优化后的配置API...\n") + + # 测试所有配置方法 + api_test_success = test_all_config_methods() + + # 测试代码优化效果 + optimization_test_success = test_code_optimization() + + print(f"\n=== 最终结果 ===") + if api_test_success and optimization_test_success: + print("🎉 所有测试通过!代码优化成功!") + else: + print("❌ 部分测试失败,请检查相关问题") \ No newline at end of file diff --git a/server/game_saves/3205788256.json b/server/game_saves/3205788256.json index a5964cb..ad82613 100644 --- a/server/game_saves/3205788256.json +++ b/server/game_saves/3205788256.json @@ -13,7 +13,7 @@ }, { "crop_type": "杂交树2", - "grow_time": 19020, + "grow_time": 25230, "is_dead": false, "is_diged": true, "is_planted": true, @@ -35,7 +35,7 @@ }, { "crop_type": "杂交树1", - "grow_time": 19060, + "grow_time": 21670, "is_dead": false, "is_diged": true, "is_planted": true, @@ -46,7 +46,7 @@ }, { "crop_type": "杂交树2", - "grow_time": 4438, + "grow_time": 25200, "is_dead": false, "is_diged": true, "is_planted": true, @@ -68,7 +68,7 @@ }, { "crop_type": "杂交树2", - "grow_time": 13308, + "grow_time": 25254, "is_dead": false, "is_diged": true, "is_planted": true, @@ -101,7 +101,7 @@ }, { "crop_type": "杂交树2", - "grow_time": 2218, + "grow_time": 12749, "is_dead": false, "is_diged": true, "is_planted": true, @@ -178,7 +178,7 @@ }, { "crop_type": "杂交树2", - "grow_time": 2216, + "grow_time": 12747, "is_dead": false, "is_diged": true, "is_planted": true, @@ -189,7 +189,7 @@ }, { "crop_type": "杂交树2", - "grow_time": 2216, + "grow_time": 12747, "is_dead": false, "is_diged": true, "is_planted": true, @@ -200,7 +200,7 @@ }, { "crop_type": "杂交树2", - "grow_time": 2216, + "grow_time": 12747, "is_dead": false, "is_diged": true, "is_planted": true, @@ -277,7 +277,7 @@ }, { "crop_type": "杂交树2", - "grow_time": 2214, + "grow_time": 12745, "is_dead": false, "is_diged": true, "is_planted": true, @@ -321,7 +321,7 @@ }, { "crop_type": "杂交树2", - "grow_time": 2213, + "grow_time": 12744, "is_dead": false, "is_diged": true, "is_planted": true, @@ -822,9 +822,9 @@ "农场名称": "树萌芽の狗窝", "玩家昵称": "树萌芽", "等级": 66, - "钱币": 615196901889, - "最后登录时间": "2025年07月21日10时54分03秒", - "总游玩时间": "162时57分23秒", + "钱币": 615196902055, + "最后登录时间": "2025年07月21日13时20分46秒", + "总游玩时间": "162时58分35秒", "玩家账号": "3205788256", "玩家密码": "123456", "个人简介": "人生啊,就这样吧", @@ -923,7 +923,7 @@ { "name": "仙人掌", "quality": "优良", - "count": 3 + "count": 2 }, { "name": "胡萝卜", @@ -933,7 +933,7 @@ { "name": "土豆", "quality": "普通", - "count": 3 + "count": 2 }, { "name": "马铃薯", @@ -1361,27 +1361,10 @@ ], "登录时间": 1753061433.2417476 }, - "玩家小卖部": [ - { - "商品类型": "作物", - "商品名称": "番茄", - "商品价格": 999, - "商品数量": 1 - }, - { - "商品类型": "作物", - "商品名称": "番茄", - "商品价格": 888, - "商品数量": 81 - }, - { - "商品类型": "作物", - "商品名称": "荔枝", - "商品价格": 99999999999999999, - "商品数量": 1 - } - ], - "小卖部格子数": 10, + "小卖部配置": { + "商品列表": [], + "格子数": 11 + }, "游戏设置": { "背景音乐音量": 0.0, "天气显示": true