# -*- coding: utf-8 -*- import requests import time import random from datetime import datetime import logging import os import sys import json import threading from threading import Lock # 配置日志 def setup_logging(): try: # 获取当前脚本所在目录 script_dir = os.path.dirname(os.path.abspath(__file__)) log_file = os.path.join(script_dir, 'cms_monitor.log') # 确保日志文件存在 if not os.path.exists(log_file): with open(log_file, 'w', encoding='utf-8') as f: f.write('') logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(log_file, encoding='utf-8', mode='a'), logging.StreamHandler() ] ) logging.info("CMS监控日志系统初始化成功") except Exception as e: print(f"日志系统初始化失败: {str(e)}") # 如果文件日志失败,至少使用控制台日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.StreamHandler()] ) # 初始化日志系统 setup_logging() # 配置 API_BASE_URL = 'https://god-cms.gameyw.netease.com/cms/admin/logPage' # 默认的各类操作的权重系数 DEFAULT_COEFFICIENTS = { 'comment': 0.55, # 评论审核权重 'feed': 1.54, # 动态审核权重 'complaint': 5.4 # 举报处理权重 } # 系数配置文件路径 COEFFICIENTS_CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_coefficients.json') # 全局变量 user_credentials = { 'cookie': None, 'username': None } credentials_lock = Lock() coefficients_lock = Lock() # 定义全局系数变量 COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() # 读取系数配置 def load_coefficients(): """从配置文件读取系数,如果文件不存在则创建默认配置""" global COEFFICIENTS try: with coefficients_lock: if os.path.exists(COEFFICIENTS_CONFIG_FILE): with open(COEFFICIENTS_CONFIG_FILE, 'r', encoding='utf-8') as f: loaded_coefficients = json.load(f) log(f"从配置文件加载系数: {str(loaded_coefficients)}") # 更新系数 COEFFICIENTS.update(loaded_coefficients) else: # 创建默认配置文件 with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(DEFAULT_COEFFICIENTS, f, indent=4, ensure_ascii=False) log("创建默认系数配置文件") COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() log(f"当前使用的系数: 评论={COEFFICIENTS['comment']}, 动态={COEFFICIENTS['feed']}, 举报={COEFFICIENTS['complaint']}") except Exception as e: log(f"加载系数配置失败: {str(e)}") # 出错时使用默认系数 COEFFICIENTS = DEFAULT_COEFFICIENTS.copy() # 保存系数配置 def save_coefficients(coefficients=None): """保存系数到配置文件""" try: if coefficients is None: coefficients = COEFFICIENTS with coefficients_lock: with open(COEFFICIENTS_CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(coefficients, f, indent=4, ensure_ascii=False) log(f"系数配置已保存: {str(coefficients)}") except Exception as e: log(f"保存系数配置失败: {str(e)}") # 初始化用户凭据(从环境变量读取) def init_credentials(): """从环境变量初始化用户凭据""" try: cookie = os.environ.get('CMS_COOKIE', '') username = os.environ.get('CMS_USERNAME', '') if cookie and username: with credentials_lock: user_credentials['cookie'] = cookie user_credentials['username'] = username log(f"已从环境变量加载用户凭据: {username}") return True else: log(f"未能从环境变量获取用户凭据,CMS_COOKIE长度: {len(cookie)}, CMS_USERNAME: {username}") return False except Exception as e: log(f"初始化用户凭据失败: {str(e)}") return False def get_api_headers(cookie): """获取API请求头""" return { 'accept': 'application/json, text/javascript, */*; q=0.01', 'accept-language': 'zh-CN,zh;q=0.9', 'cookie': cookie, 'priority': 'u=1, i', 'referer': 'https://god-cms.gameyw.netease.com/cms/admin/logList', 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-origin', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'x-requested-with': 'XMLHttpRequest' } def get_api_params(username, start_time, end_time, page=1, limit=500): """获取API请求参数""" return { 'page': page, 'limit': limit, 'username': username, 'operation': '', 'oid': '', 'startTime': start_time, 'endTime': end_time, 'ext': '{}' } def log(message): """记录日志""" try: logging.info(f"[CMS] {message}") except Exception as e: print(f"日志记录失败: {str(e)}") print(f"原始消息: {message}") def get_stats_from_api(cookie, username, start_time, end_time, max_pages=500): """从API获取统计数据""" stats = { 'comment': 0, 'feed': 0, 'complaint': 0 } headers = get_api_headers(cookie) total_count = 0 page = 1 while page <= max_pages: try: params = get_api_params(username, start_time, end_time, page) log(f"正在获取第 {page} 页数据,时间范围:{start_time} 至 {end_time}") # 记录请求详情(不包含敏感信息) log(f"请求URL: {API_BASE_URL}") log(f"请求参数: {str({k: v for k, v in params.items() if k not in ['cookie', 'username']})}") response = requests.get(API_BASE_URL, headers=headers, params=params, timeout=10) if response.status_code == 200: try: data = response.json() # 检查API响应格式 if 'result' in data: # 这是一个成功的响应,直接使用result字段 items = data.get('result', []) log(f"API返回成功,找到 {len(items)} 条记录") elif data.get('success') == False: # 这是一个明确的失败响应 error_msg = data.get('message', '未知错误') log(f"API返回错误:{error_msg}") log(f"API响应内容:{response.text[:500]}") if 'login' in error_msg.lower(): log("Cookie可能已过期") return None break else: # 其他情况,记录响应并退出 log(f"未知的API响应格式:{response.text[:500]}") break count = len(items) if count == 0: log("当前页没有数据") break for item in items: title = item.get('title', '') operation = item.get('operation', '') if (title == '评论审核:审核通过' or operation == 'FEED_COMMENT_REVIEW_PASS') and not title == '动态审核:审核通过': stats['comment'] += 1 elif title == '动态审核:审核通过' or operation == 'FEED_REVIEW_PASS': stats['feed'] += 1 elif operation == 'HANDLE_COMPLAINT': stats['complaint'] += 1 total_count += count log(f"第{page}页有 {count} 条记录,总计 {total_count} 条") log(f"统计结果:评论 {stats['comment']},动态 {stats['feed']},举报 {stats['complaint']}") if count < params['limit']: log("已获取所有数据") break except ValueError as e: log(f"解析JSON数据失败: {str(e)}") log(f"原始响应内容:{response.text[:500]}") if 'login' in response.text.lower(): log("Cookie已过期") return None break else: log(f"API请求失败: HTTP {response.status_code}") log(f"响应内容: {response.text[:500]}") break page += 1 except requests.exceptions.Timeout: log("API请求超时") break except requests.exceptions.ConnectionError: log("网络连接错误,请检查网络连接") break except Exception as e: log(f"获取数据失败: {str(e)}") break # 确保每次都是从配置文件读取最新系数 load_coefficients() # 使用全局系数变量 with coefficients_lock: current_coefficients = COEFFICIENTS.copy() # 计算折算总数 weighted_total = ( stats['comment'] * current_coefficients['comment'] + stats['feed'] * current_coefficients['feed'] + stats['complaint'] * current_coefficients['complaint'] ) log(f"最终统计结果:评论 {stats['comment']},动态 {stats['feed']},举报 {stats['complaint']},折算总计 {weighted_total:.2f}") log(f"使用系数:评论={current_coefficients['comment']}, 动态={current_coefficients['feed']}, 举报={current_coefficients['complaint']}") # 构建包含系数的结果 categories = { "评论审核": { "count": stats['comment'], "weighted": stats['comment'] * current_coefficients['comment'], "coefficient": current_coefficients['comment'] }, "动态审核": { "count": stats['feed'], "weighted": stats['feed'] * current_coefficients['feed'], "coefficient": current_coefficients['feed'] }, "举报处理": { "count": stats['complaint'], "weighted": stats['complaint'] * current_coefficients['complaint'], "coefficient": current_coefficients['complaint'] } } return { 'stats': stats, 'weighted_total': weighted_total, 'total_count': total_count, 'categories': categories, 'coefficients': current_coefficients } def check_current_hour_counts(cookie, username): """检查当前小时数据""" try: # 获取当前小时的开始时间 now = datetime.now() current_hour = now.hour start_time = now.replace(minute=0, second=0, microsecond=0).strftime('%Y-%m-%d %H:%M:%S') end_time = now.strftime('%Y-%m-%d %H:%M:%S') # 调用API获取数据 result = get_stats_from_api(cookie, username, start_time, end_time) if result is None: log("获取当前小时数据失败") return None # 将结果写入共享文件 try: data = { 'type': 'cms_hourly', 'stats': result, 'timestamp': now.strftime('%Y-%m-%d %H:%M:%S') } with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_hourly.json'), 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False) log("小时数据已更新到共享文件") except Exception as e: log(f"写入小时数据到共享文件失败: {str(e)}") return result except Exception as e: log(f"检查当前小时数据失败: {str(e)}") return None def check_daily_counts(cookie, username): """检查全天数据""" try: # 获取今天的开始时间 today = datetime.now().strftime('%Y-%m-%d') start_time = "%s 00:00:00" % today end_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 调用API获取数据 result = get_stats_from_api(cookie, username, start_time, end_time) if result is None: log("获取今日数据失败") return None # 将结果写入共享文件 try: data = { 'type': 'cms_daily', 'stats': result, 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_daily.json'), 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False) log("全天数据已更新到共享文件") except Exception as e: log(f"写入全天数据到共享文件失败: {str(e)}") return result except Exception as e: log(f"检查今日数据失败: {str(e)}") return None def monitor_hourly_thread(): """每小时监控线程""" log("每小时监控线程启动") while True: try: # 从全局变量获取用户信息 with credentials_lock: cookie = user_credentials['cookie'] username = user_credentials['username'] if cookie and username: # 检查当前小时数据 check_current_hour_counts(cookie, username) time.sleep(120) # 每2分钟检查一次 else: time.sleep(30) # 未登录时等待30秒 except Exception as e: log(f"每小时监控线程异常: {str(e)}") time.sleep(60) # 发生异常时等待1分钟后重试 def monitor_daily_thread(): """每日监控线程""" log("每日监控线程启动") while True: try: # 从全局变量获取用户信息 with credentials_lock: cookie = user_credentials['cookie'] username = user_credentials['username'] if cookie and username: # 检查全天数据 check_daily_counts(cookie, username) time.sleep(3600) # 每60分钟检查一次 else: time.sleep(30) # 未登录时等待30秒 except Exception as e: log(f"每日监控线程异常: {str(e)}") time.sleep(60) # 发生异常时等待1分钟后重试 # 监控配置文件变化线程 def monitor_config_thread(): """监控配置文件变化线程""" log("配置监控线程启动") last_modified_time = 0 while True: try: if os.path.exists(COEFFICIENTS_CONFIG_FILE): current_modified_time = os.path.getmtime(COEFFICIENTS_CONFIG_FILE) # 检查文件是否有更新 if current_modified_time > last_modified_time: log(f"检测到配置文件变化,重新加载系数") load_coefficients() # 系数变化后,立即重新计算数据 with credentials_lock: cookie = user_credentials['cookie'] username = user_credentials['username'] if cookie and username: log("系数变更后立即更新数据...") threading.Thread(target=lambda: check_current_hour_counts(cookie, username)).start() threading.Thread(target=lambda: check_daily_counts(cookie, username)).start() last_modified_time = current_modified_time time.sleep(10) # 每10秒检查一次,确保数据更新及时 except Exception as e: log(f"配置监控线程异常: {str(e)}") time.sleep(60) # 发生异常时等待1分钟后重试 def main(): """主函数""" log("CMS监控系统启动") # 解析命令行参数 check_now = False force_mode = False update_coefficients = False no_config_check = False for arg in sys.argv: if arg == "--check-now": check_now = True log("收到立即检查参数") elif arg == "--force": force_mode = True log("收到强制模式参数") elif arg == "--update-coefficients": update_coefficients = True log("收到更新系数参数") elif arg == "--no-config-check": no_config_check = True log("收到禁用配置检查参数") # 从配置文件加载系数,除非指定了不检查配置 if not no_config_check: load_coefficients() else: log("跳过配置检查,使用当前已加载的系数") # 处理系数更新 if update_coefficients: # 检查是否提供了新系数 if len(sys.argv) >= 5: # 脚本名 + --update-coefficients + 3个系数值 try: # 查找参数中的系数值 for i, arg in enumerate(sys.argv): if arg == "--update-coefficients" and i + 3 < len(sys.argv): new_coefficients = { 'comment': float(sys.argv[i+1]), 'feed': float(sys.argv[i+2]), 'complaint': float(sys.argv[i+3]) } log(f"更新系数:评论={new_coefficients['comment']}, 动态={new_coefficients['feed']}, 举报={new_coefficients['complaint']}") # 更新全局系数 with coefficients_lock: COEFFICIENTS.update(new_coefficients) # 保存到配置文件 save_coefficients() break except ValueError as e: log(f"系数更新失败: {str(e)}") log("系数必须是有效的数字") log("系数更新完成,退出程序") sys.exit(0) # 确保输出目录存在 script_dir = os.path.dirname(os.path.abspath(__file__)) if not os.path.exists(script_dir): os.makedirs(script_dir) # 从环境变量初始化凭据 init_credentials() # 处理--check-now参数 if check_now: # 从全局变量获取用户信息 with credentials_lock: cookie = user_credentials['cookie'] username = user_credentials['username'] if cookie and username: log("开始执行手动检查") if force_mode: # 在强制模式下,使用当前小时的整点数据进行查询 now = datetime.now() current_hour = now.hour start_time = now.replace(minute=0, second=0, microsecond=0).strftime('%Y-%m-%d %H:%M:%S') end_time = now.replace(minute=59, second=59, microsecond=999999).strftime('%Y-%m-%d %H:%M:%S') log(f"强制模式:使用整点时间 {start_time} 至 {end_time}") # 调用API获取数据 result = get_stats_from_api(cookie, username, start_time, end_time) if result is not None: # 将结果写入共享文件 try: data = { 'type': 'cms_hourly', 'stats': result, 'timestamp': now.strftime('%Y-%m-%d %H:%M:%S') } with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_hourly.json'), 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False) log("小时数据已更新到共享文件") except Exception as e: log(f"写入小时数据到共享文件失败: {str(e)}") else: # 常规检查 check_current_hour_counts(cookie, username) log("手动检查完成") else: log("无法执行手动检查: 凭据不可用") # 立即检查完成后退出 sys.exit(0) # 启动监控线程 hourly_thread = threading.Thread(target=monitor_hourly_thread) hourly_thread.daemon = True hourly_thread.start() daily_thread = threading.Thread(target=monitor_daily_thread) daily_thread.daemon = True daily_thread.start() # 启动配置监控线程 config_thread = threading.Thread(target=monitor_config_thread) config_thread.daemon = True config_thread.start() # 保持主线程运行 try: while True: time.sleep(10) except KeyboardInterrupt: log("程序被用户中断") except Exception as e: log(f"主线程异常: {str(e)}") finally: log("CMS监控系统关闭") if __name__ == '__main__': main()