NetEaseDSMonitor/cms_monitor.py

589 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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()