#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 玩玩云上传工具 v3.0 支持本地存储和 OSS 云存储 """ import sys import os import json import requests import hashlib import time from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QVBoxLayout, QWidget, QProgressBar, QTextEdit, QPushButton) from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtGui import QDragEnterEvent, QDropEvent, QFont class UploadThread(QThread): """上传线程 - 支持 OSS 和本地存储""" progress = pyqtSignal(int, str) # 进度,状态信息 finished = pyqtSignal(bool, str) # 成功/失败,消息 def __init__(self, api_config, file_path, remote_path): super().__init__() self.api_config = api_config self.file_path = file_path self.remote_path = remote_path def run(self): try: filename = os.path.basename(self.file_path) self.progress.emit(10, f'正在准备上传: {filename}') # 使用服务器 API 上传 api_base_url = self.api_config['api_base_url'] api_key = self.api_config['api_key'] # 读取文件 with open(self.file_path, 'rb') as f: file_data = f.read() file_size = len(file_data) self.progress.emit(20, f'文件大小: {file_size / (1024*1024):.2f} MB') # 分块上传(支持大文件) chunk_size = 5 * 1024 * 1024 # 5MB 每块 uploaded = 0 # 使用 multipart/form-data 上传 files = { 'file': (filename, file_data) } data = { 'path': self.remote_path } headers = { 'X-API-Key': api_key } self.progress.emit(30, f'开始上传...') # 带进度的上传 response = requests.post( f"{api_base_url}/api/upload", files=files, data=data, headers=headers, timeout=300 # 5分钟超时 ) uploaded = file_size self.progress.emit(90, f'上传完成,等待服务器确认...') if response.status_code == 200: result = response.json() if result.get('success'): self.progress.emit(100, f'上传完成!') self.finished.emit(True, f'文件 {filename} 上传成功!') else: self.finished.emit(False, f'上传失败: {result.get("message", "未知错误")}') else: self.finished.emit(False, f'服务器错误: {response.status_code}') except requests.exceptions.Timeout: self.finished.emit(False, '上传超时,请检查网络连接') except requests.exceptions.ConnectionError: self.finished.emit(False, '无法连接到服务器,请检查网络') except Exception as e: self.finished.emit(False, f'上传失败: {str(e)}') class ConfigCheckThread(QThread): """配置检查线程""" result = pyqtSignal(bool, str, object) # 成功/失败,消息,配置信息 def __init__(self, api_base_url, api_key): super().__init__() self.api_base_url = api_base_url self.api_key = api_key def run(self): try: response = requests.post( f"{self.api_base_url}/api/upload/get-config", json={'api_key': self.api_key}, timeout=10 ) if response.status_code == 200: data = response.json() if data['success']: config = data['config'] storage_type = config.get('storage_type', 'unknown') if storage_type == 'oss': # OSS 云存储 provider = config.get('oss_provider', '未知') bucket = config.get('oss_bucket', '未知') msg = f'已连接 - OSS存储 ({provider}) | Bucket: {bucket}' else: # 本地存储 msg = f'已连接 - 本地存储' self.result.emit(True, msg, config) else: self.result.emit(False, data.get('message', '获取配置失败'), None) else: self.result.emit(False, f'服务器错误: {response.status_code}', None) except requests.exceptions.Timeout: self.result.emit(False, '连接超时,请检查网络', None) except requests.exceptions.ConnectionError: self.result.emit(False, '无法连接到服务器', None) except Exception as e: self.result.emit(False, f'连接失败: {str(e)}', None) class UploadWindow(QMainWindow): def __init__(self): super().__init__() self.config = self.load_config() self.server_config = None self.remote_path = '/' # 默认上传目录 self.upload_queue = [] # 上传队列 self.is_uploading = False # 是否正在上传 self.initUI() self.check_config() def load_config(self): """加载配置文件""" try: # PyInstaller打包后使用sys._MEIPASS if getattr(sys, 'frozen', False): # 打包后的exe base_path = os.path.dirname(sys.executable) else: # 开发环境 base_path = os.path.dirname(__file__) config_path = os.path.join(base_path, 'config.json') if not os.path.exists(config_path): from PyQt5.QtWidgets import QMessageBox QMessageBox.critical(None, '错误', f'找不到配置文件: {config_path}\n\n请确保config.json与程序在同一目录下!') sys.exit(1) with open(config_path, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: from PyQt5.QtWidgets import QMessageBox QMessageBox.critical(None, '错误', f'加载配置失败:\n{str(e)}') sys.exit(1) def check_config(self): """检查服务器配置""" self.log('正在连接服务器...') self.check_thread = ConfigCheckThread( self.config['api_base_url'], self.config['api_key'] ) self.check_thread.result.connect(self.on_config_result) self.check_thread.start() def on_config_result(self, success, message, config): """处理配置检查结果""" if success: self.server_config = config self.log(f'✓ {message}') # 更新状态显示 if config.get('storage_type') == 'oss': provider_name = { 'aliyun': '阿里云OSS', 'tencent': '腾讯云COS', 'aws': 'AWS S3' }.get(config.get('oss_provider'), config.get('oss_provider', 'OSS')) self.status_label.setText( f'

