shop_config_manager.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 商店配置管理器
  5. 从config_manager.py中提取的商店相关配置管理功能
  6. 支持从Excel读取商店配置并与现有JSON配置合并
  7. """
  8. import json
  9. import os
  10. from pathlib import Path
  11. from datetime import datetime
  12. try:
  13. import pandas as pd
  14. PANDAS_AVAILABLE = True
  15. except ImportError:
  16. PANDAS_AVAILABLE = False
  17. print("警告: pandas未安装,无法处理Excel文件")
  18. class ShopConfigManager:
  19. """商店配置管理器"""
  20. def __init__(self, excel_file_path=None, json_file_path=None):
  21. """初始化商店配置管理器
  22. Args:
  23. excel_file_path: Excel配置文件路径
  24. json_file_path: JSON配置文件路径
  25. """
  26. self.script_dir = Path(__file__).parent
  27. # 设置默认路径
  28. if excel_file_path is None:
  29. self.excel_file = self.script_dir / "商店配置" / "商店配置表.xlsx"
  30. else:
  31. self.excel_file = Path(excel_file_path)
  32. if json_file_path is None:
  33. self.json_file = self.script_dir.parent / "shop.json"
  34. else:
  35. self.json_file = Path(json_file_path)
  36. print(f"Excel文件路径: {self.excel_file}")
  37. print(f"JSON文件路径: {self.json_file}")
  38. # 商店配置映射
  39. self.shop_mapping = {
  40. 'format_type': 'horizontal',
  41. 'param_types': {
  42. # 每日奖励配置字段
  43. '奖励类型': str,
  44. '奖励索引': int,
  45. '奖励数量': int,
  46. '每日最大领取次数': int,
  47. # 英文字段支持
  48. 'rewardType': str,
  49. 'rewardIndex': int,
  50. 'rewardAmount': int,
  51. 'maxClaimsPerDay': int
  52. }
  53. }
  54. def load_existing_json_config(self):
  55. """加载现有的JSON配置文件"""
  56. try:
  57. if self.json_file.exists():
  58. with open(self.json_file, 'r', encoding='utf-8') as f:
  59. config = json.load(f)
  60. print(f"成功加载现有JSON配置")
  61. return config
  62. else:
  63. print(f"JSON文件不存在,将创建新配置: {self.json_file}")
  64. return {'dailyRewards': {'money': {'rewards': [], 'maxClaimsPerDay': 36}, 'diamond': {'rewards': [], 'maxClaimsPerDay': 36}}}
  65. except Exception as e:
  66. print(f"加载JSON配置失败: {e}")
  67. return {'dailyRewards': {'money': {'rewards': [], 'maxClaimsPerDay': 36}, 'diamond': {'rewards': [], 'maxClaimsPerDay': 36}}}
  68. def read_excel_config(self):
  69. """读取Excel配置文件"""
  70. if not PANDAS_AVAILABLE:
  71. print("错误: pandas未安装,无法读取Excel文件")
  72. return None
  73. if not self.excel_file.exists():
  74. print(f"Excel文件不存在: {self.excel_file}")
  75. return None
  76. try:
  77. # 读取Excel文件的所有工作表
  78. excel_data = pd.read_excel(self.excel_file, sheet_name=None)
  79. print(f"成功读取Excel文件,包含工作表: {list(excel_data.keys())}")
  80. config = {'dailyRewards': {'money': {'rewards': [], 'maxClaimsPerDay': 36}, 'diamond': {'rewards': [], 'maxClaimsPerDay': 36}}}
  81. # 处理金币奖励工作表
  82. if 'money_rewards' in excel_data or '金币奖励' in excel_data:
  83. sheet_name = 'money_rewards' if 'money_rewards' in excel_data else '金币奖励'
  84. money_df = excel_data[sheet_name]
  85. money_rewards = self.parse_rewards_sheet(money_df, 'money')
  86. if money_rewards:
  87. config['dailyRewards']['money'] = money_rewards
  88. # 处理钻石奖励工作表
  89. if 'diamond_rewards' in excel_data or '钻石奖励' in excel_data:
  90. sheet_name = 'diamond_rewards' if 'diamond_rewards' in excel_data else '钻石奖励'
  91. diamond_df = excel_data[sheet_name]
  92. diamond_rewards = self.parse_rewards_sheet(diamond_df, 'diamond')
  93. if diamond_rewards:
  94. config['dailyRewards']['diamond'] = diamond_rewards
  95. # 如果只有一个工作表,尝试解析为通用格式
  96. if len(excel_data) == 1:
  97. sheet_name = list(excel_data.keys())[0]
  98. df = excel_data[sheet_name]
  99. parsed_config = self.parse_general_shop_sheet(df)
  100. if parsed_config:
  101. config = parsed_config
  102. return config
  103. except Exception as e:
  104. print(f"读取Excel文件失败: {e}")
  105. return None
  106. def parse_rewards_sheet(self, df, reward_type):
  107. """解析奖励工作表"""
  108. try:
  109. rewards = []
  110. max_claims = 10 # 默认值
  111. # 读取所有数据行(pandas已经自动处理了标题行)
  112. if len(df) > 0:
  113. # 从第一行开始读取奖励数据(pandas自动将标题行作为列名)
  114. for index in range(0, len(df)):
  115. row = df.iloc[index]
  116. if pd.isna(row.iloc[0]):
  117. continue
  118. try:
  119. reward_amount = int(row.iloc[0])
  120. rewards.append(reward_amount)
  121. except (ValueError, TypeError):
  122. continue
  123. # 检查是否有第三列的maxClaimsPerDay配置
  124. if len(df.columns) >= 3 and len(df) > 1:
  125. try:
  126. # 从第一行第三列读取maxClaimsPerDay值(索引0是第一行数据)
  127. max_claims_value = df.iloc[0, 2]
  128. if not pd.isna(max_claims_value):
  129. max_claims = int(max_claims_value)
  130. except (ValueError, TypeError, IndexError):
  131. pass
  132. print(f"解析{reward_type}奖励: {len(rewards)}个奖励, maxClaimsPerDay: {max_claims}")
  133. return {
  134. 'rewards': rewards,
  135. 'maxClaimsPerDay': max_claims
  136. }
  137. except Exception as e:
  138. print(f"解析{reward_type}奖励工作表失败: {e}")
  139. return None
  140. def parse_general_shop_sheet(self, df):
  141. """解析通用商店配置工作表"""
  142. try:
  143. config = {'dailyRewards': {'money': {'rewards': [], 'maxClaimsPerDay': 36}, 'diamond': {'rewards': [], 'maxClaimsPerDay': 36}}}
  144. # 检查列名
  145. columns = [str(col).lower() for col in df.columns]
  146. for index, row in df.iterrows():
  147. if pd.isna(row.iloc[0]):
  148. continue
  149. try:
  150. # 根据列名判断奖励类型
  151. reward_type = None
  152. reward_amount = None
  153. max_claims = 36
  154. # 查找奖励类型列
  155. for i, col in enumerate(columns):
  156. if 'type' in col or '类型' in col:
  157. reward_type = str(row.iloc[i]).lower()
  158. break
  159. # 查找奖励数量列
  160. for i, col in enumerate(columns):
  161. if 'amount' in col or 'reward' in col or '数量' in col or '奖励' in col:
  162. reward_amount = int(row.iloc[i])
  163. break
  164. # 查找最大领取次数列
  165. for i, col in enumerate(columns):
  166. if 'max' in col or '最大' in col:
  167. max_claims = int(row.iloc[i])
  168. break
  169. # 添加到对应的奖励类型
  170. if reward_type and reward_amount is not None:
  171. if 'money' in reward_type or '金币' in reward_type:
  172. config['dailyRewards']['money']['rewards'].append(reward_amount)
  173. config['dailyRewards']['money']['maxClaimsPerDay'] = max_claims
  174. elif 'diamond' in reward_type or '钻石' in reward_type:
  175. config['dailyRewards']['diamond']['rewards'].append(reward_amount)
  176. config['dailyRewards']['diamond']['maxClaimsPerDay'] = max_claims
  177. except Exception as e:
  178. print(f"解析第 {index+1} 行失败: {e}")
  179. continue
  180. return config
  181. except Exception as e:
  182. print(f"解析通用商店配置工作表失败: {e}")
  183. return None
  184. def merge_configs(self, excel_config, json_config):
  185. """合并Excel配置和现有JSON配置"""
  186. if not excel_config:
  187. return json_config
  188. try:
  189. # 合并每日奖励配置
  190. if 'dailyRewards' in excel_config:
  191. if 'dailyRewards' not in json_config:
  192. json_config['dailyRewards'] = {}
  193. # 合并金币奖励
  194. if 'money' in excel_config['dailyRewards']:
  195. json_config['dailyRewards']['money'] = excel_config['dailyRewards']['money']
  196. # 合并钻石奖励
  197. if 'diamond' in excel_config['dailyRewards']:
  198. json_config['dailyRewards']['diamond'] = excel_config['dailyRewards']['diamond']
  199. print("配置合并完成")
  200. return json_config
  201. except Exception as e:
  202. print(f"合并配置失败: {e}")
  203. return json_config
  204. def validate_config(self, config):
  205. """验证配置数据"""
  206. errors = []
  207. # 验证每日奖励配置
  208. if not config.get("dailyRewards"):
  209. errors.append("每日奖励配置为空")
  210. else:
  211. daily_rewards = config["dailyRewards"]
  212. # 验证金币奖励
  213. if "money" in daily_rewards:
  214. money_config = daily_rewards["money"]
  215. if not money_config.get("rewards"):
  216. errors.append("金币奖励列表为空")
  217. elif not isinstance(money_config["rewards"], list):
  218. errors.append("金币奖励必须是数组")
  219. else:
  220. for i, reward in enumerate(money_config["rewards"]):
  221. if not isinstance(reward, (int, float)) or reward <= 0:
  222. errors.append(f"金币奖励第 {i+1} 项必须是正数")
  223. if money_config.get("maxClaimsPerDay", 0) <= 0:
  224. errors.append("金币每日最大领取次数必须大于0")
  225. # 验证钻石奖励
  226. if "diamond" in daily_rewards:
  227. diamond_config = daily_rewards["diamond"]
  228. if not diamond_config.get("rewards"):
  229. errors.append("钻石奖励列表为空")
  230. elif not isinstance(diamond_config["rewards"], list):
  231. errors.append("钻石奖励必须是数组")
  232. else:
  233. for i, reward in enumerate(diamond_config["rewards"]):
  234. if not isinstance(reward, (int, float)) or reward <= 0:
  235. errors.append(f"钻石奖励第 {i+1} 项必须是正数")
  236. if diamond_config.get("maxClaimsPerDay", 0) <= 0:
  237. errors.append("钻石每日最大领取次数必须大于0")
  238. return errors
  239. def backup_json(self):
  240. """备份当前JSON文件(已禁用)"""
  241. # 不再创建备份文件,直接返回成功
  242. return True
  243. def save_json_config(self, config):
  244. """保存配置到JSON文件"""
  245. try:
  246. # 确保目录存在
  247. self.json_file.parent.mkdir(parents=True, exist_ok=True)
  248. with open(self.json_file, 'w', encoding='utf-8') as f:
  249. json.dump(config, f, ensure_ascii=False, indent=2)
  250. print(f"配置已保存到: {self.json_file}")
  251. return True
  252. except Exception as e:
  253. print(f"保存JSON文件失败: {e}")
  254. return False
  255. def import_config(self):
  256. """导入配置的主要方法"""
  257. print("开始导入商店配置...")
  258. # 1. 加载现有JSON配置
  259. json_config = self.load_existing_json_config()
  260. # 2. 读取Excel配置
  261. excel_config = self.read_excel_config()
  262. if not excel_config:
  263. print("无法读取Excel配置,导入失败")
  264. return False
  265. # 3. 合并配置
  266. merged_config = self.merge_configs(excel_config, json_config)
  267. # 4. 验证配置
  268. errors = self.validate_config(merged_config)
  269. if errors:
  270. print("配置验证失败:")
  271. for error in errors:
  272. print(f" - {error}")
  273. return False
  274. # 5. 备份现有配置
  275. if not self.backup_json():
  276. print("备份失败,但继续导入...")
  277. # 6. 保存新配置
  278. if self.save_json_config(merged_config):
  279. print("商店配置导入成功!")
  280. return True
  281. else:
  282. print("保存配置失败")
  283. return False
  284. def create_excel_template(self):
  285. """创建Excel模板文件"""
  286. if not PANDAS_AVAILABLE:
  287. print("错误: pandas未安装,无法创建Excel模板")
  288. return False
  289. try:
  290. # 确保目录存在
  291. template_dir = self.excel_file.parent
  292. template_dir.mkdir(parents=True, exist_ok=True)
  293. # 创建示例数据
  294. money_data = {
  295. '奖励数量': [100, 120, 150, 180, 200, 250, 300, 350, 400, 450],
  296. '备注': ['第1天', '第2天', '第3天', '第4天', '第5天', '第6天', '第7天', '第8天', '第9天', '第10天']
  297. }
  298. diamond_data = {
  299. '奖励数量': [10, 12, 15, 18, 20, 25, 30, 35, 40, 45],
  300. '备注': ['第1天', '第2天', '第3天', '第4天', '第5天', '第6天', '第7天', '第8天', '第9天', '第10天']
  301. }
  302. # 创建DataFrame
  303. money_df = pd.DataFrame(money_data)
  304. diamond_df = pd.DataFrame(diamond_data)
  305. # 保存到Excel文件
  306. with pd.ExcelWriter(self.excel_file, engine='openpyxl') as writer:
  307. money_df.to_excel(writer, sheet_name='金币奖励', index=False)
  308. diamond_df.to_excel(writer, sheet_name='钻石奖励', index=False)
  309. print(f"Excel模板已创建: {self.excel_file}")
  310. return True
  311. except Exception as e:
  312. print(f"创建Excel模板失败: {e}")
  313. return False
  314. # 测试代码
  315. if __name__ == "__main__":
  316. manager = ShopConfigManager()
  317. # 创建模板
  318. print("创建Excel模板...")
  319. manager.create_excel_template()
  320. # 导入配置
  321. print("\n导入配置...")
  322. success = manager.import_config()
  323. if success:
  324. print("\n商店配置管理器测试完成!")
  325. else:
  326. print("\n商店配置管理器测试失败!")