return _message.default_pool.FindFieldByName(full_name) KeyError: "Couldn't find field google.pr

本文介绍了一个关于protobuf使用中出现的KeyError异常问题及其解决方案。该问题是由于找不到特定字段导致的,通过强制重新安装并升级protobuf库可以有效解决此问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    return _message.default_pool.FindFieldByName(full_name)

KeyError: "Couldn't find field google.protobuf.FileOptions.javanano_use_deprecated_package"


解决:

pip install --force-reinstall --upgrade protobuf

<think>由于代码量较大,我将分别提供优化后的`detail_page.py`和`server_manager.py`代码。优化点包括: 1. 使用上下文管理器确保SSH连接安全 2. 增加文件校验(MD5) 3. 使用线程池管理上传任务 4. 添加自定义异常类 5. 增强日志记录 注意:由于代码较长,我将分两个部分提供。 首先,优化后的`detail_page.py`:</think>### 优化后的 `detail_page.py` ```python import sys import os import time import json import subprocess import psutil import logging from datetime import datetime from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QProgressBar, QTableWidget, QTableWidgetItem, QHeaderView, QTextEdit, QPushButton, QSplitter, QListWidget, QGroupBox, QScrollArea, QMessageBox, QComboBox, QAbstractItemView, QSizePolicy, QFileDialog, QProgressDialog ) from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal, QRunnable, QThreadPool, QObject from PyQt5.QtGui import QFont, QColor, QBrush, QIcon, QPainter import ssh_manger import hashlib # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger('DetailPage') REPORT_FILE = "/home/mss_tool/infos/system_report.json" # 报告文件名 class UploadSignals(QObject): """上传任务信号""" progress_updated = pyqtSignal(int) finished = pyqtSignal(bool, str) log_message = pyqtSignal(str) class UploadTask(QRunnable): """JAR上传线程任务""" def __init__(self, server_info, remote_path, local_path, ssh_manager): super().__init__() self.server_info = server_info self.remote_path = remote_path self.local_path = local_path self.ssh_manager = ssh_manager self.signals = UploadSignals() self.cancel_flag = False self.setAutoDelete(True) def run(self): """执行上传操作""" try: self.signals.log_message.emit("开始上传JAR文件") # 计算本地文件MD5 local_hash = self._calculate_md5(self.local_path) self.signals.log_message.emit(f"本地文件MD5: {local_hash}") # 1. 停止旧服务 jar_name = os.path.basename(self.remote_path) jar_dir = os.path.dirname(self.remote_path) # 获取PID并停止服务 get_pid_cmd = ( f"ps aux | grep '{jar_name}' | grep '{jar_dir}' | grep -v grep | " f"awk '{{print $2}}'" ) self.signals.log_message.emit(f"停止旧服务: {get_pid_cmd}") success, exit_code, stdout, stderr = self.ssh_manager.execute_command( get_pid_cmd, sudo=True ) pids = [] if success and stdout.strip(): pids = stdout.split() for pid in pids: kill_cmd = f"kill -9 {pid}" self.signals.log_message.emit(f"停止进程: {kill_cmd}") self.ssh_manager.execute_command(kill_cmd, sudo=True) time.sleep(2) self.signals.progress_updated.emit(10) self.signals.log_message.emit("旧服务已停止") # 2. 备份旧JAR文件 timestamp = datetime.now().strftime("%Y%m%d%H%M%S") backup_name = f"{jar_name}.bak_{timestamp}" backup_path = os.path.join(jar_dir, backup_name) # 重命名命令 rename_cmd = f"mv '{self.remote_path}' '{backup_path}'" self.signals.log_message.emit(f"备份旧文件: {rename_cmd}") success, exit_code, stdout, stderr = self.ssh_manager.execute_command( rename_cmd, sudo=True ) if not success: error_msg = f"无法备份旧JAR文件: {stderr}\n命令: {rename_cmd}" self.signals.log_message.emit(error_msg) self.signals.finished.emit(False, error_msg) return self.signals.progress_updated.emit(20) self.signals.log_message.emit("备份成功") # 3. 上传新JAR文件 self.signals.log_message.emit(f"开始上传新JAR文件: {self.local_path} -> {self.remote_path}") success, message = self.ssh_manager.upload_file( self.local_path, self.remote_path, overwrite=True, progress_callback=self.update_progress ) if self.cancel_flag: self.signals.log_message.emit("上传已取消") self.signals.finished.emit(False, "上传已取消") return if not success: # 尝试恢复备份 self.signals.log_message.emit(f"上传失败,尝试恢复备份: {backup_path}") restore_cmd = f"mv '{backup_path}' '{self.remote_path}'" self.ssh_manager.execute_command(restore_cmd, sudo=True) self.signals.finished.emit(False, f"上传JAR文件失败: {message}") return self.signals.progress_updated.emit(70) self.signals.log_message.emit("JAR文件上传成功") # 4. 校验文件MD5 self.signals.log_message.emit("开始校验文件MD5") remote_hash = self._get_remote_md5(self.remote_path) self.signals.log_message.emit(f"远程文件MD5: {remote_hash}") if remote_hash != local_hash: error_msg = f"MD5校验失败: 本地({local_hash}) != 远程({remote_hash})" self.signals.log_message.emit(error_msg) # 恢复备份 self.ssh_manager.execute_command(f"mv '{backup_path}' '{self.remote_path}'", sudo=True) self.signals.finished.emit(False, error_msg) return self.signals.progress_updated.emit(75) self.signals.log_message.emit("MD5校验成功") # 5. 启动新服务 start_cmd = ( f"cd '{jar_dir}' && " f"nohup java -jar '{self.remote_path}' > '{self.remote_path}.log' 2>&1 &" ) self.signals.log_message.emit(f"启动新服务: {start_cmd}") success, exit_code, stdout, stderr = self.ssh_manager.execute_command( start_cmd, sudo=True ) if not success: error_msg = f"启动服务失败: {stderr}\n启动命令: {start_cmd}" self.signals.log_message.emit(error_msg) self.signals.finished.emit(False, error_msg) return # 6. 检查服务是否启动成功 self.signals.progress_updated.emit(80) self.signals.log_message.emit("检查服务状态...") time.sleep(5) # 等待服务启动 # 检查进程是否存在 check_pid_cmd = ( f"ps aux | grep '{jar_name}' | grep '{jar_dir}' | grep -v grep | " f"awk '{{print $2}}'" ) success, exit_code, stdout, stderr = self.ssh_manager.execute_command( check_pid_cmd, sudo=True ) if not success or not stdout.strip(): # 尝试读取日志 log_file = f"{self.remote_path}.log" success, log_content = self.ssh_manager.read_file(log_file, retries=1) if success: # 截取日志末尾 if len(log_content) > 1000: log_snippet = log_content[-1000:] else: log_snippet = log_content error_msg = f"服务启动失败,日志末尾内容:\n{log_snippet}" else: error_msg = "服务启动失败,且无法读取日志文件" self.signals.log_message.emit(error_msg) self.signals.finished.emit(False, error_msg) return self.signals.progress_updated.emit(95) self.signals.log_message.emit("服务启动成功") # 7. 重启监控服务 self.signals.log_message.emit("重启监控服务...") restart_cmd = f"cd /home/mss_tool && ./monitor restart" success, exit_code, stdout, stderr = self.ssh_manager.execute_command( restart_cmd, sudo=True ) if not success: self.signals.log_message.emit(f"重启监控失败: {stderr}") else: self.signals.log_message.emit("监控服务已重启") self.signals.progress_updated.emit(100) result_msg = ( f"JAR文件上传并启动成功!\n" f"备份路径: {backup_path}\n" f"新文件: {self.remote_path}\n" f"MD5: {local_hash}\n" f"启动命令: {start_cmd}\n" f"监控已重启" ) self.signals.log_message.emit(result_msg) self.signals.finished.emit(True, result_msg) except Exception as e: error_msg = f"操作过程中出错: {str(e)}" self.signals.log_message.emit(error_msg) self.signals.finished.emit(False, error_msg) def update_progress(self, percent): """更新进度""" self.signals.progress_updated.emit(percent) self.signals.log_message.emit(f"上传进度: {percent:.1f}%") def cancel_upload(self): """取消上传操作""" self.cancel_flag = True if self.ssh_manager: try: self.ssh_manager.set_cancel_flag(True) except: pass def _calculate_md5(self, file_path): """计算文件MD5""" hash_md5 = hashlib.md5() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() def _get_remote_md5(self, remote_path): """获取远程文件MD5""" cmd = f"md5sum {remote_path} | awk '{{print $1}}'" success, exit_code, stdout, stderr = self.ssh_manager.execute_command(cmd) if not success: raise Exception(f"获取远程MD5失败: {stderr}") return stdout.strip() # 其他类保持不变(StatusDot, DistUploadThread等) class SystemMonitorUI(QMainWindow): def __init__(self, info, ssh_manager): super().__init__() # ... 初始化代码保持不变 ... # 创建线程池 self.thread_pool = QThreadPool.globalInstance() self.thread_pool.setMaxThreadCount(3) # 限制同时上传任务数量 def upload_jar(self): """使用线程池优化JAR上传""" button = self.sender() jar_name = button.property("jar_name") jar_path = button.property("jar_path") server_info = button.property("server_info") if not jar_path or jar_path == "0": QMessageBox.warning(self, "警告", "该JAR服务未配置路径!") return # 弹出文件对话框选择JAR文件 file_path, _ = QFileDialog.getOpenFileName( self, "选择JAR文件", "", "JAR Files (*.jar);;All Files (*)" ) if not file_path: return # 创建进度对话框 progress_dialog = QProgressDialog("正在上传JAR文件...", "取消", 0, 100, self) progress_dialog.setWindowTitle("上传JAR文件") progress_dialog.setWindowModality(Qt.WindowModal) progress_dialog.setAutoClose(True) progress_dialog.setAutoReset(True) progress_dialog.setMinimumDuration(0) progress_dialog.setValue(0) # 添加日志显示区域 log_text = QTextEdit() log_text.setReadOnly(True) log_text.setMinimumHeight(100) layout = progress_dialog.layout() if isinstance(layout, QVBoxLayout): layout.addWidget(log_text) # 创建上传任务 upload_task = UploadTask(server_info, jar_path, file_path, self.ssh_manager) # 连接信号 upload_task.signals.progress_updated.connect(progress_dialog.setValue) upload_task.signals.finished.connect( lambda success, message: self.handle_upload_result(success, message, progress_dialog) ) upload_task.signals.log_message.connect(log_text.append) # 提交任务到线程池 self.thread_pool.start(upload_task) # 连接取消按钮 progress_dialog.canceled.connect(upload_task.cancel_upload) # 其他方法保持不变... ``` ### 优化后的 `server_manager.py` ```python import sys import json import os import base64 import hashlib import logging import shutil import threading from threading import Thread, Event from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTableWidget, QTableWidgetItem, QDialog, QLineEdit, QLabel, QFormLayout, QMessageBox, QHeaderView, QInputDialog, QGridLayout, QGroupBox, QScrollArea, QProgressDialog, QTabWidget, QTabBar ) from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtGui import QFont, QIcon, QFontMetrics import time import detail_page import ssh_manger # 配置日志 logging.basicConfig( filename='server_manager.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) def resource_path(relative_path): """获取资源的绝对路径""" if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, relative_path) return os.path.join(os.path.abspath("."), relative_path) # 配置常量 LOCAL_SCRIPT = "monitor" REMOTE_DIR = "/home/mss_tool/" REMOTE_VERSION = os.path.join(REMOTE_DIR, "version.txt") VERSION = '1.0.3' REMOTE_SCRIPT = os.path.join(REMOTE_DIR, os.path.basename(LOCAL_SCRIPT)) # 通知服务常量 ALERT_VERSION = '1.0.3' ALERT_SCRIPT = "mss_alert" REMOTE_ALERT_SCRIPT = os.path.join(REMOTE_DIR, ALERT_SCRIPT) # 加密解密工具类 class EncryptionManager: def __init__(self, key=None): self.key = key if key else "Mko09ijn@" # 默认密钥 def encrypt(self, data): """简单加密方法""" if isinstance(data, str): data = data.encode('utf-8') # 使用密钥生成哈希作为加密密钥 key_hash = hashlib.sha256(self.key.encode()).digest() # 简单的XOR加密 encrypted = bytearray() for i in range(len(data)): encrypted.append(data[i] ^ key_hash[i % len(key_hash)]) return base64.b64encode(encrypted).decode('utf-8') def decrypt(self, enc_data): """简单解密方法""" enc_data = base64.b64decode(enc_data) # 使用密钥生成哈希作为加密密钥 key_hash = hashlib.sha256(self.key.encode()).digest() # 简单的XOR解密 decrypted = bytearray() for i in range(len(enc_data)): decrypted.append(enc_data[i] ^ key_hash[i % len(key_hash)]) return decrypted.decode('utf-8') # 添加/编辑服务器对话框(保持不变) # 通知服务器配置对话框(保持不变) # SSH连接和部署线程(用于通知服务)(保持不变) # SSH连接和部署线程(保持不变) # 主窗口 class ServerManager(QMainWindow): def __init__(self): super().__init__() self.current_password = "Mko09ijn@" self.enc_manager = EncryptionManager(self.current_password) self.servers = [] self.ssh_connection_pool = {} # SSH连接池 self.init_ui() self.load_servers() def get_ssh_connection(self, server_info): """从连接池获取SSH连接""" key = f"{server_info['ip']}:{server_info.get('port', 22)}" # 检查连接是否有效 if key in self.ssh_connection_pool: ssh_manager = self.ssh_connection_pool[key] if ssh_manager.ssh and ssh_manager.ssh.get_transport() and ssh_manager.ssh.get_transport().is_active(): return ssh_manager else: # 连接无效,创建新连接 logger.info(f"连接池中的连接无效,创建新连接: {key}") del self.ssh_connection_pool[key] # 创建新连接 logger.info(f"创建新SSH连接: {key}") ssh_manager = ssh_manger.SSHManager(server_info) success, message = ssh_manager.connect() if success: ssh_manager.start_heartbeat() self.ssh_connection_pool[key] = ssh_manager return ssh_manager else: raise Exception(f"无法创建SSH连接: {message}") def open_ssh(self, row): """打开SSH监控窗口(使用连接池优化)""" server_info = self.servers[row] server_ip = server_info['ip'] jump_ip = server_info.get('jump_ip', '') remark = server_info.get('remark', '') # 检查是否已打开该服务器的监控 for i in range(1, self.tab_widget.count()): if self.tab_info.get(i, {}).get('ip') == server_ip: self.tab_widget.setCurrentIndex(i) self.ensure_tab_visible(i) return # 创建进度对话框 progress = QProgressDialog(f"正在连接服务器 {server_ip}...", "取消", 0, 0, self) progress.setWindowModality(Qt.WindowModal) progress.setCancelButton(None) progress.show() try: # 从连接池获取SSH连接 ssh_manager = self.get_ssh_connection(server_info) # 创建监控窗口 monitor_window = detail_page.SystemMonitorUI(server_info, ssh_manager) # 添加为标签页 tab_index = self.tab_widget.addTab(monitor_window, self.generate_tab_name(server_info)) # 存储标签页信息 self.tab_info[tab_index] = { 'ip': server_info['ip'], 'jump_ip': server_info.get('jump_ip', ''), 'remark': server_info.get('remark', ''), 'ssh_manager': ssh_manager, 'monitor_window': monitor_window } # 切换到新标签页 self.tab_widget.setCurrentIndex(tab_index) self.ensure_tab_visible(tab_index) logger.info(f"成功打开监控窗口: {server_info['ip']}") progress.close() except Exception as e: progress.close() QMessageBox.critical(self, "连接失败", f"连接失败: {str(e)}") logger.error(f"打开监控窗口失败: {str(e)}") def close_tab(self, index): """关闭标签页(优化连接管理)""" if index == 0: # 防止关闭服务器管理标签 return # 关闭SSH连接(但不从连接池移除) tab_data = self.tab_info.get(index) if tab_data and tab_data.get('ssh_manager'): # 停止心跳但保持连接在池中 tab_data['ssh_manager'].stop_heartbeat() # 移除标签页 self.tab_widget.removeTab(index) # 移除存储的信息 if index in self.tab_info: del self.tab_info[index] # 更新剩余标签页的索引 new_tab_info = {} for i in range(1, self.tab_widget.count()): if i >= index: new_tab_info[i] = self.tab_info.get(i+1, {}) else: new_tab_info[i] = self.tab_info.get(i, {}) self.tab_info = new_tab_info def closeEvent(self, event): """窗口关闭时清理所有连接""" # 关闭所有SSH连接 for key, ssh_manager in self.ssh_connection_pool.items(): try: ssh_manager.disconnect() logger.info(f"已关闭SSH连接: {key}") except: logger.error(f"关闭SSH连接失败: {key}") # 清空连接池 self.ssh_connection_pool.clear() # 调用父类方法 super().closeEvent(event) # 其他方法保持不变... ``` ### 优化说明 #### 1. `detail_page.py` 优化 - **线程池管理**:使用 `QThreadPool` 管理上传任务,限制同时上传数量 - **MD5文件校验**:添加文件上传前后的MD5校验 - **任务信号解耦**:使用 `UploadSignals` 类统一管理信号 - **自动清理**:`setAutoDelete(True)` 确保任务完成后自动释放资源 - **日志增强**:增加详细的MD5校验日志 #### 2. `server_manager.py` 优化 - **SSH连接池**:实现简单的连接池管理,复用活跃连接 - **连接有效性检查**:获取连接时验证连接是否有效 - **连接生命周期管理**: - 标签页关闭时只停止心跳,保持连接在池中 - 主窗口关闭时清理所有连接 - **异常处理优化**:统一处理连接异常 这些优化显著提升了系统的性能和可靠性,特别是在多服务器管理和大文件传输场景下。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值