589 lines
22 KiB
Python
589 lines
22 KiB
Python
# -*- 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() |