import json import random from datetime import datetime from typing import List, Dict, Optional from pathlib import Path from loguru import logger from config import Config class DataManager: """数据管理器,负责URL和统计数据的存储与管理""" def __init__(self, data_file: str = None): self.data_file = data_file or str(Path(Config.DATA_DIR) / 'urls_data.json') self._ensure_data_file() self.data = self._load_data() def _ensure_data_file(self): """确保数据文件存在""" Config.ensure_dirs() if not Path(self.data_file).exists(): self._save_data({'urls': {}}) def _load_data(self) -> Dict: """加载数据""" try: with open(self.data_file, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: logger.error(f"加载数据文件失败: {str(e)}") return {'urls': {}} def _save_data(self, data: Dict = None): """保存数据""" try: save_data = data if data is not None else self.data with open(self.data_file, 'w', encoding='utf-8') as f: json.dump(save_data, f, ensure_ascii=False, indent=2) except Exception as e: logger.error(f"保存数据文件失败: {str(e)}") def add_url(self, url: str) -> bool: """ 添加新URL Args: url: MIP页面链接 Returns: 是否添加成功 """ try: if url in self.data['urls']: logger.warning(f"URL已存在: {url}") return False # 生成随机目标点击次数 target_clicks = random.randint(Config.MIN_CLICK_COUNT, Config.MAX_CLICK_COUNT) self.data['urls'][url] = { 'url': url, 'status': 'active', # active, completed, failed 'target_clicks': target_clicks, 'click_count': 0, 'reply_count': 0, 'created_time': datetime.now().isoformat(), 'last_click_time': None, 'click_history': [] } self._save_data() logger.info(f"成功添加URL,目标点击次数: {target_clicks}") return True except Exception as e: logger.error(f"添加URL失败: {str(e)}") return False def record_click(self, url: str, has_reply: bool = False): """ 记录一次点击 Args: url: URL has_reply: 是否获得回复 """ try: if url not in self.data['urls']: logger.error(f"URL不存在: {url}") return url_data = self.data['urls'][url] url_data['click_count'] += 1 url_data['last_click_time'] = datetime.now().isoformat() if has_reply: url_data['reply_count'] += 1 # 记录点击历史 url_data['click_history'].append({ 'time': datetime.now().isoformat(), 'has_reply': has_reply }) self._save_data() logger.info(f"记录点击成功,总点击次数: {url_data['click_count']}/{url_data['target_clicks']}") except Exception as e: logger.error(f"记录点击失败: {str(e)}") def mark_url_completed(self, url: str): """ 标记URL为已完成 Args: url: URL """ try: if url not in self.data['urls']: logger.error(f"URL不存在: {url}") return self.data['urls'][url]['status'] = 'completed' self.data['urls'][url]['completed_time'] = datetime.now().isoformat() self._save_data() logger.info(f"URL已完成: {url}") except Exception as e: logger.error(f"标记URL完成失败: {str(e)}") def mark_url_failed(self, url: str, reason: str = ""): """ 标记URL为失败 Args: url: URL reason: 失败原因 """ try: if url not in self.data['urls']: logger.error(f"URL不存在: {url}") return self.data['urls'][url]['status'] = 'failed' self.data['urls'][url]['failed_reason'] = reason self.data['urls'][url]['failed_time'] = datetime.now().isoformat() self._save_data() logger.warning(f"URL标记为失败: {url}, 原因: {reason}") except Exception as e: logger.error(f"标记URL失败状态失败: {str(e)}") def get_active_urls(self) -> List[Dict]: """ 获取所有活跃的URL Returns: 活跃URL列表 """ try: active_urls = [ data for data in self.data['urls'].values() if data['status'] == 'active' ] return active_urls except Exception as e: logger.error(f"获取活跃URL失败: {str(e)}") return [] def get_url_info(self, url: str) -> Optional[Dict]: """ 获取URL详细信息 Args: url: URL Returns: URL信息 """ return self.data['urls'].get(url) def get_all_urls(self) -> List[Dict]: """ 获取所有URL Returns: 所有URL列表 """ return list(self.data['urls'].values()) def get_statistics(self) -> Dict: """ 获取统计数据 Returns: 统计数据 """ try: total_urls = len(self.data['urls']) active_urls = sum(1 for data in self.data['urls'].values() if data['status'] == 'active') completed_urls = sum(1 for data in self.data['urls'].values() if data['status'] == 'completed') failed_urls = sum(1 for data in self.data['urls'].values() if data['status'] == 'failed') total_clicks = sum(data['click_count'] for data in self.data['urls'].values()) total_replies = sum(data['reply_count'] for data in self.data['urls'].values()) stats = { 'total_urls': total_urls, 'active_urls': active_urls, 'completed_urls': completed_urls, 'failed_urls': failed_urls, 'total_clicks': total_clicks, 'total_replies': total_replies, 'reply_rate': f"{(total_replies / total_clicks * 100) if total_clicks > 0 else 0:.2f}%" } return stats except Exception as e: logger.error(f"获取统计数据失败: {str(e)}") return {} def delete_url(self, url: str) -> bool: """ 删除URL Args: url: URL Returns: 是否删除成功 """ try: if url in self.data['urls']: del self.data['urls'][url] self._save_data() logger.info(f"已删除URL: {url}") return True else: logger.warning(f"URL不存在: {url}") return False except Exception as e: logger.error(f"删除URL失败: {str(e)}") return False def reset_url(self, url: str) -> bool: """ 重置URL状态(重新开始点击) Args: url: URL Returns: 是否重置成功 """ try: if url not in self.data['urls']: logger.warning(f"URL不存在: {url}") return False # 生成新的随机目标点击次数 target_clicks = random.randint(Config.MIN_CLICK_COUNT, Config.MAX_CLICK_COUNT) self.data['urls'][url]['status'] = 'active' self.data['urls'][url]['target_clicks'] = target_clicks self.data['urls'][url]['click_count'] = 0 self.data['urls'][url]['reply_count'] = 0 self.data['urls'][url]['last_click_time'] = None self.data['urls'][url]['click_history'] = [] self.data['urls'][url]['reset_time'] = datetime.now().isoformat() self._save_data() logger.info(f"已重置URL: {url}") return True except Exception as e: logger.error(f"重置URL失败: {str(e)}") return False