玩玩云上传工具 v3.0

' f'

✓ 已连接 - {provider_name}

' f'

拖拽文件到此处上传

' f'

存储桶: {config.get("oss_bucket", "未知")}

' ) else: self.status_label.setText( f'

玩玩云上传工具 v3.0

' f'

✓ 已连接 - 本地存储

' f'

拖拽文件到此处上传

' ) else: self.log(f'✗ {message}') self.show_error(message) def show_error(self, message): """显示错误信息""" self.status_label.setText( f'

玩玩云上传工具 v3.0

' f'

✗ 错误: {message}

' f'

请检查网络连接或联系管理员

' ) def initUI(self): """初始化界面""" self.setWindowTitle('玩玩云上传工具 v3.0') self.setGeometry(300, 300, 500, 450) # 设置接受拖拽 self.setAcceptDrops(True) # 中心部件 central_widget = QWidget() self.setCentralWidget(central_widget) # 布局 layout = QVBoxLayout() # 状态标签 self.status_label = QLabel('正在连接服务器...') self.status_label.setAlignment(Qt.AlignCenter) self.status_label.setFont(QFont('Arial', 11)) self.status_label.setWordWrap(True) self.status_label.setStyleSheet('padding: 20px;') layout.addWidget(self.status_label) # 拖拽提示区域 self.drop_area = QLabel('📁\n\n支持多文件和文件夹') self.drop_area.setAlignment(Qt.AlignCenter) self.drop_area.setStyleSheet(""" QLabel { font-size: 50px; color: #667eea; border: 3px dashed #667eea; border-radius: 10px; background-color: #f5f7fa; padding: 40px; } """) layout.addWidget(self.drop_area) # 队列状态标签 self.queue_label = QLabel('队列: 0 个文件等待上传') self.queue_label.setAlignment(Qt.AlignCenter) self.queue_label.setStyleSheet('color: #2c3e50; font-weight: bold; padding: 5px;') layout.addWidget(self.queue_label) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setValue(0) self.progress_bar.setVisible(False) self.progress_bar.setStyleSheet(""" QProgressBar { border: 2px solid #e0e0e0; border-radius: 5px; text-align: center; height: 25px; } QProgressBar::chunk { background-color: #667eea; } """) layout.addWidget(self.progress_bar) # 进度信息 self.progress_label = QLabel('') self.progress_label.setAlignment(Qt.AlignCenter) self.progress_label.setVisible(False) layout.addWidget(self.progress_label) # 日志区域 self.log_text = QTextEdit() self.log_text.setReadOnly(True) self.log_text.setMaximumHeight(100) self.log_text.setStyleSheet(""" QTextEdit { background-color: #f9f9f9; border: 1px solid #e0e0e0; border-radius: 5px; padding: 5px; font-family: 'Courier New', monospace; font-size: 11px; } """) layout.addWidget(self.log_text) central_widget.setLayout(layout) self.log('程序已启动 - 版本 v3.0 (支持OSS云存储)') def log(self, message): """添加日志""" self.log_text.append(f'[{self.get_time()}] {message}') # 自动滚动到底部 self.log_text.verticalScrollBar().setValue( self.log_text.verticalScrollBar().maximum() ) def get_time(self): """获取当前时间""" from datetime import datetime return datetime.now().strftime('%H:%M:%S') def dragEnterEvent(self, event: QDragEnterEvent): """拖拽进入事件""" if event.mimeData().hasUrls(): event.acceptProposedAction() self.drop_area.setStyleSheet(""" QLabel { font-size: 50px; color: #667eea; border: 3px dashed #667eea; border-radius: 10px; background-color: #e8ecf7; padding: 40px; } """) def dragLeaveEvent(self, event): """拖拽离开事件""" self.drop_area.setStyleSheet(""" QLabel { font-size: 50px; color: #667eea; border: 3px dashed #667eea; border-radius: 10px; background-color: #f5f7fa; padding: 40px; } """) def dropEvent(self, event: QDropEvent): """拖拽放下事件""" self.drop_area.setStyleSheet(""" QLabel { font-size: 50px; color: #667eea; border: 3px dashed #667eea; border-radius: 10px; background-color: #f5f7fa; padding: 40px; } """) if not self.server_config: self.log('错误: 未连接到服务器,请等待连接完成') self.show_error('服务器未连接,请稍后重试') return paths = [url.toLocalFile() for url in event.mimeData().urls()] all_files = [] for path in paths: if os.path.isfile(path): all_files.append(path) elif os.path.isdir(path): self.log(f'扫描文件夹: {os.path.basename(path)}') folder_files = self.scan_folder(path) all_files.extend(folder_files) self.log(f'找到 {len(folder_files)} 个文件') if all_files: self.upload_queue.extend(all_files) self.update_queue_label() self.log(f'添加 {len(all_files)} 个文件到上传队列') if not self.is_uploading: self.process_upload_queue() def scan_folder(self, folder_path): """递归扫描文件夹""" files = [] try: for root, dirs, filenames in os.walk(folder_path): for filename in filenames: file_path = os.path.join(root, filename) files.append(file_path) except Exception as e: self.log(f'扫描文件夹失败: {str(e)}') return files def update_queue_label(self): """更新队列标签""" count = len(self.upload_queue) self.queue_label.setText(f'队列: {count} 个文件等待上传') def process_upload_queue(self): """处理上传队列""" if not self.upload_queue: self.is_uploading = False self.update_queue_label() self.log('✓ 所有文件上传完成!') return self.is_uploading = True file_path = self.upload_queue.pop(0) self.update_queue_label() self.upload_file(file_path) def upload_file(self, file_path): """上传文件""" filename = os.path.basename(file_path) # 构建远程路径 if self.remote_path == '/': remote_path = f'/{filename}' else: remote_path = f'{self.remote_path}/{filename}' self.log(f'开始上传: {filename}') # 显示进度控件 self.progress_bar.setVisible(True) self.progress_bar.setValue(0) self.progress_label.setVisible(True) self.progress_label.setText('准备上传...') # 创建上传线程 api_config = { 'api_base_url': self.config['api_base_url'], 'api_key': self.config['api_key'] } self.upload_thread = UploadThread(api_config, file_path, remote_path) self.upload_thread.progress.connect(self.on_progress) self.upload_thread.finished.connect(self.on_finished) self.upload_thread.start() def on_progress(self, value, message): """上传进度更新""" self.progress_bar.setValue(value) self.progress_label.setText(message) def on_finished(self, success, message): """上传完成""" self.log(message) if success: self.progress_label.setText('✓ ' + message) self.progress_label.setStyleSheet('color: green; font-weight: bold;') else: self.progress_label.setText('✗ ' + message) self.progress_label.setStyleSheet('color: red; font-weight: bold;') # 继续处理队列 from PyQt5.QtCore import QTimer QTimer.singleShot(1000, self.process_upload_queue) def main(): app = QApplication(sys.argv) window = UploadWindow() window.show() sys.exit(app.exec_()) if __name__ == '__main__': main()