1022 lines
41 KiB
Python
1022 lines
41 KiB
Python
# -*- coding: utf-8 -*-
|
||
import requests
|
||
import time
|
||
import json
|
||
from datetime import datetime, timedelta
|
||
import logging
|
||
import os
|
||
import sys
|
||
import threading
|
||
from threading import Lock
|
||
import re
|
||
import subprocess
|
||
|
||
# 配置日志
|
||
def setup_logging():
|
||
try:
|
||
# 获取当前脚本所在目录
|
||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||
log_file = os.path.join(script_dir, 'breeze_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("Breeze监控日志系统初始化成功")
|
||
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配置
|
||
API_BASE_URL = 'https://breeze.gameyw.netease.com/api/cms/issue/list'
|
||
|
||
# 默认的各类工单的折算系数
|
||
DEFAULT_COEFFICIENTS = {
|
||
'NTES_GOD_IMAGES': 0.54, # 网易大神APP图片
|
||
'NTES_GOD_VIDEOS': 3.8, # 网易大神APP视频
|
||
'NTES_GOD_CHAT_IMAGES': 0.54, # 网易大神APP聊天图片
|
||
'NTES_GOD_CHAT_VIDEOS': 3.8, # 网易大神APP聊天视频
|
||
'NTES_DASONG': 139.19, # 大神大宋视频
|
||
'SPIDER_VIDEO': 3.8, # 大神普通供给视频
|
||
'SPIDER_VIDEO_SP': 13.3, # 大神高优供给视频
|
||
'NTES_GOD_AI': 0.54, # 大神AI图片
|
||
'NTES_GOD_TOP': 3.8, # 大神短视频
|
||
'T_SPIDER_VIDEO': 3.8, # 大神tiktok普通视频
|
||
'T_SPIDER_VIDEO_SP': 13.3, # 大神tiktok高优视频
|
||
'V_SPIDER_VIDEO': 3.8, # 大神ins普通供给视频
|
||
'V_SPIDER_VIDEO_SP': 13.3, # 大神ins高优供给视频
|
||
'NTES_GOD_XHS': 0.54, # 大神小红书图片
|
||
'XHS_SPIDER_VIDEO': 3.8, # 小红书供给视频
|
||
'Cupid': 0.54, # 大神交友
|
||
'CHAT_P2P': 0.55, # 大神聊天/风险用户_私聊/私聊频繁
|
||
'CHAT_TEAM': 0.55, # 大神聊天/风险用户_群聊/群聊频繁
|
||
'CHAT_ROOM': 0.55, # 大神聊天_聊天室
|
||
'CHAT_ROOM_MSG': 0.55 # 风险用户_聊天室频繁
|
||
}
|
||
|
||
# 系数配置文件路径
|
||
COEFFICIENTS_CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_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"当前使用的系数已更新")
|
||
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"系数配置已保存")
|
||
except Exception as e:
|
||
log(f"保存系数配置失败: {str(e)}")
|
||
|
||
# 初始化用户凭据(从环境变量读取)
|
||
def init_credentials():
|
||
"""从环境变量初始化用户凭据"""
|
||
try:
|
||
cookie = os.environ.get('BREEZE_COOKIE', '')
|
||
username = os.environ.get('BREEZE_USERNAME', '')
|
||
|
||
if cookie and username:
|
||
with credentials_lock:
|
||
user_credentials['cookie'] = cookie
|
||
user_credentials['username'] = username
|
||
log(f"已从环境变量加载用户凭据: {username}")
|
||
return True
|
||
else:
|
||
log(f"未能从环境变量获取用户凭据,BREEZE_COOKIE长度: {len(cookie)}, BREEZE_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/plain, */*',
|
||
'accept-language': 'zh-CN,zh;q=0.9',
|
||
'cookie': cookie,
|
||
'priority': 'u=1, i',
|
||
'referer': 'https://breeze.opd.netease.com/',
|
||
'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-site',
|
||
'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'
|
||
}
|
||
|
||
def log(message, level='info'):
|
||
"""记录日志"""
|
||
try:
|
||
if level == 'warning':
|
||
logging.warning(f"[Breeze] {message}")
|
||
elif level == 'error':
|
||
logging.error(f"[Breeze] {message}")
|
||
else:
|
||
logging.info(f"[Breeze] {message}")
|
||
except Exception as e:
|
||
print(f"日志记录失败: {str(e)}")
|
||
print(f"原始消息: {message}")
|
||
|
||
def is_image_url(url):
|
||
"""判断URL是否指向图片"""
|
||
if not url:
|
||
return False
|
||
|
||
# 图片文件格式检查
|
||
image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.heif', '.heic']
|
||
if any(ext in url.lower() for ext in image_extensions):
|
||
return True
|
||
|
||
# 图片服务链接检查
|
||
image_patterns = [
|
||
'fp.ps.netease.com',
|
||
'cc.fp.ps.netease.com',
|
||
'nos.netease.com',
|
||
'imageView' # 特殊参数标记为图片
|
||
]
|
||
|
||
return any(pattern in url.lower() for pattern in image_patterns)
|
||
|
||
def is_video_url(url):
|
||
"""判断URL是否指向视频"""
|
||
if not url:
|
||
return False
|
||
|
||
# 视频文件格式检查
|
||
video_extensions = ['.mp4', '.avi', '.mov', '.wmv', '.mkv', '.flv', '.webm', '.m4v']
|
||
if any(ext in url.lower() for ext in video_extensions):
|
||
return True
|
||
|
||
# 视频服务链接检查
|
||
video_patterns = [
|
||
'vod.cc.163.com',
|
||
'my.fp.ps.netease.com',
|
||
'vframe=1' # 特殊参数标记为视频
|
||
]
|
||
|
||
return any(pattern in url.lower() for pattern in video_patterns)
|
||
|
||
def determine_media_type(issue):
|
||
"""判断工单媒体类型"""
|
||
try:
|
||
# 获取必要字段
|
||
title = issue.get('title', '')
|
||
product_code = issue.get('product_code', '')
|
||
uniqueid = issue.get('uniqueid', '')
|
||
url = issue.get('url', '')
|
||
msg_type = issue.get('msg_type', '')
|
||
|
||
# 检查标题关键词
|
||
if '图片' in title:
|
||
return product_code + "_IMAGES"
|
||
elif '视频' in title:
|
||
return product_code + "_VIDEOS"
|
||
elif 'AI' in title:
|
||
return product_code + "_AI_IMAGES"
|
||
elif '短视频' in title:
|
||
return product_code + "_SHORT_VIDEOS"
|
||
elif '抖音' in title:
|
||
return product_code + "_TIKTOK_VIDEOS"
|
||
elif 'INS' in title:
|
||
return product_code + "_INS_VIDEOS"
|
||
elif '小红书' in title:
|
||
return product_code + "_XIAOHONGSHU_VIDEOS"
|
||
|
||
# 检查msg_type
|
||
if msg_type == 'P2P':
|
||
return 'CHAT_P2P'
|
||
elif msg_type == 'TEAM':
|
||
return 'CHAT_TEAM'
|
||
elif msg_type == 'ROOM':
|
||
return 'CHAT_ROOM'
|
||
elif msg_type == 'CHAT_ROOM_MSG':
|
||
return 'CHAT_ROOM_MSG'
|
||
|
||
# 检查URL
|
||
if url and 'nos.netease.com' in url:
|
||
if 'dasong' in url:
|
||
return product_code + "_DASONG_VIDEOS"
|
||
elif 'audiozhurong' in url:
|
||
return product_code + "_SUPPLY_VIDEOS"
|
||
elif 'high' in url:
|
||
return product_code + "_HIGH_VIDEOS"
|
||
elif 'ai' in url:
|
||
return product_code + "_AI_IMAGES"
|
||
elif 'shortvideo' in url:
|
||
return product_code + "_SHORT_VIDEOS"
|
||
elif 'tiktok' in url:
|
||
return product_code + "_TIKTOK_VIDEOS"
|
||
elif 'ins' in url:
|
||
return product_code + "_INS_VIDEOS"
|
||
elif 'xiaohongshu' in url:
|
||
return product_code + "_XIAOHONGSHU_VIDEOS"
|
||
else:
|
||
return product_code + "_IMAGES"
|
||
|
||
# 默认返回图片类型
|
||
return 'NTES_GOD_IMAGES'
|
||
|
||
except Exception as e:
|
||
log(f"确定工单类型时出错: {str(e)}", level='error')
|
||
return 'NTES_GOD_IMAGES'
|
||
|
||
def get_coefficient(issue_data):
|
||
"""获取工单的折算系数"""
|
||
media_type = determine_media_type(issue_data)
|
||
|
||
# 确保使用最新的系数配置
|
||
with coefficients_lock:
|
||
current_coefficients = COEFFICIENTS.copy()
|
||
|
||
if media_type in current_coefficients:
|
||
return current_coefficients[media_type]
|
||
|
||
# 如果无法确定媒体类型,使用默认系数
|
||
log(f"Unknown media type: {media_type}, using default coefficient")
|
||
return current_coefficients['NTES_GOD_IMAGES']
|
||
|
||
def fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time=None, close_end_time=None, max_pages=10000):
|
||
"""从API获取工单数据"""
|
||
issues = []
|
||
headers = get_api_headers(cookie)
|
||
page = 1
|
||
|
||
stats = {
|
||
'total_count': 0,
|
||
'types': {}
|
||
}
|
||
|
||
# 如果未提供关闭时间,则使用创建时间
|
||
if close_start_time is None:
|
||
close_start_time = create_start_time
|
||
if close_end_time is None:
|
||
close_end_time = create_end_time
|
||
|
||
# 添加重试逻辑
|
||
max_retries = 5
|
||
retry_interval = 5
|
||
current_retry = 0
|
||
|
||
while page <= max_pages:
|
||
try:
|
||
params = {
|
||
'pageNum': page,
|
||
'pageSize': 500, # 每页500条
|
||
'createTimeStart': create_start_time,
|
||
'createTimeEnd': create_end_time,
|
||
'closeTimeStart': close_start_time,
|
||
'closeTimeEnd': close_end_time,
|
||
'gameCode': 'a19',
|
||
'handleUsername': username,
|
||
'cold': 'false',
|
||
'status': 'FINISH'
|
||
}
|
||
|
||
log(f"正在获取第 {page} 页数据,创建时间范围:{create_start_time} 至 {create_end_time}")
|
||
log(f"关闭时间范围:{close_start_time} 至 {close_end_time}")
|
||
|
||
# 发送请求
|
||
response = requests.get(API_BASE_URL, headers=headers, params=params, timeout=15)
|
||
|
||
# 检查响应状态
|
||
if response.status_code == 200:
|
||
try:
|
||
data = response.json()
|
||
|
||
# 检查API响应格式的逻辑
|
||
if data.get('code') == 200 and 'data' in data and 'records' in data['data']:
|
||
items = data['data']['records']
|
||
total = data['data'].get('total', 0)
|
||
log(f"API返回成功,找到 {len(items)} 条记录,总计 {total} 条")
|
||
|
||
# 如果返回0条记录,进行重试
|
||
if total == 0 and current_retry < max_retries - 1:
|
||
current_retry += 1
|
||
log(f"API返回0条记录,正在进行第{current_retry}次重试...")
|
||
time.sleep(retry_interval)
|
||
continue
|
||
elif total == 0 and current_retry >= max_retries - 1:
|
||
log(f"API返回0条记录,已达到最大重试次数({max_retries}次),停止重试")
|
||
return None
|
||
|
||
# 记录当前统计的工单总数占API返回总数的百分比
|
||
if total > 0 and stats['total_count'] + len(items) <= total:
|
||
progress = ((stats['total_count'] + len(items)) / total) * 100
|
||
log(f"当前进度: {progress:.2f}% ({stats['total_count'] + len(items)}/{total})")
|
||
elif data.get('code') != 200:
|
||
# 这是一个明确的失败响应
|
||
error_msg = data.get('msg', '未知错误')
|
||
log(f"API返回错误:{error_msg}")
|
||
log(f"API响应内容:{response.text[:500]}")
|
||
return None
|
||
else:
|
||
# 其他情况,记录响应并退出
|
||
log(f"未知的API响应格式:{response.text[:500]}")
|
||
return None
|
||
|
||
# 处理工单数据
|
||
if not items or len(items) == 0:
|
||
log("当前页没有数据,结束获取")
|
||
break
|
||
|
||
issues.extend(items)
|
||
|
||
# 更新当前页的统计信息
|
||
for item in items:
|
||
media_type = determine_media_type(item)
|
||
if media_type not in stats['types']:
|
||
stats['types'][media_type] = 0
|
||
stats['types'][media_type] += 1
|
||
|
||
stats['total_count'] += len(items)
|
||
log(f"第{page}页有 {len(items)} 条记录,累计处理 {stats['total_count']} 条")
|
||
|
||
# 检查是否还有下一页数据
|
||
total_pages = data['data'].get('pages', 1)
|
||
if page >= total_pages or len(items) < params['pageSize']:
|
||
log(f"已获取所有数据,共 {total_pages} 页,处理了 {stats['total_count']} 条记录")
|
||
break
|
||
|
||
except ValueError as e:
|
||
log(f"解析JSON数据失败: {str(e)}")
|
||
log(f"原始响应内容:{response.text[:500]}")
|
||
return None
|
||
else:
|
||
log(f"API请求失败: HTTP {response.status_code}")
|
||
log(f"响应内容: {response.text[:500]}")
|
||
return None
|
||
|
||
page += 1
|
||
|
||
except requests.exceptions.Timeout:
|
||
log("API请求超时")
|
||
return None
|
||
except requests.exceptions.ConnectionError:
|
||
log("网络连接错误,请检查网络连接")
|
||
return None
|
||
except Exception as e:
|
||
log(f"获取数据失败: {str(e)}")
|
||
return None
|
||
|
||
# 检查是否因为达到最大页数而停止
|
||
if page > max_pages:
|
||
log(f"达到最大页数限制({max_pages}页),停止获取。如需获取更多数据,请增加max_pages参数。")
|
||
|
||
# 确保每次都是从配置文件读取最新系数
|
||
load_coefficients()
|
||
|
||
# 使用全局系数变量
|
||
with coefficients_lock:
|
||
current_coefficients = COEFFICIENTS.copy()
|
||
|
||
# 计算各类型工单折算总数
|
||
weighted_total = 0
|
||
|
||
for media_type, count in stats['types'].items():
|
||
coefficient = current_coefficients.get(media_type, current_coefficients['NTES_GOD_IMAGES'])
|
||
weighted_count = count * coefficient
|
||
weighted_total += weighted_count
|
||
|
||
log(f"最终统计结果:工单总数 {stats['total_count']},折算总计 {weighted_total:.2f}")
|
||
|
||
# 将统计结果整理为前端需要的格式
|
||
frontend_stats = {
|
||
'total': stats['total_count'],
|
||
'weighted_total': weighted_total,
|
||
'categories': {},
|
||
'coefficients': current_coefficients # 添加系数到返回结果中
|
||
}
|
||
|
||
# 整理分类统计,确保所有可能的工单类型都包括在内
|
||
for media_type, coefficient in current_coefficients.items():
|
||
count = stats['types'].get(media_type, 0)
|
||
weighted_count = count * coefficient
|
||
|
||
# 使用对应的中文名称
|
||
if media_type == 'NTES_GOD_IMAGES':
|
||
type_name = "网易大神APP图片"
|
||
elif media_type == 'NTES_GOD_VIDEOS':
|
||
type_name = "网易大神APP视频"
|
||
elif media_type == 'NTES_GOD_CHAT_IMAGES':
|
||
type_name = "网易大神APP聊天图片"
|
||
elif media_type == 'NTES_GOD_CHAT_VIDEOS':
|
||
type_name = "网易大神APP聊天视频"
|
||
elif media_type == 'NTES_DASONG':
|
||
type_name = "大神大宋视频"
|
||
elif media_type == 'SPIDER_VIDEO':
|
||
type_name = "大神普通供给视频"
|
||
elif media_type == 'SPIDER_VIDEO_SP':
|
||
type_name = "大神高优供给视频"
|
||
elif media_type == 'NTES_GOD_AI':
|
||
type_name = "大神AI图片"
|
||
elif media_type == 'NTES_GOD_TOP':
|
||
type_name = "大神短视频"
|
||
elif media_type == 'T_SPIDER_VIDEO':
|
||
type_name = "大神tiktok普通视频"
|
||
elif media_type == 'T_SPIDER_VIDEO_SP':
|
||
type_name = "大神tiktok高优视频"
|
||
elif media_type == 'V_SPIDER_VIDEO':
|
||
type_name = "大神ins普通供给视频"
|
||
elif media_type == 'V_SPIDER_VIDEO_SP':
|
||
type_name = "大神ins高优供给视频"
|
||
elif media_type == 'NTES_GOD_XHS':
|
||
type_name = "大神小红书图片"
|
||
elif media_type == 'XHS_SPIDER_VIDEO':
|
||
type_name = "小红书供给视频"
|
||
elif media_type == 'Cupid':
|
||
type_name = "大神交友"
|
||
elif media_type == 'CHAT_P2P':
|
||
type_name = "大神聊天/风险用户_私聊/私聊频繁"
|
||
elif media_type == 'CHAT_TEAM':
|
||
type_name = "大神聊天/风险用户_群聊/群聊频繁"
|
||
elif media_type == 'CHAT_ROOM':
|
||
type_name = "大神聊天_聊天室"
|
||
elif media_type == 'CHAT_ROOM_MSG':
|
||
type_name = "风险用户_聊天室频繁"
|
||
else:
|
||
# 默认情况下仍然使用替换方式
|
||
type_name = media_type.replace('_', ' ').replace('NTES', '大神')
|
||
|
||
frontend_stats['categories'][type_name] = {
|
||
'count': count,
|
||
'weighted': weighted_count,
|
||
'coefficient': coefficient
|
||
}
|
||
|
||
return {
|
||
'stats': frontend_stats,
|
||
'issues': issues
|
||
}
|
||
|
||
def switch_business(cookie, business_id):
|
||
"""切换业务线"""
|
||
try:
|
||
url = 'https://breeze.gameyw.netease.com/api/cms/user/switchBusiness'
|
||
headers = {
|
||
'accept': 'application/json, text/plain, */*',
|
||
'accept-language': 'zh-CN,zh;q=0.9',
|
||
'content-type': 'application/json',
|
||
'cookie': cookie,
|
||
'origin': 'https://breeze.opd.netease.com',
|
||
'priority': 'u=1, i',
|
||
'referer': 'https://breeze.opd.netease.com/',
|
||
'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-site',
|
||
'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'
|
||
}
|
||
data = {"businessId": business_id}
|
||
|
||
log(f"正在切换业务线,目标业务ID: {business_id}")
|
||
response = requests.post(url, headers=headers, json=data, timeout=15)
|
||
|
||
if response.status_code == 200:
|
||
try:
|
||
result = response.json()
|
||
if result.get('code') == 200:
|
||
log(f"业务线切换成功: {business_id}")
|
||
time.sleep(1) # 等待切换完成
|
||
return True
|
||
else:
|
||
log(f"业务线切换失败: {result.get('msg', '未知错误')}")
|
||
return False
|
||
except ValueError as e:
|
||
log(f"解析业务线切换响应失败: {str(e)}")
|
||
return False
|
||
else:
|
||
log(f"业务线切换请求失败: HTTP {response.status_code}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
log(f"切换业务线时出错: {str(e)}")
|
||
return False
|
||
|
||
def check_current_hour_counts(cookie, username):
|
||
"""检查当前小时数据"""
|
||
try:
|
||
# 获取当前小时的开始和结束时间
|
||
now = datetime.now()
|
||
current_hour = now.hour
|
||
|
||
# 创建时间仍然使用全天
|
||
create_start_time = now.strftime('%Y-%m-%d') + " 00:00:00"
|
||
create_end_time = now.strftime('%Y-%m-%d') + " 23:59:59"
|
||
|
||
# 关闭时间使用当前小时
|
||
close_start_time = now.strftime('%Y-%m-%d %H') + ":00:00"
|
||
close_end_time = now.strftime('%Y-%m-%d %H') + ":59:59"
|
||
|
||
log(f"当前小时查询 - 创建时间范围: {create_start_time} 至 {create_end_time}")
|
||
log(f"当前小时查询 - 关闭时间范围: {close_start_time} 至 {close_end_time}")
|
||
|
||
# 首先切换到清风审核-大神审核(业务ID: 7)
|
||
log("正在切换到清风审核-大神审核...")
|
||
switch_business(cookie, 7)
|
||
|
||
# 调用API获取大神审核数据
|
||
log("正在获取大神审核数据...")
|
||
godResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000)
|
||
if godResult is None:
|
||
log("获取大神审核数据失败")
|
||
godStats = {
|
||
'total': 0,
|
||
'weighted_total': 0,
|
||
'categories': {}
|
||
}
|
||
else:
|
||
godStats = godResult['stats']
|
||
log(f"大神审核数据获取成功,共 {godStats['total']} 条记录,折算总计 {godStats['weighted_total']:.2f}")
|
||
|
||
# 然后切换到清风审核-图片审核(业务ID: 12)
|
||
log("正在切换到清风审核-图片审核...")
|
||
switch_business(cookie, 12)
|
||
|
||
# 调用API获取图片审核数据
|
||
log("正在获取图片审核数据...")
|
||
imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000)
|
||
if imageResult is None:
|
||
log("获取图片审核数据失败")
|
||
imageStats = {
|
||
'total': 0,
|
||
'weighted_total': 0,
|
||
'categories': {}
|
||
}
|
||
else:
|
||
imageStats = imageResult['stats']
|
||
log(f"图片审核数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}")
|
||
|
||
# 合并两部分统计结果
|
||
mergedStats = {
|
||
'total': godStats['total'] + imageStats['total'],
|
||
'weighted_total': godStats['weighted_total'] + imageStats['weighted_total'],
|
||
'categories': {}
|
||
}
|
||
|
||
# 合并分类统计
|
||
allCategories = set(list(godStats['categories'].keys()) + list(imageStats['categories'].keys()))
|
||
for category in allCategories:
|
||
godCat = godStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0})
|
||
imageCat = imageStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0})
|
||
|
||
# 使用相同的系数(两者应该是一样的,如果有就使用它)
|
||
coefficient = godCat['coefficient'] or imageCat['coefficient']
|
||
|
||
mergedStats['categories'][category] = {
|
||
'count': godCat['count'] + imageCat['count'],
|
||
'coefficient': coefficient,
|
||
'weighted': godCat['weighted'] + imageCat['weighted']
|
||
}
|
||
|
||
log(f"合并后的统计结果:工单总数 {mergedStats['total']},折算总计 {mergedStats['weighted_total']:.2f}")
|
||
|
||
# 将统计结果写入到共享数据文件中
|
||
try:
|
||
data = {
|
||
'type': 'breeze_hourly',
|
||
'stats': mergedStats,
|
||
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
}
|
||
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_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 mergedStats
|
||
|
||
except Exception as e:
|
||
log(f"检查当前小时数据失败: {str(e)}")
|
||
return None
|
||
|
||
def check_daily_counts(cookie, username):
|
||
"""检查全天数据"""
|
||
try:
|
||
# 获取今日开始和结束时间
|
||
today = datetime.now().strftime('%Y-%m-%d')
|
||
create_start_time = "%s 00:00:00" % today
|
||
create_end_time = "%s 23:59:59" % today
|
||
|
||
# 关闭时间也使用全天
|
||
close_start_time = create_start_time
|
||
close_end_time = create_end_time
|
||
|
||
log(f"全天查询 - 创建时间范围: {create_start_time} 至 {create_end_time}")
|
||
log(f"全天查询 - 关闭时间范围: {close_start_time} 至 {close_end_time}")
|
||
|
||
# 首先切换到清风审核-大神审核(业务ID: 7)
|
||
log("正在切换到清风审核-大神审核...")
|
||
switch_business(cookie, 7)
|
||
|
||
# 调用API获取大神审核数据
|
||
log("正在获取大神审核全天数据...")
|
||
godResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000)
|
||
if godResult is None:
|
||
log("获取大神审核全天数据失败")
|
||
godStats = {
|
||
'total': 0,
|
||
'weighted_total': 0,
|
||
'categories': {}
|
||
}
|
||
else:
|
||
godStats = godResult['stats']
|
||
log(f"大神审核全天数据获取成功,共 {godStats['total']} 条记录,折算总计 {godStats['weighted_total']:.2f}")
|
||
|
||
# 然后切换到清风审核-图片审核(业务ID: 12)
|
||
log("正在切换到清风审核-图片审核...")
|
||
switch_business(cookie, 12)
|
||
|
||
# 调用API获取图片审核数据
|
||
log("正在获取图片审核全天数据...")
|
||
imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000)
|
||
if imageResult is None:
|
||
log("获取图片审核全天数据失败")
|
||
imageStats = {
|
||
'total': 0,
|
||
'weighted_total': 0,
|
||
'categories': {}
|
||
}
|
||
else:
|
||
imageStats = imageResult['stats']
|
||
log(f"图片审核全天数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}")
|
||
|
||
# 合并两部分统计结果
|
||
mergedStats = {
|
||
'total': godStats['total'] + imageStats['total'],
|
||
'weighted_total': godStats['weighted_total'] + imageStats['weighted_total'],
|
||
'categories': {}
|
||
}
|
||
|
||
# 合并分类统计
|
||
allCategories = set(list(godStats['categories'].keys()) + list(imageStats['categories'].keys()))
|
||
for category in allCategories:
|
||
godCat = godStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0})
|
||
imageCat = imageStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0})
|
||
|
||
# 使用相同的系数(两者应该是一样的,如果有就使用它)
|
||
coefficient = godCat['coefficient'] or imageCat['coefficient']
|
||
|
||
mergedStats['categories'][category] = {
|
||
'count': godCat['count'] + imageCat['count'],
|
||
'coefficient': coefficient,
|
||
'weighted': godCat['weighted'] + imageCat['weighted']
|
||
}
|
||
|
||
log(f"合并后的全天统计结果:工单总数 {mergedStats['total']},折算总计 {mergedStats['weighted_total']:.2f}")
|
||
|
||
# 将统计结果写入到共享数据文件中
|
||
try:
|
||
data = {
|
||
'type': 'breeze_daily',
|
||
'stats': mergedStats,
|
||
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
}
|
||
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_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 mergedStats
|
||
|
||
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:
|
||
# 检查当前小时数据
|
||
stats = 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:
|
||
# 检查全天数据
|
||
stats = 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("Breeze监控系统启动")
|
||
|
||
# 解析命令行参数
|
||
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 + 类型 + 值
|
||
try:
|
||
coefficient_type = sys.argv[2]
|
||
coefficient_value = float(sys.argv[3])
|
||
|
||
# 检查是否为有效的系数类型
|
||
if coefficient_type in COEFFICIENTS:
|
||
log(f"更新系数:{coefficient_type}={coefficient_value}")
|
||
|
||
# 更新全局系数
|
||
with coefficients_lock:
|
||
COEFFICIENTS[coefficient_type] = coefficient_value
|
||
|
||
# 保存到配置文件
|
||
save_coefficients()
|
||
else:
|
||
log(f"未知的系数类型: {coefficient_type}")
|
||
except ValueError as e:
|
||
log(f"系数更新失败: {str(e)}")
|
||
log("系数必须是有效的数字")
|
||
except IndexError:
|
||
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()
|
||
|
||
# 创建时间仍然使用全天
|
||
create_start_time = now.strftime('%Y-%m-%d') + " 00:00:00"
|
||
create_end_time = now.strftime('%Y-%m-%d') + " 23:59:59"
|
||
|
||
# 关闭时间使用当前小时的整点范围
|
||
close_start_time = now.strftime('%Y-%m-%d %H') + ":00:00"
|
||
close_end_time = now.strftime('%Y-%m-%d %H') + ":59:59"
|
||
|
||
log(f"强制模式 - 创建时间范围: {create_start_time} 至 {create_end_time}")
|
||
log(f"强制模式 - 关闭时间范围: {close_start_time} 至 {close_end_time}")
|
||
|
||
# 首先切换到清风审核-大神审核(业务ID: 7)
|
||
log("正在切换到清风审核-大神审核...")
|
||
switch_business(cookie, 7)
|
||
|
||
# 调用API获取大神审核数据
|
||
log("正在获取大神审核数据...")
|
||
godResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000)
|
||
if godResult is None:
|
||
log("获取大神审核数据失败")
|
||
godStats = {
|
||
'total': 0,
|
||
'weighted_total': 0,
|
||
'categories': {}
|
||
}
|
||
else:
|
||
godStats = godResult['stats']
|
||
log(f"大神审核数据获取成功,共 {godStats['total']} 条记录,折算总计 {godStats['weighted_total']:.2f}")
|
||
|
||
# 然后切换到清风审核-图片审核(业务ID: 12)
|
||
log("正在切换到清风审核-图片审核...")
|
||
switch_business(cookie, 12)
|
||
|
||
# 调用API获取图片审核数据
|
||
log("正在获取图片审核数据...")
|
||
imageResult = fetch_issue_data(cookie, username, create_start_time, create_end_time, close_start_time, close_end_time, max_pages=10000)
|
||
if imageResult is None:
|
||
log("获取图片审核数据失败")
|
||
imageStats = {
|
||
'total': 0,
|
||
'weighted_total': 0,
|
||
'categories': {}
|
||
}
|
||
else:
|
||
imageStats = imageResult['stats']
|
||
log(f"图片审核数据获取成功,共 {imageStats['total']} 条记录,折算总计 {imageStats['weighted_total']:.2f}")
|
||
|
||
# 合并两部分统计结果
|
||
mergedStats = {
|
||
'total': godStats['total'] + imageStats['total'],
|
||
'weighted_total': godStats['weighted_total'] + imageStats['weighted_total'],
|
||
'categories': {}
|
||
}
|
||
|
||
# 合并分类统计
|
||
allCategories = set(list(godStats['categories'].keys()) + list(imageStats['categories'].keys()))
|
||
for category in allCategories:
|
||
godCat = godStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0})
|
||
imageCat = imageStats['categories'].get(category, {'count': 0, 'coefficient': 0, 'weighted': 0})
|
||
|
||
# 使用相同的系数(两者应该是一样的,如果有就使用它)
|
||
coefficient = godCat['coefficient'] or imageCat['coefficient']
|
||
|
||
mergedStats['categories'][category] = {
|
||
'count': godCat['count'] + imageCat['count'],
|
||
'coefficient': coefficient,
|
||
'weighted': godCat['weighted'] + imageCat['weighted']
|
||
}
|
||
|
||
log(f"合并后的统计结果:工单总数 {mergedStats['total']},折算总计 {mergedStats['weighted_total']:.2f}")
|
||
|
||
# 将统计结果写入到共享数据文件中
|
||
try:
|
||
data = {
|
||
'type': 'breeze_hourly',
|
||
'stats': mergedStats,
|
||
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
}
|
||
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'breeze_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("Breeze监控系统关闭")
|
||
|
||
if __name__ == '__main__':
|
||
main() |