Files
diary-family/submit_summary_tool.py
xiaji 80d40b5daa feat(api): 为汇总记录提交添加来源验证和来源字段
- 移除 api_submit_summary 的 csrf_exempt 装饰器
- 允许客户端提交来源字段
- 在提交工具中添加来源输入框
- 自动生成来源信息当未提供时
2026-01-26 22:35:51 +08:00

463 lines
17 KiB
Python
Raw Permalink 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 python3
"""
汇总记录提交工具 - PySide6 GUI版本
用于向家庭日报系统提交汇总记录
"""
import sys
import socket
import psutil
import requests
import json
import os
from datetime import datetime
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QTextEdit, QGroupBox, QFormLayout,
QMessageBox, QSystemTrayIcon, QMenu, QStatusBar, QProgressBar,
QSpinBox, QComboBox
)
from PySide6.QtCore import QTimer, Qt, QSettings
from PySide6.QtGui import QIcon, QAction
class SubmitSummaryTool(QMainWindow):
"""汇总记录提交工具主窗口"""
def __init__(self):
super().__init__()
self.setWindowTitle("汇总记录提交工具")
self.setMinimumSize(600, 500)
self.resize(700, 550)
self.settings = QSettings("DiaryFamily", "SubmitSummaryTool")
self.load_settings()
self.init_ui()
self.setup_tray()
self.start_auto_refresh()
def load_settings(self):
"""加载保存的设置"""
self.server_url = self.settings.value("server_url", "http://localhost:8000")
self.auto_submit = self.settings.value("auto_submit", False, type=bool)
self.submit_interval = self.settings.value("submit_interval", 60, type=int)
self.auto_start = self.settings.value("auto_start", True, type=bool)
def save_settings(self):
"""保存设置"""
self.settings.setValue("server_url", self.server_url)
self.settings.setValue("auto_submit", self.auto_submit)
self.settings.setValue("submit_interval", self.submit_interval)
self.settings.setValue("auto_start", self.auto_start)
def init_ui(self):
"""初始化UI"""
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
main_layout.setSpacing(10)
self.create_server_config_group(main_layout)
self.create_system_info_group(main_layout)
self.create_manual_submit_group(main_layout)
self.create_log_group(main_layout)
self.create_status_bar()
self.create_actions()
self.statusBar().showMessage("就绪")
self.refresh_system_info()
def create_server_config_group(self, parent_layout):
"""服务器配置组"""
group = QGroupBox("服务器配置")
layout = QFormLayout()
layout.setSpacing(10)
self.server_url_edit = QLineEdit()
self.server_url_edit.setPlaceholderText("http://your-server:8000")
self.server_url_edit.setText(self.server_url)
self.server_url_edit.textChanged.connect(self.on_url_changed)
self.auto_submit_check = QPushButton("自动提交")
self.auto_submit_check.setCheckable(True)
self.auto_submit_check.setChecked(self.auto_submit)
self.auto_submit_check.toggled.connect(self.on_auto_submit_toggled)
self.interval_spin = QSpinBox()
self.interval_spin.setRange(10, 3600)
self.interval_spin.setValue(self.submit_interval)
self.interval_spin.setSuffix("")
self.interval_spin.valueChanged.connect(self.on_interval_changed)
self.test_connection_btn = QPushButton("测试连接")
self.test_connection_btn.clicked.connect(self.test_connection)
interval_layout = QHBoxLayout()
interval_layout.addWidget(self.auto_submit_check)
interval_layout.addWidget(self.interval_spin)
interval_layout.addStretch()
layout.addRow("服务器地址:", self.server_url_edit)
layout.addRow("自动提交:", interval_layout)
layout.addRow("", self.test_connection_btn)
group.setLayout(layout)
parent_layout.addWidget(group)
def create_system_info_group(self, parent_layout):
"""系统信息组"""
group = QGroupBox("系统监控信息")
layout = QFormLayout()
layout.setSpacing(8)
self.cpu_label = QLabel("0%")
self.memory_label = QLabel("0%")
self.disk_label = QLabel("0%")
self.hostname_label = QLabel()
self.ip_label = QLabel()
self.timestamp_label = QLabel()
self.cpu_progress = QProgressBar()
self.cpu_progress.setRange(0, 100)
self.memory_progress = QProgressBar()
self.memory_progress.setRange(0, 100)
self.disk_progress = QProgressBar()
self.disk_progress.setRange(0, 100)
layout.addRow("CPU使用率:", self.cpu_progress)
layout.addRow("内存使用率:", self.memory_progress)
layout.addRow("磁盘使用率:", self.disk_progress)
layout.addRow("主机名:", self.hostname_label)
layout.addRow("IP地址:", self.ip_label)
layout.addRow("更新时间:", self.timestamp_label)
refresh_btn = QPushButton("刷新")
refresh_btn.clicked.connect(self.refresh_system_info)
btn_layout = QHBoxLayout()
btn_layout.addWidget(refresh_btn)
btn_layout.addStretch()
layout.addRow("", btn_layout)
group.setLayout(layout)
parent_layout.addWidget(group)
def create_manual_submit_group(self, parent_layout):
"""手动提交组"""
group = QGroupBox("手动提交")
layout = QVBoxLayout()
layout.setSpacing(10)
self.content_edit = QTextEdit()
self.content_edit.setPlaceholderText("输入要提交的内容...")
self.content_edit.setMaximumHeight(100)
source_layout = QHBoxLayout()
source_layout.addWidget(QLabel("来源:"))
self.source_edit = QLineEdit()
self.source_edit.setPlaceholderText("留空则自动生成")
source_layout.addWidget(self.source_edit)
submit_btn = QPushButton("提交汇总记录")
submit_btn.clicked.connect(self.manual_submit)
layout.addWidget(self.content_edit)
layout.addLayout(source_layout)
layout.addWidget(submit_btn)
group.setLayout(layout)
parent_layout.addWidget(group)
def create_log_group(self, parent_layout):
"""日志组"""
group = QGroupBox("运行日志")
layout = QVBoxLayout()
layout.setSpacing(5)
self.log_edit = QTextEdit()
self.log_edit.setReadOnly(True)
self.log_edit.setMaximumHeight(150)
clear_btn = QPushButton("清除日志")
clear_btn.clicked.connect(self.log_edit.clear)
layout.addWidget(self.log_edit)
layout.addWidget(clear_btn, alignment=Qt.AlignRight)
group.setLayout(layout)
parent_layout.addWidget(group)
def create_status_bar(self):
"""创建状态栏"""
self.status_label = QLabel("就绪")
self.statusBar().addPermanentWidget(self.status_label)
def create_actions(self):
"""创建菜单动作"""
self.refresh_action = QAction("刷新", self)
self.refresh_action.triggered.connect(self.refresh_system_info)
self.submit_action = QAction("提交", self)
self.submit_action.triggered.connect(self.manual_submit)
self.quit_action = QAction("退出", self)
self.quit_action.triggered.connect(self.close)
def setup_tray(self):
"""设置系统托盘"""
if QSystemTrayIcon.isSystemTrayAvailable():
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setToolTip("汇总记录提交工具")
tray_menu = QMenu()
tray_menu.addAction(self.refresh_action)
tray_menu.addAction(self.submit_action)
tray_menu.addSeparator()
tray_menu.addAction(self.quit_action)
self.tray_icon.setContextMenu(tray_menu)
self.tray_icon.show()
self.tray_icon.activated.connect(self.on_tray_activated)
def on_tray_activated(self, reason):
"""托盘图标激活处理"""
if reason == QSystemTrayIcon.DoubleClick:
self.show()
self.raise_()
self.activateWindow()
def start_auto_refresh(self):
"""启动自动刷新"""
self.timer = QTimer(self)
self.timer.timeout.connect(self.auto_refresh_cycle)
self.timer.start(self.submit_interval * 1000 if self.auto_submit else 5000)
def auto_refresh_cycle(self):
"""自动刷新周期"""
self.refresh_system_info()
if self.auto_submit:
self.auto_submit_content()
def refresh_system_info(self):
"""刷新系统信息"""
try:
cpu = psutil.cpu_percent(interval=None)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
hostname = socket.gethostname()
ip = socket.gethostbyname(hostname)
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.cpu_progress.setValue(int(cpu))
self.memory_progress.setValue(int(memory.percent))
self.disk_progress.setValue(int(disk.percent))
self.hostname_label.setText(hostname)
self.ip_label.setText(ip)
self.timestamp_label.setText(timestamp)
self.log(f"系统信息已刷新 - CPU: {cpu}%, 内存: {memory.percent}%, 磁盘: {disk.percent}%")
except Exception as e:
self.log(f"获取系统信息失败: {str(e)}")
def get_system_content(self):
"""获取系统监控内容"""
info = self.get_system_info()
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
return f"[系统监控 {timestamp}] CPU使用率: {info['cpu']}%, 内存使用率: {info['memory']}%, 磁盘使用率: {info['disk']}%"
def auto_submit_content(self):
"""自动提交系统监控内容"""
content = self.get_system_content()
self.submit_summary(content, source=None)
def manual_submit(self):
"""手动提交"""
content = self.content_edit.toPlainText().strip()
if not content:
QMessageBox.warning(self, "警告", "请输入要提交的内容")
return
source = self.source_edit.text().strip()
self.submit_summary(content, source)
self.content_edit.clear()
def get_csrf_token(self):
"""获取CSRF token"""
try:
session = requests.Session()
get_url = f"{self.server_url}/"
self.log(f"正在访问首页获取CSRF token: {get_url}")
response = session.get(get_url, timeout=10)
self.log(f"首页响应状态码: {response.status_code}")
cookies = session.cookies.get_dict()
self.log(f"收到的Cookies: {list(cookies.keys())}")
csrf_token = cookies.get('csrftoken')
if csrf_token:
self.log(f"获取CSRF token成功: {csrf_token[:20]}...")
return session, csrf_token
else:
self.log("警告: 未获取到CSRF tokencookie中可能没有csrftoken")
self.log(f"所有cookies: {cookies}")
return session, None
except Exception as e:
self.log(f"获取CSRF token失败: {str(e)}")
return None, None
def submit_summary(self, content, source=""):
"""提交汇总记录到家庭日报系统"""
submit_url = f"{self.server_url}/api/v1/summary/submit/"
try:
self.status_label.setText("正在提交...")
session, csrf_token = self.get_csrf_token()
if session is None:
self.log("错误: 无法建立会话,连接可能失败")
self.status_label.setText("连接失败")
QMessageBox.critical(self, "错误", "无法连接到服务器请检查URL是否正确")
return
headers = {
'Referer': self.server_url,
}
if csrf_token:
headers['X-CSRFToken'] = csrf_token
self.log(f"设置CSRF header: X-CSRFToken = {csrf_token[:20]}...")
self.log(f"发送POST请求到: {submit_url}")
post_data = {'content': content}
if source:
post_data['source'] = source
self.log(f"提交来源: {source}")
response = session.post(submit_url, data=post_data, headers=headers, timeout=10)
self.log(f"服务器响应状态码: {response.status_code}")
self.log(f"响应Headers: {dict(response.headers)}")
self.log(f"响应内容: {response.text[:200]}...")
if response.status_code != 200:
error_msg = f"服务器返回错误: HTTP {response.status_code}"
self.log(error_msg)
self.status_label.setText(f"错误: {response.status_code}")
QMessageBox.critical(self, "错误", f"{error_msg}\n\n响应内容:\n{response.text[:500]}")
return
try:
result = response.json()
except json.JSONDecodeError as e:
error_msg = f"JSON解析失败: {str(e)}\n响应内容: {response.text[:200]}"
self.log(error_msg)
self.status_label.setText("JSON解析失败")
QMessageBox.critical(self, "错误", error_msg)
return
if result['success']:
self.log(f"提交成功记录ID: {result['id']}")
self.status_label.setText("提交成功")
QMessageBox.information(self, "成功", f"提交成功记录ID: {result['id']}")
else:
self.log(f"提交失败: {result['message']}")
self.status_label.setText(f"失败: {result['message']}")
QMessageBox.warning(self, "失败", f"提交失败: {result['message']}")
except requests.exceptions.ConnectionError as e:
error_msg = f"连接失败: 无法连接到 {submit_url}"
self.log(error_msg)
self.status_label.setText("连接失败")
QMessageBox.critical(self, "错误", error_msg)
except requests.exceptions.Timeout:
error_msg = "请求超时,请检查服务器是否正常运行"
self.log(error_msg)
self.status_label.setText("连接超时")
QMessageBox.critical(self, "错误", error_msg)
except Exception as e:
error_msg = f"请求异常: {str(e)}"
self.log(error_msg)
self.status_label.setText("请求异常")
QMessageBox.critical(self, "错误", error_msg)
def test_connection(self):
"""测试服务器连接"""
url = self.server_url.rstrip('/') + "/api/v1/summary/submit/"
try:
self.status_label.setText("测试连接中...")
response = requests.get(url.replace('/submit/', '/'), timeout=5)
self.log(f"连接测试: 服务器响应状态码 {response.status_code}")
self.status_label.setText("连接成功")
QMessageBox.information(self, "成功", "服务器连接成功")
except requests.exceptions.ConnectionError:
self.log("连接测试: 无法连接到服务器")
self.status_label.setText("连接失败")
QMessageBox.warning(self, "失败", "无法连接到服务器请检查URL是否正确")
except Exception as e:
self.log(f"连接测试: {str(e)}")
self.status_label.setText("测试异常")
QMessageBox.critical(self, "错误", f"测试失败: {str(e)}")
def get_system_info(self):
"""获取系统基本信息"""
try:
cpu_percent = psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
return {
'cpu': cpu_percent,
'memory': memory.percent,
'disk': disk.percent
}
except Exception as e:
self.log(f"获取系统信息失败: {str(e)}")
return {'cpu': 0, 'memory': 0, 'disk': 0}
def log(self, message):
"""记录日志"""
timestamp = datetime.now().strftime('%H:%M:%S')
log_message = f"[{timestamp}] {message}"
self.log_edit.append(log_message)
print(log_message)
def on_url_changed(self, url):
"""URL改变处理"""
self.server_url = url
def on_auto_submit_toggled(self, checked):
"""自动提交开关处理"""
self.auto_submit = checked
if checked:
self.log("已启用自动提交")
else:
self.log("已禁用自动提交")
def on_interval_changed(self, value):
"""刷新间隔改变处理"""
self.submit_interval = value
self.timer.setInterval(value * 1000 if self.auto_submit else 5000)
self.log(f"刷新间隔已设置为 {value}")
def closeEvent(self, event):
"""关闭窗口事件"""
self.save_settings()
self.log("正在保存设置...")
event.accept()
def main():
app = QApplication(sys.argv)
app.setStyle('Fusion')
window = SubmitSummaryTool()
if window.auto_start:
window.show()
else:
window.showMinimized()
sys.exit(app.exec())
if __name__ == "__main__":
main()