NetEaseDSMonitor/dashboard.py

1737 lines
67 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.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import json
import time
import subprocess
import threading
import sys
from datetime import datetime, timedelta
import logging
import webbrowser
from flask import Flask, render_template, request, jsonify, session, redirect, url_for, make_response, send_from_directory, flash
import threading
from threading import Lock
import signal
import socket
import requests
import re
from flask_socketio import SocketIO # 添加WebSocket支持
import hashlib
# 全局应用状态变量
shutdown_flag = False # 控制应用程序关闭的标志
# 初始化 SocketIO
socketio = SocketIO()
# 常量定义:警报阈值和系统版本
ALARM_THRESHOLD = 2750
# 系统版本固定在代码中
VERSION = "v20250412034434-dev" # 系统版本号 - 这是固定的本地版本
# 获取或创建版本文件,格式为"当前时间(v版本号)",例如"20250408125815(v1.1)"。
def ensure_version_file():
"""确保版本文件存在,写入固定版本号"""
try:
version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'VERSION.txt')
# 使用固定版本号
version_content = VERSION
# 写入版本文件
with open(version_file, 'w') as f:
f.write(version_content)
logging.info(f"更新版本文件: {version_content}")
return version_content
except Exception as e:
logging.error(f"处理版本文件时出错: {e}")
return VERSION # 返回固定版本号
# 获取线上版本进行比较
def get_online_version():
"""获取线上版本信息"""
try:
log(f"正在从 {VERSION_CHECK_URL} 获取线上版本信息...")
# 获取系统代理设置
proxies = {
'http': os.environ.get('HTTP_PROXY', ''),
'https': os.environ.get('HTTPS_PROXY', '')
}
# 如果没有代理设置,尝试不使用代理直接访问
if not any(proxies.values()):
log("未检测到系统代理设置,尝试直接连接...")
response = requests.get(VERSION_CHECK_URL, timeout=5, verify=False)
else:
log(f"使用系统代理设置: {proxies}")
response = requests.get(VERSION_CHECK_URL, timeout=5, proxies=proxies, verify=False)
log(f"获取线上版本响应状态码: {response.status_code}")
if response.status_code == 200:
version = response.text.strip()
log(f"成功获取线上版本: {version}")
return version
elif response.status_code == 407:
log("需要代理认证,尝试不使用代理直接访问...")
# 尝试不使用代理直接访问
response = requests.get(VERSION_CHECK_URL, timeout=5, proxies={'http': None, 'https': None}, verify=False)
if response.status_code == 200:
version = response.text.strip()
log(f"直接访问成功获取线上版本: {version}")
return version
else:
log(f"直接访问失败HTTP状态码: {response.status_code}")
return None
else:
log(f"获取线上版本失败HTTP状态码: {response.status_code}")
return None
except requests.exceptions.Timeout:
log("获取线上版本超时")
return None
except requests.exceptions.ConnectionError:
log("连接线上版本服务器失败,尝试不使用代理直接访问...")
try:
# 尝试不使用代理直接访问
response = requests.get(VERSION_CHECK_URL, timeout=5, proxies={'http': None, 'https': None}, verify=False)
if response.status_code == 200:
version = response.text.strip()
log(f"直接访问成功获取线上版本: {version}")
return version
else:
log(f"直接访问失败HTTP状态码: {response.status_code}")
return None
except Exception as e:
log(f"直接访问失败: {str(e)}")
return None
except Exception as e:
log(f"获取线上版本时发生未知错误: {str(e)}")
return None
VERSION_CHECK_URL = "http://cos.ui-beam.com/work_scripts/monitor/dev/VERSION.txt" # 测试版本地址
# VERSION_CHECK_URL = "http://cos.ui-beam.com/work_scripts/monitor/releases/VERSION.txt" # 正式版本地址
version_status = {
'last_check_time': None,
'online_version': None,
'has_update': False
}
version_lock = Lock()
# 版本检测线程标志
version_check_thread_running = False
# 检查并安装依赖
def check_and_install_dependencies():
try:
# 检查plyer库是否安装
try:
import plyer
except ImportError:
log("正在安装plyer库...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "plyer"])
log("plyer库安装成功")
except Exception as e:
log(f"依赖检查/安装过程中出错: {str(e)}")
# 配置日志
def setup_logging():
"""设置日志系统"""
try:
log_file = os.path.join('logs', f'dashboard_{datetime.now().strftime("%Y%m%d")}.log')
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.FileHandler(log_file, encoding='utf-8'),
logging.StreamHandler()
]
)
return True
except Exception as e:
print(f"设置日志系统失败: {str(e)}")
return False
# 禁用 Flask 的访问日志
logging.getLogger('werkzeug').setLevel(logging.ERROR)
# 创建Flask应用
app = Flask(__name__)
app.secret_key = 'netease-dashboard-secret-key' # 用于session加密
app.permanent_session_lifetime = timedelta(days=30) # 设置会话有效期为30天
socketio.init_app(app) # 初始化 SocketIO
# 告警阈值
ALARM_THRESHOLD = 1900 # 两个系统折算总和的告警阈值
alarm_status = {
'last_alarm_time': None,
'alarm_count': 0,
'is_alarming': False,
'alarm_type': None
}
alarm_lock = Lock()
# 进程对象
processes = {
'breeze': None,
'cms': None
}
def log(message):
"""记录日志"""
try:
logging.info(message)
except Exception as e:
print("Log error: " + str(e))
print("Original message: " + message)
def start_monitor_processes():
"""启动监控进程"""
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
# 获取会话中的凭据
breeze_cookie = session.get('breeze_cookie', '')
cms_cookie = session.get('cms_cookie', '')
username = session.get('username', '')
# 获取Python可执行文件路径
python_executable = sys.executable
# 启动breeze监控
if processes['breeze'] is None or processes['breeze'].poll() is not None:
breeze_script = os.path.join(script_dir, "breeze_monitor.py")
# 在命令行参数中传递环境变量
cmd = [python_executable, breeze_script]
env = os.environ.copy()
env['BREEZE_COOKIE'] = breeze_cookie
env['BREEZE_USERNAME'] = username
processes['breeze'] = subprocess.Popen(
cmd,
env=env,
shell=False
)
log(f"Breeze监控进程已启动, PID: {processes['breeze'].pid}")
# 启动cms监控
if processes['cms'] is None or processes['cms'].poll() is not None:
cms_script = os.path.join(script_dir, "cms_monitor.py")
# 在命令行参数中传递环境变量
cmd = [python_executable, cms_script]
env = os.environ.copy()
env['CMS_COOKIE'] = cms_cookie
env['CMS_USERNAME'] = username
processes['cms'] = subprocess.Popen(
cmd,
env=env,
shell=False
)
log(f"CMS监控进程已启动, PID: {processes['cms'].pid}")
return True
except Exception as e:
log(f"启动监控进程失败: {str(e)}")
return False
def add_no_cache_headers(response):
"""添加禁止缓存的响应头"""
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return response
@app.after_request
def after_request(response):
"""每个响应添加禁止缓存的头部"""
return add_no_cache_headers(response)
@app.route('/favicon.ico')
def favicon():
"""提供网站图标"""
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
static_path = os.path.join(script_dir, 'static')
return send_from_directory(static_path, 'dashboard-favicon.ico', mimetype='image/vnd.microsoft.icon')
except Exception as e:
log("Failed to serve favicon: " + str(e))
return '', 204
@app.route('/')
def index():
"""首页路由"""
if not session.get('logged_in'):
return redirect(url_for('login'))
staff_name = session.get('staff_name', '')
staff_id = session.get('username', '') # 获取工号
# 添加登录成功的提示消息
message = None
if session.get('just_logged_in'):
# 不再在这里设置消息,而是在前端处理
session['just_logged_in'] = False # 清除标记,确保消息只显示一次
return render_template('dashboard.html',
staff_name=f"{staff_name}{staff_id}", # 修改显示格式,添加工号
message=message,
version=VERSION)
def get_staff_name(staff_id):
"""根据工号获取员工姓名"""
try:
# 从在线链接获取staff.json
url = "http://cos.ui-beam.com/work_scripts/monitor/config/staff.json"
# 尝试不使用代理直接获取
try:
response = requests.get(url, timeout=5, proxies={'http': None, 'https': None}, verify=False)
except:
# 如果直接获取失败,尝试使用系统代理
proxies = {
'http': os.environ.get('HTTP_PROXY', ''),
'https': os.environ.get('HTTPS_PROXY', '')
}
response = requests.get(url, timeout=5, proxies=proxies, verify=False)
if response.status_code == 200:
staff_data = response.json()
return staff_data.get(staff_id)
else:
log(f"获取在线staff.json失败HTTP状态码: {response.status_code}")
return None
except Exception as e:
log(f"获取员工姓名失败: {str(e)}")
return None
@app.route('/login', methods=['GET', 'POST'])
def login():
"""登录路由"""
if request.method == 'POST':
staff_id = request.form.get('username', '').strip() # 从username字段获取工号
breeze_cookie = request.form.get('breeze_cookie', '').strip() # 获取Breeze Cookie
cms_cookie = request.form.get('cms_cookie', '').strip() # 获取CMS Cookie
staff_name = get_staff_name(staff_id)
if staff_name:
session['logged_in'] = True
session['staff_name'] = staff_name
session['username'] = staff_id # 保存工号
session['breeze_cookie'] = breeze_cookie # 保存Breeze Cookie
session['cms_cookie'] = cms_cookie # 保存CMS Cookie
session['just_logged_in'] = True # 添加标记,用于显示欢迎消息
# 添加详细的系统启动日志
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(f"\n{current_time} - INFO - Breeze监控日志系统初始化成功")
print(f"{current_time} - INFO - CMS监控日志系统初始化成功")
print(f"{current_time} - INFO - 监控服务启动")
print(f"{current_time} - INFO - 初始化监控服务...")
print(f"{current_time} - INFO - 初始化用户凭据...")
print(f"{current_time} - INFO - [Breeze] 已从环境变量加载用户凭据: {staff_id}")
print(f"{current_time} - INFO - [CMS] 已从环境变量加载用户凭据: {staff_id}")
print(f"{current_time} - INFO - 加载系数配置...")
# 加载并显示 Breeze 系数
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
breeze_coefficients_file = os.path.join(script_dir, 'breeze_coefficients.json')
if os.path.exists(breeze_coefficients_file):
with open(breeze_coefficients_file, 'r', encoding='utf-8') as f:
breeze_coefficients = json.load(f)
print(f"{current_time} - INFO - [Breeze] 从配置文件加载系数: {breeze_coefficients}")
print(f"{current_time} - INFO - [Breeze] 当前使用的系数已更新")
except Exception as e:
print(f"{current_time} - ERROR - [Breeze] 加载系数配置失败: {str(e)}")
# 加载并显示 CMS 系数
try:
cms_coefficients_file = os.path.join(script_dir, 'cms_coefficients.json')
if os.path.exists(cms_coefficients_file):
with open(cms_coefficients_file, 'r', encoding='utf-8') as f:
cms_coefficients = json.load(f)
print(f"{current_time} - INFO - [CMS] 从配置文件加载系数: {cms_coefficients}")
print(f"{current_time} - INFO - [CMS] 当前使用的系数: 评论={cms_coefficients['comment']}, 动态={cms_coefficients['feed']}, 举报={cms_coefficients['complaint']}")
except Exception as e:
print(f"{current_time} - ERROR - [CMS] 加载系数配置失败: {str(e)}")
# 启动监控进程
start_monitor_processes()
print(f"{current_time} - INFO - Breeze监控线程启动")
print(f"{current_time} - INFO - CMS监控线程启动")
# 返回成功响应,让前端显示欢迎消息
return jsonify({
'success': True,
'staff_name': staff_name,
'redirect': url_for('index')
})
else:
return jsonify({
'success': False,
'message': f'您的账号 ({staff_id}) 尚未在系统上注册或为不可用状态,无法登录,请联系系统管理员 (od0269@mail.go.com) 协助'
})
return render_template('login.html')
@app.route('/logout')
def logout():
"""登出"""
try:
# 记录登出用户
username = session.get('username', '未知用户')
log("用户 [" + username + "] 登出系统")
# 清除会话
session.clear()
# 标记系统即将退出
global shutdown_flag
shutdown_flag = True
# 尝试停止监控进程
if processes['breeze'] and processes['breeze'].poll() is None:
try:
processes['breeze'].terminate()
log("已停止Breeze监控进程")
except Exception as e:
log("停止Breeze监控进程失败: " + str(e))
if processes['cms'] and processes['cms'].poll() is None:
try:
processes['cms'].terminate()
log("已停止CMS监控进程")
except Exception as e:
log("停止CMS监控进程失败: " + str(e))
# 删除共享数据文件
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
data_files = ['breeze_daily.json', 'breeze_hourly.json', 'cms_daily.json', 'cms_hourly.json']
for file_name in data_files:
file_path = os.path.join(script_dir, file_name)
if os.path.exists(file_path):
os.remove(file_path)
log(f"已删除共享数据文件: {file_name}")
log("成功清理所有共享数据文件")
except Exception as e:
log(f"删除共享数据文件时出错: {str(e)}")
# 在一个后台线程中等待几秒后退出应用程序
def shutdown_app():
time.sleep(3) # 等待3秒
log("用户登出,系统正在完全关闭...")
# 关闭所有子进程和当前进程
if sys.platform.startswith('win'):
# 在Windows系统下强制关闭当前进程及所有子进程
try:
# 获取当前进程PID
current_pid = os.getpid()
# 使用taskkill命令强制结束进程树
subprocess.run(f'taskkill /F /T /PID {current_pid}', shell=True)
except Exception as e:
log(f"关闭进程时出错: {str(e)}")
else:
# 非Windows系统使用标准退出方式
os._exit(0) # 强制关闭整个程序
# 启动关闭线程
threading.Thread(target=shutdown_app).start()
# 返回包含关闭倒计时的页面
return """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>正在关闭系统...</title>
<style>
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background-color: #f5f5f5;
text-align: center;
padding-top: 100px;
}
.logout-message {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 30px;
max-width: 500px;
margin: 0 auto;
}
h2 {
color: #1890ff;
margin-top: 0;
}
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
width: 36px;
height: 36px;
border-radius: 50%;
border-left-color: #1890ff;
animation: spin 1s linear infinite;
margin: 20px auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.countdown {
font-size: 18px;
font-weight: bold;
color: #1890ff;
margin-top: 15px;
}
</style>
</head>
<body>
<div class="logout-message">
<h2>正在关闭系统</h2>
<p>数据清理完毕,系统即将退出,请手动关闭浏览器页面和控制台</p>
<div class="spinner"></div>
<div class="countdown" id="countdown">3</div>
</div>
<script>
// 显示倒计时
var seconds = 3;
var countdownElement = document.getElementById('countdown');
var interval = setInterval(function() {
seconds--;
countdownElement.textContent = seconds;
if (seconds <= 0) {
clearInterval(interval);
window.close(); // 尝试关闭窗口
}
}, 1000);
</script>
</body>
</html>
"""
except Exception as e:
log("Error in logout route: " + str(e))
return "Logout error. Please check logs.", 500
def read_data_file(filename):
"""读取数据文件"""
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(script_dir, filename)
if not os.path.exists(file_path):
return None
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
except Exception as e:
log("Failed to read file {0}: {1}".format(filename, str(e)))
return None
def check_version():
"""检查系统版本并与在线版本比较"""
global version_check_thread_running
try:
log("开始检查系统版本更新...")
with version_lock:
version_status['last_check_time'] = datetime.now()
# 确保VERSION.txt文件存在且内容与VERSION一致
local_version = ensure_version_file()
log(f"当前本地版本: {local_version}")
# 获取在线版本
online_version = get_online_version()
log(f"获取到的在线版本: {online_version}")
if online_version:
with version_lock:
version_status['online_version'] = online_version
# 比较版本号,只在线上版本比本地版本新时才提示更新
local_timestamp = int(local_version[1:].split('-')[0]) # 去掉'v'前缀并转换为数字
online_timestamp = int(online_version[1:].split('-')[0]) # 去掉'v'前缀并转换为数字
version_status['has_update'] = online_timestamp > local_timestamp
log(f"版本比较结果 - 在线版本: {online_version}, 本地版本: {local_version}, 是否有更新: {version_status['has_update']}")
# 如果有更新且没有正在运行的线程,启动告警线程
if version_status['has_update'] and not version_check_thread_running:
version_check_thread_running = True
threading.Thread(target=notify_version_update).start()
return {
'current_version': VERSION,
'online_version': online_version,
'has_update': version_status['has_update'],
'last_check_time': version_status['last_check_time'].strftime('%Y-%m-%d %H:%M:%S') if version_status['last_check_time'] else None
}
else:
log("无法获取在线版本,返回本地版本信息")
return {
'current_version': VERSION,
'online_version': None,
'has_update': False,
'last_check_time': version_status['last_check_time'].strftime('%Y-%m-%d %H:%M:%S') if version_status['last_check_time'] else None
}
except Exception as e:
log(f"版本检查过程中发生错误: {str(e)}")
return {
'current_version': VERSION,
'online_version': None,
'has_update': False,
'last_check_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'error': str(e)
}
finally:
# 确保线程标志被重置
if version_check_thread_running:
version_check_thread_running = False
def notify_version_update():
"""通知用户有新版本可用"""
try:
# 确保VERSION.txt文件存在
ensure_version_file()
with version_lock:
online_version = version_status['online_version']
if online_version:
message = f"系统有新版本可用: {online_version},当前版本: {VERSION}"
log(message)
try:
# 发送桌面通知
from plyer import notification
notification.notify(
title="系统版本更新",
message=message + "\n点击'检测更新'按钮了解详情",
app_icon=None,
timeout=10,
toast=False
)
# 通过WebSocket通知前端弹出版本检测窗口
socketio.emit('version_update', {
'type': 'show_version_dialog',
'current_version': VERSION,
'online_version': online_version
})
except Exception as e:
log(f"显示桌面通知失败: {str(e)}")
except Exception as e:
log(f"通知版本更新失败: {str(e)}")
finally:
# 重置线程运行标志
global version_check_thread_running
version_check_thread_running = False
def monitor_version_thread():
"""版本检测后台线程"""
log("版本监控线程启动")
while True:
try:
# 每30分钟检查一次版本更新
check_version()
time.sleep(1800) # 30分钟
except Exception as e:
log(f"版本监控线程异常: {str(e)}")
time.sleep(600) # 出错后等待10分钟重试
@app.route('/api/get-version')
def get_version():
"""获取系统版本信息"""
try:
# 确保VERSION.txt文件存在
ensure_version_file()
with version_lock:
status = {
'current_version': VERSION,
'local_version': VERSION, # 固定的本地版本号
'online_version': version_status['online_version'],
'has_update': version_status['has_update'],
'last_check_time': version_status['last_check_time'].strftime('%Y-%m-%d %H:%M:%S') if version_status['last_check_time'] else None
}
return jsonify({
'success': True,
'data': status
})
except Exception as e:
log("Error in get_version route: " + str(e))
return jsonify({'success': False, 'message': 'Internal server error'}), 500
@app.route('/api/check-version')
def check_version_api():
"""手动检查版本更新的API"""
try:
result = check_version()
if result:
return jsonify({
'success': True,
'data': result
})
else:
# 至少返回当前版本信息
return jsonify({
'success': False,
'message': '获取在线版本信息失败',
'data': {
'current_version': VERSION,
'local_version': VERSION,
'online_version': None,
'has_update': False,
'last_check_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
})
except Exception as e:
log("Error in check_version_api route: " + str(e))
return jsonify({'success': False, 'message': f'检测版本更新失败: {str(e)}'}), 500
@app.route('/api/get-stats')
def get_stats():
"""获取统计数据"""
try:
breeze_hourly = read_data_file('breeze_hourly.json')
breeze_daily = read_data_file('breeze_daily.json')
cms_hourly = read_data_file('cms_hourly.json')
cms_daily = read_data_file('cms_daily.json')
# 获取时间戳
breeze_hourly_time = datetime.strptime(breeze_hourly['timestamp'], '%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') if breeze_hourly else ''
breeze_daily_time = datetime.strptime(breeze_daily['timestamp'], '%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') if breeze_daily else ''
cms_hourly_time = datetime.strptime(cms_hourly['timestamp'], '%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') if cms_hourly else ''
cms_daily_time = datetime.strptime(cms_daily['timestamp'], '%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') if cms_daily else ''
# 构建返回数据
data = {
'breeze_hourly': {
'total': breeze_hourly['stats']['total'] if breeze_hourly else 0,
'weighted_total': round(breeze_hourly['stats']['weighted_total'], 2) if breeze_hourly else 0,
'categories': breeze_hourly['stats']['categories'] if breeze_hourly else {},
'timestamp': breeze_hourly_time
},
'breeze_daily': {
'total': breeze_daily['stats']['total'] if breeze_daily else 0,
'weighted_total': round(breeze_daily['stats']['weighted_total'], 2) if breeze_daily else 0,
'categories': breeze_daily['stats']['categories'] if breeze_daily else {},
'timestamp': breeze_daily_time
},
'cms_hourly': {
'stats': cms_hourly['stats']['stats'] if cms_hourly else {'comment': 0, 'feed': 0, 'complaint': 0},
'weighted_total': round(cms_hourly['stats']['weighted_total'], 2) if cms_hourly else 0,
'total_count': cms_hourly['stats']['total_count'] if cms_hourly else 0,
'coefficients': cms_hourly['stats']['coefficients'] if cms_hourly else {'comment': 0.55, 'feed': 1.54, 'complaint': 5.4},
'timestamp': cms_hourly_time
},
'cms_daily': {
'stats': cms_daily['stats']['stats'] if cms_daily else {'comment': 0, 'feed': 0, 'complaint': 0},
'weighted_total': round(cms_daily['stats']['weighted_total'], 2) if cms_daily else 0,
'total_count': cms_daily['stats']['total_count'] if cms_daily else 0,
'coefficients': cms_daily['stats']['coefficients'] if cms_daily else {'comment': 0.55, 'feed': 1.54, 'complaint': 5.4},
'timestamp': cms_daily_time
}
}
# 计算总折算量
total_weighted = (data['breeze_hourly']['weighted_total'] + data['cms_hourly']['weighted_total'])
return jsonify({
'code': 0,
'data': data,
'total_weighted': round(total_weighted, 2)
})
except Exception as e:
log(f"获取统计数据时发生错误: {str(e)}")
return jsonify({'code': 1, 'message': '获取统计数据失败'})
def check_alarm(total_weighted):
"""检查并触发告警"""
try:
with alarm_lock:
now = datetime.now()
# 如果最近30秒内已经告警过增加计数但不重复告警
if alarm_status['last_alarm_time'] and (now - alarm_status['last_alarm_time']).total_seconds() < 30:
return
# 设置告警状态
alarm_status['last_alarm_time'] = now
alarm_status['alarm_count'] += 1
alarm_status['is_alarming'] = True
# 如果已经达到3次告警不再继续
if alarm_status['alarm_count'] > 3:
return
# 启动线程播放告警声音和显示通知
# 将当前加权总值传递给函数
threading.Thread(target=lambda: show_alarm_notification(total_weighted)).start()
log("Alarm triggered: Current weighted total {0:.2f}, exceeded threshold {1}".format(total_weighted, ALARM_THRESHOLD))
except Exception as e:
log("Error in check_alarm: " + str(e))
def show_alarm_notification(total_weighted, is_test=False, alarm_type="实时告警"):
"""显示告警通知和播放告警声音"""
try:
# 播放系统告警声音
try:
import winsound
winsound.PlaySound("SystemExclamation", winsound.SND_ALIAS)
except:
# 如果winsound不可用尝试使用beep
print('\a')
# 添加桌面通知
try:
from plyer import notification
# 计算超出阈值的百分比
over_percentage = ((total_weighted - ALARM_THRESHOLD) / ALARM_THRESHOLD) * 100
# 根据是否为测试调整标题
title = "测试告警" if is_test else "审核数量告警"
# 根据是否为测试调整消息前缀
prefix = "[测试数据] " if is_test else ""
notification.notify(
title=title,
message=f"{prefix}{alarm_type}当前小时加权总计:{total_weighted:.2f}\n阈值:{ALARM_THRESHOLD}\n超出:{over_percentage:.1f}%\n请立即检查数据监控看板!",
app_icon=None,
timeout=15,
toast=False
)
except Exception as e:
log("Failed to show desktop notification: " + str(e))
except Exception as e:
log("Failed to play alarm sound: " + str(e))
@app.route('/api/get-alarm-status')
def get_alarm_status():
"""获取告警状态"""
try:
with alarm_lock:
status = {
'is_alarming': alarm_status['is_alarming'],
'alarm_count': alarm_status['alarm_count'],
'last_alarm_time': alarm_status['last_alarm_time'].strftime('%Y-%m-%d %H:%M:%S') if alarm_status['last_alarm_time'] else None,
'threshold': ALARM_THRESHOLD,
'alarm_type': alarm_status['alarm_type']
}
return jsonify({
'success': True,
'data': status
})
except Exception as e:
log("Error in get_alarm_status route: " + str(e))
return jsonify({'success': False, 'message': 'Internal server error'}), 500
@app.route('/api/reset-alarm')
def reset_alarm():
"""重置告警状态"""
try:
with alarm_lock:
alarm_status['is_alarming'] = False
alarm_status['alarm_count'] = 0
return jsonify({
'success': True,
'message': 'Alarm has been reset'
})
except Exception as e:
log("Error in reset_alarm route: " + str(e))
return jsonify({'success': False, 'message': 'Internal server error'}), 500
@app.route('/api/restart-monitoring')
def restart_monitoring():
"""重启监控进程"""
try:
if 'logged_in' not in session:
return jsonify({'success': False, 'message': 'Not logged in'})
log("正在尝试重启监控进程...")
# 停止现有进程
if processes['breeze'] and processes['breeze'].poll() is None:
try:
processes['breeze'].terminate()
log("Breeze监控进程已停止")
except Exception as e:
log(f"停止Breeze监控进程出错: {str(e)}")
if processes['cms'] and processes['cms'].poll() is None:
try:
processes['cms'].terminate()
log("CMS监控进程已停止")
except Exception as e:
log(f"停止CMS监控进程出错: {str(e)}")
# 等待进程完全终止
time.sleep(2)
# 获取Python可执行文件路径
python_executable = sys.executable
log(f"使用Python可执行文件: {python_executable}")
# 重新启动
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
# 获取会话中的凭据
breeze_cookie = session.get('breeze_cookie', '')
cms_cookie = session.get('cms_cookie', '')
username = session.get('username', '')
# 启动breeze监控
breeze_script = os.path.join(script_dir, "breeze_monitor.py")
env = os.environ.copy()
env['BREEZE_COOKIE'] = breeze_cookie
env['BREEZE_USERNAME'] = username
processes['breeze'] = subprocess.Popen(
[python_executable, breeze_script],
env=env,
shell=False
)
log(f"Breeze监控进程已启动, PID: {processes['breeze'].pid}")
# 启动cms监控
cms_script = os.path.join(script_dir, "cms_monitor.py")
env = os.environ.copy()
env['CMS_COOKIE'] = cms_cookie
env['CMS_USERNAME'] = username
processes['cms'] = subprocess.Popen(
[python_executable, cms_script],
env=env,
shell=False
)
log(f"CMS监控进程已启动, PID: {processes['cms'].pid}")
return jsonify({
'success': True,
'message': '监控进程已成功重启'
})
except Exception as e:
log(f"重启监控进程时出错: {str(e)}")
return jsonify({
'success': False,
'message': f"重启监控进程失败: {str(e)}"
}), 500
except Exception as e:
log(f"重启监控进程时出错: {str(e)}")
return jsonify({
'success': False,
'message': f"重启监控进程失败: {str(e)}"
}), 500
def open_browser():
"""在新线程中打开浏览器"""
time.sleep(1) # 等待服务器启动
try:
webbrowser.open('http://localhost:8000')
except Exception as e:
log("Failed to open browser: " + str(e))
@app.route('/api/check-now')
def check_now():
"""立即检查当前小时数据"""
try:
if 'logged_in' not in session:
return jsonify({'success': False, 'message': 'Not logged in'})
# 获取Python可执行文件路径
python_executable = sys.executable
# 向子进程发送检查请求
if processes['breeze'] and processes['breeze'].poll() is None:
# 获取会话中的凭据
breeze_cookie = session.get('breeze_cookie', '')
username = session.get('username', '')
# 创建临时环境
env = os.environ.copy()
env['BREEZE_COOKIE'] = breeze_cookie
env['BREEZE_USERNAME'] = username
# 启动一个临时进程来执行检查,添加--force参数强制使用整点时间
# 添加--no-config-check参数防止频繁检查配置文件
script_dir = os.path.dirname(os.path.abspath(__file__))
subprocess.Popen(
[python_executable, os.path.join(script_dir, "breeze_monitor.py"), "--check-now", "--force", "--no-config-check"],
env=env,
shell=False
)
log("开始执行Breeze系统手动检查")
if processes['cms'] and processes['cms'].poll() is None:
# 获取会话中的凭据
cms_cookie = session.get('cms_cookie', '')
username = session.get('username', '')
# 创建临时环境
env = os.environ.copy()
env['CMS_COOKIE'] = cms_cookie
env['CMS_USERNAME'] = username
# 启动一个临时进程来执行检查,添加--force参数强制使用整点时间
# 添加--no-config-check参数防止频繁检查配置文件
script_dir = os.path.dirname(os.path.abspath(__file__))
subprocess.Popen(
[python_executable, os.path.join(script_dir, "cms_monitor.py"), "--check-now", "--force", "--no-config-check"],
env=env,
shell=False
)
log("开始执行CMS系统手动检查")
return jsonify({
'success': True,
'message': '已启动数据检查',
'threshold': ALARM_THRESHOLD
})
except Exception as e:
log(f"立即检查功能出错: {str(e)}")
return jsonify({'success': False, 'message': f'数据检查启动失败: {str(e)}'}), 500
@app.route('/api/test-alarm')
def test_alarm():
"""测试告警功能"""
try:
if 'logged_in' not in session:
return jsonify({'success': False, 'message': 'Not logged in'})
# 检查是否为实际数据告警
is_real_data = request.args.get('real_data', 'false').lower() == 'true'
# 根据请求类型选择不同的消息
message = "实时告警" if is_real_data else "测试告警"
# 立即触发第一次告警
if not is_real_data:
# 手动触发告警并传递测试告警值
show_alarm_notification(ALARM_THRESHOLD + 100, is_test=True, alarm_type="测试告警")
else:
# 从API获取当前真实的小时总量
try:
breeze_hourly = read_data_file('breeze_hourly.json') or {'stats': {'weighted_total': 0}}
cms_hourly = read_data_file('cms_hourly.json') or {'stats': {'weighted_total': 0}}
total_hourly = 0
if 'stats' in breeze_hourly and 'weighted_total' in breeze_hourly['stats']:
total_hourly += breeze_hourly['stats']['weighted_total']
if 'stats' in cms_hourly and 'weighted_total' in cms_hourly['stats']:
total_hourly += cms_hourly['stats']['weighted_total']
# 只有在实际超过阈值时才显示通知
if total_hourly >= ALARM_THRESHOLD:
show_alarm_notification(total_hourly, is_test=False, alarm_type="实时告警")
except Exception as e:
log(f"Error getting real data for alarm: {str(e)}")
# 设置告警状态,启用连续告警
with alarm_lock:
now = datetime.now()
alarm_status['last_alarm_time'] = now
alarm_status['is_alarming'] = True
alarm_status['alarm_type'] = message # 保存告警类型
# 设置告警计数为1开始计数
alarm_status['alarm_count'] = 1
# 创建后台线程发送连续告警
if not is_real_data: # 仅对测试告警启用连续通知
threading.Thread(target=lambda: send_sequential_test_alarms("测试告警")).start()
log(f"{message} triggered by user")
return jsonify({
'success': True,
'message': f'{message} successful'
})
except Exception as e:
log("Error in test_alarm route: " + str(e))
return jsonify({'success': False, 'message': 'Failed to trigger alarm: ' + str(e)}), 500
@app.route('/api/get-coefficients')
def get_coefficients():
"""获取CMS系数配置"""
try:
if 'logged_in' not in session:
return jsonify({'success': False, 'message': 'Not logged in'})
# 从CMS系数配置文件读取
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
coefficients_file = os.path.join(script_dir, 'cms_coefficients.json')
if not os.path.exists(coefficients_file):
# 如果配置文件不存在,返回默认系数
default_coefficients = {
'comment': 0.55,
'feed': 1.54,
'complaint': 5.4
}
return jsonify({
'success': True,
'data': default_coefficients,
'message': 'Using default coefficients'
})
with open(coefficients_file, 'r', encoding='utf-8') as f:
coefficients = json.load(f)
return jsonify({
'success': True,
'data': coefficients
})
except Exception as e:
log(f"Error reading coefficients file: {str(e)}")
return jsonify({
'success': False,
'message': f'Error reading coefficients: {str(e)}'
})
except Exception as e:
log("Error in get_coefficients route: " + str(e))
return jsonify({'success': False, 'message': 'Internal server error'}), 500
@app.route('/api/update-coefficients', methods=['POST'])
def update_coefficients():
"""更新CMS系数配置"""
try:
if 'logged_in' not in session:
return jsonify({'success': False, 'message': 'Not logged in'})
# 获取请求中的系数
try:
data = request.get_json()
if not data:
return jsonify({
'success': False,
'message': 'No data provided'
})
# 验证系数格式
required_keys = ['comment', 'feed', 'complaint']
for key in required_keys:
if key not in data:
return jsonify({
'success': False,
'message': f'Missing required key: {key}'
})
# 验证值是数字
try:
data[key] = float(data[key])
except ValueError:
return jsonify({
'success': False,
'message': f'Value for {key} must be a number'
})
# 确保只有必要的键
coefficients = {
'comment': data['comment'],
'feed': data['feed'],
'complaint': data['complaint']
}
# 保存到配置文件
script_dir = os.path.dirname(os.path.abspath(__file__))
coefficients_file = os.path.join(script_dir, 'cms_coefficients.json')
with open(coefficients_file, 'w', encoding='utf-8') as f:
json.dump(coefficients, f, indent=4, ensure_ascii=False)
log(f"CMS系数已更新: 评论={coefficients['comment']}, 动态={coefficients['feed']}, 举报={coefficients['complaint']}")
# 使用命令行参数方式通知CMS监控进程更新系数
try:
# 获取Python可执行文件路径
python_executable = sys.executable
# 获取CMS监控脚本路径
cms_script = os.path.join(script_dir, "cms_monitor.py")
# 获取环境变量
env = os.environ.copy()
env['CMS_COOKIE'] = session.get('cms_cookie', '')
env['CMS_USERNAME'] = session.get('username', '')
# 启动进程更新系数
cmd = [
python_executable,
cms_script,
"--update-coefficients",
str(coefficients['comment']),
str(coefficients['feed']),
str(coefficients['complaint'])
]
subprocess.Popen(
cmd,
env=env,
shell=False
)
log("已发送系数更新命令给CMS监控进程")
except Exception as e:
log(f"发送系数更新命令失败: {str(e)}")
return jsonify({
'success': True,
'message': 'Coefficients updated successfully',
'data': coefficients
})
except Exception as e:
log(f"更新系数失败: {str(e)}")
return jsonify({
'success': False,
'message': f'Error updating coefficients: {str(e)}'
})
except Exception as e:
log("Error in update_coefficients route: " + str(e))
return jsonify({'success': False, 'message': 'Internal server error'}), 500
def send_sequential_test_alarms(alarm_type):
"""发送连续的测试告警通知"""
try:
# 等待10秒后发送第二次告警
time.sleep(10)
with alarm_lock:
if alarm_status['is_alarming'] and alarm_status['alarm_count'] < 3:
alarm_status['alarm_count'] = 2
# 发送第二次告警
show_alarm_notification(ALARM_THRESHOLD + 120, is_test=True, alarm_type=alarm_type)
log("Second test alarm notification sent")
# 再等待10秒后发送第三次告警
time.sleep(10)
with alarm_lock:
if alarm_status['is_alarming'] and alarm_status['alarm_count'] < 3:
alarm_status['alarm_count'] = 3
# 发送第三次告警
show_alarm_notification(ALARM_THRESHOLD + 150, is_test=True, alarm_type=alarm_type)
log("Third test alarm notification sent")
except Exception as e:
log(f"Error in sequential test alarms: {str(e)}")
@app.route('/api/get-breeze-coefficients')
def get_breeze_coefficients():
"""获取Breeze系数配置"""
try:
if 'logged_in' not in session:
return jsonify({'success': False, 'message': 'Not logged in'})
# 从Breeze系数配置文件读取
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
coefficients_file = os.path.join(script_dir, 'breeze_coefficients.json')
if not os.path.exists(coefficients_file):
# 如果配置文件不存在,返回错误信息
return jsonify({
'success': False,
'message': 'Breeze coefficients file not found'
})
with open(coefficients_file, 'r', encoding='utf-8') as f:
coefficients = json.load(f)
return jsonify({
'success': True,
'data': coefficients
})
except Exception as e:
log(f"Error reading Breeze coefficients file: {str(e)}")
return jsonify({
'success': False,
'message': f'Error reading coefficients: {str(e)}'
})
except Exception as e:
log("Error in get_breeze_coefficients route: " + str(e))
return jsonify({'success': False, 'message': 'Internal server error'}), 500
@app.route('/api/update-breeze-coefficients', methods=['POST'])
def update_breeze_coefficients():
"""更新Breeze系数配置"""
try:
if 'logged_in' not in session:
return jsonify({'success': False, 'message': 'Not logged in'})
# 获取请求中的系数
try:
data = request.get_json()
if not data:
return jsonify({
'success': False,
'message': 'No data provided'
})
# 验证系数格式
if not isinstance(data, dict):
return jsonify({
'success': False,
'message': 'Invalid data format, expected a dictionary'
})
# 验证所有值是数字
for key, value in data.items():
try:
data[key] = float(value)
except ValueError:
return jsonify({
'success': False,
'message': f'Value for {key} must be a number'
})
# 保存到配置文件
script_dir = os.path.dirname(os.path.abspath(__file__))
coefficients_file = os.path.join(script_dir, 'breeze_coefficients.json')
with open(coefficients_file, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
log(f"Breeze系数已更新")
# 使用命令行参数方式通知Breeze监控进程更新系数
try:
# 获取Python可执行文件路径
python_executable = sys.executable
# 获取Breeze监控脚本路径
breeze_script = os.path.join(script_dir, "breeze_monitor.py")
# 获取环境变量
env = os.environ.copy()
env['BREEZE_COOKIE'] = session.get('breeze_cookie', '')
env['BREEZE_USERNAME'] = session.get('username', '')
# 启动进程更新系数 - 一次只更新一个系数
for coefficient_type, coefficient_value in data.items():
cmd = [
python_executable,
breeze_script,
"--update-coefficients",
coefficient_type,
str(coefficient_value)
]
subprocess.Popen(
cmd,
env=env,
shell=False
)
log("已发送系数更新命令给Breeze监控进程")
except Exception as e:
log(f"发送系数更新命令失败: {str(e)}")
return jsonify({
'success': True,
'message': 'Breeze coefficients updated successfully',
'data': data
})
except Exception as e:
log(f"更新Breeze系数失败: {str(e)}")
return jsonify({
'success': False,
'message': f'Error updating coefficients: {str(e)}'
})
except Exception as e:
log("Error in update_breeze_coefficients route: " + str(e))
return jsonify({'success': False, 'message': 'Internal server error'}), 500
@app.route('/api/acknowledge-alarm')
def acknowledge_alarm():
"""确认并重置告警状态"""
try:
with alarm_lock:
# 完全重置告警状态
alarm_status['is_alarming'] = False
alarm_status['alarm_count'] = 0
alarm_status['last_alarm_time'] = None
# 记录告警已被确认
log("告警已被用户确认并重置")
return jsonify({
'success': True,
'message': 'Alarm has been acknowledged and reset'
})
except Exception as e:
log("Error in acknowledge_alarm route: " + str(e))
return jsonify({'success': False, 'message': 'Internal server error'}), 500
def is_port_available(port):
"""检查端口是否可用"""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(1)
result = s.connect_ex(('127.0.0.1', port))
return result != 0
except Exception as e:
log(f"检查端口可用性出错: {str(e)}")
return False
def free_port(port):
"""尝试释放被占用的端口"""
try:
if sys.platform.startswith('win'):
# Windows系统
os.system(f'for /f "tokens=5" %a in (\'netstat -ano ^| findstr :{port}\') do taskkill /f /pid %a')
return True
else:
# Linux/Mac系统
os.system(f"kill $(lsof -t -i:{port})")
return True
except Exception as e:
log(f"释放端口时出错: {str(e)}")
return False
def find_available_port(start_port, end_port):
"""查找可用端口"""
for port in range(start_port, end_port + 1):
if is_port_available(port):
return port
return None
@app.route('/api/check-cms-daily', methods=['POST'])
def check_cms_daily_data():
"""立即检查CMS每日数据"""
if 'user' not in session:
return jsonify({
'success': False,
'message': '请先登录系统'
})
try:
# 准备环境变量
env = os.environ.copy()
env['CMS_COOKIE'] = session.get('cms_cookie', '')
env['CMS_USERNAME'] = session.get('username', '')
# 设置--check-daily参数运行CMS监控脚本
cmd = [sys.executable, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cms_monitor.py'), '--check-daily']
# 运行子进程
process = subprocess.Popen(
cmd,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# 等待进程完成
stdout, stderr = process.communicate(timeout=60)
if process.returncode == 0:
app.logger.info('成功执行CMS每日数据检查')
return jsonify({
'success': True,
'message': 'CMS每日数据检查已执行'
})
else:
app.logger.error(f'CMS每日数据检查失败: {stderr.decode("utf-8", errors="ignore")}')
return jsonify({
'success': False,
'message': f'CMS每日数据检查失败: {stderr.decode("utf-8", errors="ignore")}'
})
except subprocess.TimeoutExpired:
app.logger.error('CMS每日数据检查超时')
return jsonify({
'success': False,
'message': 'CMS每日数据检查超时请检查系统日志'
})
except Exception as e:
app.logger.error(f'执行CMS每日数据检查时发生错误: {str(e)}')
return jsonify({
'success': False,
'message': f'执行CMS每日数据检查时发生错误: {str(e)}'
})
@app.route('/api/check-version-service')
def check_version_service():
"""检查版本检测服务的可用性"""
try:
log("正在检查版本检测服务可用性...")
# 测试连接
try:
response = requests.get(VERSION_CHECK_URL, timeout=5)
log(f"版本检测服务响应状态码: {response.status_code}")
service_status = {
'url': VERSION_CHECK_URL,
'is_available': response.status_code == 200,
'status_code': response.status_code,
'response_time': response.elapsed.total_seconds(),
'content_length': len(response.content) if response.status_code == 200 else 0,
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
if response.status_code == 200:
service_status['content'] = response.text.strip()
log(f"版本检测服务正常,返回内容: {service_status['content']}")
else:
log(f"版本检测服务异常HTTP状态码: {response.status_code}")
return jsonify({
'success': True,
'data': service_status
})
except requests.exceptions.Timeout:
log("版本检测服务连接超时")
return jsonify({
'success': False,
'message': '版本检测服务连接超时',
'data': {
'url': VERSION_CHECK_URL,
'is_available': False,
'error': 'timeout',
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
})
except requests.exceptions.ConnectionError:
log("无法连接到版本检测服务")
return jsonify({
'success': False,
'message': '无法连接到版本检测服务',
'data': {
'url': VERSION_CHECK_URL,
'is_available': False,
'error': 'connection_error',
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
})
except Exception as e:
log(f"检查版本检测服务时发生未知错误: {str(e)}")
return jsonify({
'success': False,
'message': f'检查版本检测服务时发生错误: {str(e)}',
'data': {
'url': VERSION_CHECK_URL,
'is_available': False,
'error': 'unknown',
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
})
except Exception as e:
log(f"版本检测服务检查接口发生错误: {str(e)}")
return jsonify({
'success': False,
'message': '版本检测服务检查失败',
'error': str(e)
}), 500
@app.route('/api/update-system', methods=['POST'])
def update_system():
"""处理系统更新请求"""
try:
# 获取桌面路径和快捷方式路径
desktop_path = os.path.join(os.path.expanduser('~'), 'Desktop')
temp_dir = 'monitor_temp'
temp_path = os.path.join(desktop_path, temp_dir)
shortcut_path = os.path.join(temp_path, '网易大神审核数据监控看板一键安装.lnk')
# 下载快捷方式
shortcut_url = 'http://cos.ui-beam.com/work_scripts/monitor/%E7%BD%91%E6%98%93%E5%A4%A7%E7%A5%9E%E5%AE%A1%E6%A0%B8%E6%95%B0%E6%8D%AE%E7%9B%91%E6%8E%A7%E7%9C%8B%E6%9D%BF%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85.lnk'
log(f"开始下载更新文件: {shortcut_url}")
# 获取系统代理设置
proxies = {
'http': os.environ.get('HTTP_PROXY', ''),
'https': os.environ.get('HTTPS_PROXY', '')
}
# 首先尝试不使用代理直接下载
try:
log("尝试直接下载更新文件...")
response = requests.get(shortcut_url, timeout=30, proxies={'http': None, 'https': None}, verify=False)
if response.status_code == 200:
with open(shortcut_path, 'wb') as f:
f.write(response.content)
log("更新文件下载成功")
else:
raise requests.exceptions.RequestException(f"下载失败HTTP状态码: {response.status_code}")
except (requests.exceptions.RequestException, IOError) as e:
log(f"直接下载失败: {str(e)},尝试使用系统代理...")
# 如果直接下载失败,尝试使用系统代理
try:
response = requests.get(shortcut_url, timeout=30, proxies=proxies, verify=False)
if response.status_code == 200:
with open(shortcut_path, 'wb') as f:
f.write(response.content)
log("使用代理下载更新文件成功")
elif response.status_code == 407:
log("代理需要认证,尝试最后一次直接下载...")
# 最后一次尝试直接下载
response = requests.get(shortcut_url, timeout=30, proxies={'http': None, 'https': None}, verify=False)
if response.status_code == 200:
with open(shortcut_path, 'wb') as f:
f.write(response.content)
log("最后尝试下载成功")
else:
raise requests.exceptions.RequestException(f"所有下载尝试都失败,最后状态码: {response.status_code}")
else:
raise requests.exceptions.RequestException(f"使用代理下载失败HTTP状态码: {response.status_code}")
except Exception as proxy_error:
log(f"使用代理下载失败: {str(proxy_error)}")
return jsonify({
'success': False,
'message': f'下载更新文件失败: {str(proxy_error)}'
}), 500
# 检查文件是否成功下载
if not os.path.exists(shortcut_path):
return jsonify({
'success': False,
'message': '更新文件下载成功但未找到文件'
}), 500
# 启动快捷方式
try:
log("正在启动更新程序...")
subprocess.Popen(['cmd', '/c', 'start', '', shortcut_path], shell=True)
log("更新程序启动成功")
return jsonify({
'success': True,
'message': '更新程序已启动'
})
except Exception as e:
log(f"启动更新程序失败: {str(e)}")
return jsonify({
'success': False,
'message': f'启动更新程序失败: {str(e)}'
}), 500
except Exception as e:
log(f"系统更新过程中发生错误: {str(e)}")
return jsonify({
'success': False,
'message': f'系统更新失败: {str(e)}'
}), 500
def main():
"""主函数"""
try:
print("\n" + "="*50)
print("网易大神审核数据监控服务启动中...")
print("="*50)
print("\n[系统状态]")
print("1. 正在初始化监控服务...")
# 初始化版本文件
timestamp_version = ensure_version_file()
print(f"2. 当前系统版本: {VERSION}")
# 检查并安装依赖
print("3. 正在检查系统依赖...")
check_and_install_dependencies()
# 启动版本检测线程
print("4. 启动版本检测服务...")
version_thread = threading.Thread(target=monitor_version_thread)
version_thread.daemon = True
version_thread.start()
# 获取端口
port = 8000
# 检查端口是否被占用
if not is_port_available(port):
print(f"5. 端口 {port} 已被占用,尝试释放...")
free_port(port)
# 再次检查端口
if not is_port_available(port):
port = find_available_port(8001, 8100)
print(f" - 使用替代端口 {port}")
else:
print(f"5. 端口 {port} 可用")
print("\n[启动完成]")
print(f"* 监控系统已启动,请打开浏览器访问: http://localhost:{port}")
print("* 等待用户登录...")
print("\n" + "!"*50)
print("! 警告:请勿关闭此窗口 !")
print("! 此窗口是系统监控的核心进程 !")
print("! 一旦关闭,所有监控服务将停止工作 !")
print("!"*50 + "\n")
# 在新线程中启动浏览器
threading.Thread(target=open_browser).start()
try:
# 使用 SocketIO 启动服务器
socketio.run(app, debug=False, host='0.0.0.0', port=port)
except Exception as e:
log(f"启动服务器出错: {str(e)}")
finally:
# 关闭所有子进程
for name, process in processes.items():
if process and process.poll() is None:
try:
process.terminate()
log(f"已关闭 {name} 监控进程")
except Exception as e:
log(f"关闭 {name} 监控进程失败: {str(e)}")
# 清理临时文件
script_dir = os.path.dirname(os.path.abspath(__file__))
data_files = ['breeze_daily.json', 'breeze_hourly.json', 'cms_daily.json', 'cms_hourly.json']
for file_name in data_files:
file_path = os.path.join(script_dir, file_name)
if os.path.exists(file_path):
try:
os.remove(file_path)
log(f"已删除临时文件: {file_name}")
except Exception as e:
log(f"删除临时文件 {file_name} 失败: {str(e)}")
log("监控系统已关闭")
except Exception as e:
log("Error in main function: " + str(e))
# 信号处理函数
def signal_handler(sig, frame):
"""处理信号,用于优雅退出"""
log("收到退出信号,正在关闭系统...")
global shutdown_flag
shutdown_flag = True
# 通过os._exit强制终止进程
os._exit(0)
if __name__ == "__main__":
# 注册信号处理器
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# 执行主函数
main()