diff --git a/banbenjietu.png b/banbenjietu.png index bb45f02..c090d6e 100644 Binary files a/banbenjietu.png and b/banbenjietu.png differ diff --git a/gui/main_window.py b/gui/main_window.py index 680a2a2..fc0af90 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -14,6 +14,7 @@ import time sys.path.append(str(Path(__file__).parent.parent)) from utils.config import Config +from utils.version_manager import VersionManager from account_switcher import AccountSwitcher def get_version(): @@ -130,11 +131,33 @@ class ApiWorker(QThread): except Exception as e: self.finished.emit((False, str(e))) +class UpdateWorker(QThread): + """更新检查工作线程""" + progress = pyqtSignal(str) # 发送进度信息 + finished = pyqtSignal(tuple) # 发送结果信号 + + def __init__(self, version_manager): + super().__init__() + self.version_manager = version_manager + + def run(self): + try: + self.progress.emit("正在检查更新...") + has_update, is_force, version_info = self.version_manager.needs_update() + + if has_update and version_info: + self.finished.emit((True, is_force, version_info)) + else: + self.finished.emit((False, False, None)) + except Exception as e: + self.finished.emit((False, False, str(e))) + class MainWindow(QMainWindow): def __init__(self): super().__init__() self.config = Config() self.switcher = AccountSwitcher() + self.version_manager = VersionManager() # 添加激活状态缓存 self._activation_status = None # 缓存的激活状态 @@ -148,7 +171,7 @@ class MainWindow(QMainWindow): version = get_version() cursor_version = self.switcher.get_cursor_version() self.setWindowTitle(f"听泉Cursor助手 v{version} (本机Cursor版本: {cursor_version})") - self.setMinimumSize(600, 500) + self.setMinimumSize(600, 680) # 增加最小宽度到600 # 设置窗口图标 icon_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "icon", "two.ico") @@ -175,9 +198,31 @@ class MainWindow(QMainWindow): stop:0.5 #ffffff, stop:1 #f8f9fa); } - QLabel { - color: #495057; + QFrame { + border: none; + background: transparent; } + """) + + # 创建主布局 + main_layout = QVBoxLayout(central_widget) + main_layout.setSpacing(12) # 减小区域间距 + main_layout.setContentsMargins(20, 15, 20, 15) # 调整外边距 + + # 设备ID区域 + device_frame = QFrame() + device_frame.setStyleSheet(""" + QFrame { + background: transparent; + padding: 8px; + } + """) + device_layout = QHBoxLayout(device_frame) + device_layout.setContentsMargins(0, 0, 0, 0) + device_layout.addWidget(QLabel("设备识别码(勿动):")) + self.hardware_id_edit = QLineEdit(self.switcher.hardware_id) + self.hardware_id_edit.setReadOnly(True) + self.hardware_id_edit.setStyleSheet(""" QLineEdit { background-color: #ffffff; border: 1px solid #ced4da; @@ -185,27 +230,8 @@ class MainWindow(QMainWindow): padding: 5px; color: #495057; } - QTextEdit { - background-color: #ffffff; - border: 1px solid #ced4da; - border-radius: 4px; - padding: 5px; - color: #495057; - } """) - - # 创建主布局 - main_layout = QVBoxLayout(central_widget) - main_layout.setSpacing(15) - main_layout.setContentsMargins(30, 30, 30, 30) - - # 设备ID区域 - device_layout = QHBoxLayout() - device_layout.addWidget(QLabel("设备识别码(勿动):")) - self.hardware_id_edit = QLineEdit(self.switcher.hardware_id) - self.hardware_id_edit.setReadOnly(True) device_layout.addWidget(self.hardware_id_edit) - copy_btn = QPushButton("复制ID") copy_btn.setStyleSheet(""" QPushButton { @@ -221,67 +247,121 @@ class MainWindow(QMainWindow): """) copy_btn.clicked.connect(self.copy_device_id) device_layout.addWidget(copy_btn) - main_layout.addLayout(device_layout) - - # 添加分隔线 - line = QFrame() - line.setFrameShape(QFrame.HLine) - line.setStyleSheet("background-color: #e9ecef;") - main_layout.addWidget(line) + main_layout.addWidget(device_frame) # 会员状态区域 - status_label = QLabel("会员状态") - status_label.setStyleSheet("font-weight: bold; margin-top: 10px;") - main_layout.addWidget(status_label) - + status_frame = QFrame() + status_frame.setStyleSheet(""" + QFrame { + background: transparent; + padding: 8px; + } + QLabel { + color: #495057; + font-weight: bold; + } + QTextEdit { + background-color: #ffffff; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 5px; + color: #495057; + } + QTextEdit QScrollBar:vertical { + width: 8px; + background: transparent; + margin: 0px; + } + QTextEdit QScrollBar::handle:vertical { + background: #ced4da; + min-height: 30px; + border-radius: 4px; + } + QTextEdit QScrollBar::handle:vertical:hover { + background: #adb5bd; + } + QTextEdit QScrollBar::add-line:vertical, + QTextEdit QScrollBar::sub-line:vertical { + height: 0px; + } + QTextEdit QScrollBar::add-page:vertical, + QTextEdit QScrollBar::sub-page:vertical { + background: none; + } + """) + status_layout = QVBoxLayout(status_frame) + status_layout.setContentsMargins(0, 0, 0, 0) + status_layout.addWidget(QLabel("会员状态")) self.status_text = QTextEdit() self.status_text.setReadOnly(True) self.status_text.setMinimumHeight(100) - main_layout.addWidget(self.status_text) - - # 添加分隔线 - line2 = QFrame() - line2.setFrameShape(QFrame.HLine) - line2.setStyleSheet("background-color: #e9ecef;") - main_layout.addWidget(line2) + status_layout.addWidget(self.status_text) + main_layout.addWidget(status_frame) # 激活区域 - main_layout.addWidget(QLabel("激活(叠加)会员,多个激活码可叠加整体时长")) + activation_frame = QFrame() + activation_frame.setStyleSheet(""" + QFrame { + background: transparent; + padding: 12px 0; + } + """) + activation_layout = QVBoxLayout(activation_frame) + activation_layout.setContentsMargins(0, 0, 0, 0) # 激活码输入区域 - input_layout = QHBoxLayout() - input_layout.addWidget(QLabel("激活码:")) + activation_layout.addWidget(QLabel("激活(叠加)会员,多个激活码可叠加整体时长")) + input_frame = QFrame() + input_layout = QHBoxLayout(input_frame) + input_layout.setSpacing(10) + input_layout.setContentsMargins(0, 0, 0, 0) + self.activation_edit = QLineEdit() + self.activation_edit.setPlaceholderText("请输入激活码") + self.activation_edit.setMinimumWidth(350) # 增加输入框宽度 + self.activation_edit.setFixedHeight(35) + self.activation_edit.setStyleSheet(""" + QLineEdit { + background-color: #ffffff; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 8px; + color: #495057; + font-size: 13px; + } + QLineEdit:focus { + border-color: #86b7fe; + } + """) input_layout.addWidget(self.activation_edit) + # 激活按钮 activate_btn = QPushButton("激活") + activate_btn.setFixedWidth(100) + activate_btn.setFixedHeight(35) activate_btn.setStyleSheet(""" QPushButton { background-color: #0d6efd; color: white; border: none; - padding: 8px 25px; border-radius: 4px; font-size: 13px; - min-width: 80px; } QPushButton:hover { background-color: #0b5ed7; } - QPushButton:pressed { - background-color: #0a58ca; - } """) activate_btn.clicked.connect(self.activate_account) input_layout.addWidget(activate_btn) - main_layout.addLayout(input_layout) + + activation_layout.addWidget(input_frame) # 使用说明 usage_label = QLabel() usage_text = ( - "
" - "

使用步骤说明:

" - "

" + "

" + "

使用步骤说明:

" + "

" "第一步: " "输入激活码点击【激活】按钮完成激活
" @@ -301,68 +381,117 @@ class MainWindow(QMainWindow): QLabel { color: #333333; font-size: 13px; - padding: 0px; - margin: 10px 0; + background: transparent; } """) usage_label.setTextFormat(Qt.RichText) usage_label.setWordWrap(True) - main_layout.addWidget(usage_label) + activation_layout.addWidget(usage_label) + + main_layout.addWidget(activation_frame) # 操作按钮区域 btn_frame = QFrame() btn_layout = QVBoxLayout(btn_frame) + btn_layout.setSpacing(15) # 增加按钮间距 + btn_layout.setContentsMargins(0, 10, 0, 10) - # 设置按钮样式 + # 按钮基础样式 button_style = """ QPushButton { - background-color: #0d6efd; color: white; border: none; - padding: 15px; border-radius: 6px; - font-size: 13px; - min-width: 300px; - margin: 5px; + font-size: 14px; + font-weight: 500; + min-width: 500px; + } + """ + + # 刷新授权按钮(主按钮) + refresh_btn = QPushButton("刷新 Cursor 编辑器授权") + refresh_btn.setStyleSheet(button_style + """ + QPushButton { + background-color: #0d6efd; + padding: 15px; } QPushButton:hover { background-color: #0b5ed7; } - QPushButton:pressed { - background-color: #0a58ca; - } - """ - - # 刷新授权按钮 - refresh_btn = QPushButton("刷新 Cursor 编辑器授权") - refresh_btn.setStyleSheet(button_style) + """) refresh_btn.clicked.connect(self.refresh_cursor_auth) - refresh_btn.setMinimumHeight(50) - btn_layout.addWidget(refresh_btn) + refresh_btn.setMinimumHeight(60) + refresh_btn.setMinimumWidth(500) + btn_layout.addWidget(refresh_btn, 0, Qt.AlignCenter) # 突破限制按钮 bypass_btn = QPushButton("突破 Cursor 0.45.x 限制") - bypass_btn.setStyleSheet(button_style.replace("#0d6efd", "#198754").replace("#0b5ed7", "#157347").replace("#0a58ca", "#146c43")) + bypass_btn.setStyleSheet(button_style + """ + QPushButton { + background-color: #198754; + padding: 12px; + } + QPushButton:hover { + background-color: #157347; + } + """) bypass_btn.clicked.connect(self.bypass_cursor_limit) bypass_btn.setMinimumHeight(50) - btn_layout.addWidget(bypass_btn) + bypass_btn.setMinimumWidth(500) + btn_layout.addWidget(bypass_btn, 0, Qt.AlignCenter) # 禁用更新按钮 disable_update_btn = QPushButton("禁用 Cursor 版本更新") - disable_update_btn.setStyleSheet(button_style.replace("#0d6efd", "#dc3545").replace("#0b5ed7", "#bb2d3b").replace("#0a58ca", "#b02a37")) + disable_update_btn.setStyleSheet(button_style + """ + QPushButton { + background-color: #dc3545; + padding: 12px; + } + QPushButton:hover { + background-color: #bb2d3b; + } + """) disable_update_btn.clicked.connect(self.disable_cursor_update) disable_update_btn.setMinimumHeight(50) - btn_layout.addWidget(disable_update_btn) - - # 设置按钮间距 - btn_layout.setSpacing(10) - btn_layout.setContentsMargins(20, 10, 20, 10) + disable_update_btn.setMinimumWidth(500) + btn_layout.addWidget(disable_update_btn, 0, Qt.AlignCenter) main_layout.addWidget(btn_frame) + # 检查更新按钮 + update_frame = QFrame() + update_frame.setStyleSheet(""" + QFrame { + background: transparent; + padding: 5px; + } + """) + update_layout = QHBoxLayout(update_frame) + update_layout.setContentsMargins(0, 0, 0, 0) + check_update_btn = QPushButton("检查更新") + check_update_btn.setStyleSheet(""" + QPushButton { + background-color: #6c757d; + color: white; + border: none; + padding: 8px 20px; + border-radius: 4px; + font-size: 13px; + } + QPushButton:hover { + background-color: #5a6268; + } + """) + check_update_btn.clicked.connect(self.check_for_updates) + update_layout.addWidget(check_update_btn) + main_layout.addWidget(update_frame) + # 启动时检查一次状态 QTimer.singleShot(0, self.check_status) + # 启动时自动检查更新 + QTimer.singleShot(1000, self.check_for_updates) + def create_tray_icon(self): """创建系统托盘图标""" try: @@ -1324,4 +1453,203 @@ class MainWindow(QMainWindow): def _request_complete(self): """请求完成,重置状态""" - self._is_requesting = False \ No newline at end of file + self._is_requesting = False + + def check_for_updates(self): + """检查更新""" + if self._is_requesting: + return + + self._is_requesting = True + self.show_loading_dialog("正在检查更新...") + + self.update_worker = UpdateWorker(self.version_manager) + self.update_worker.progress.connect(lambda msg: self.loading_dialog.message_label.setText(msg)) + self.update_worker.finished.connect(self.on_update_check_complete) + self.update_worker.start() + + def on_update_check_complete(self, result): + """更新检查完成的回调""" + self.hide_loading_dialog() + self._is_requesting = False + + has_update, is_force, version_info = result + + if isinstance(version_info, str): # 发生错误 + self.show_custom_error("检查更新失败", f"检查更新时发生错误: {version_info}") + return + + if not has_update: + self.show_custom_message( + "检查更新", + "已是最新版本", + "您当前使用的已经是最新版本。", + QStyle.SP_DialogApplyButton, + "#198754" + ) + return + + # 显示更新信息 + msg = QDialog(self) + msg.setWindowTitle("发现新版本") + msg.setFixedWidth(500) + msg.setWindowFlags(msg.windowFlags() & ~Qt.WindowContextHelpButtonHint) + + layout = QVBoxLayout() + + # 添加图标 + icon_label = QLabel() + icon_label.setPixmap(self.style().standardIcon(QStyle.SP_MessageBoxInformation).pixmap(32, 32)) + icon_label.setAlignment(Qt.AlignCenter) + layout.addWidget(icon_label) + + # 添加标题 + title_label = QLabel("发现新版本") + title_label.setAlignment(Qt.AlignCenter) + title_label.setStyleSheet(""" + font-size: 16px; + font-weight: bold; + color: #0d6efd; + padding: 10px; + """) + layout.addWidget(title_label) + + # 版本信息 + version_frame = QFrame() + version_frame.setStyleSheet(""" + QFrame { + background-color: #f8f9fa; + border-radius: 8px; + border: 1px solid #dee2e6; + margin: 10px; + padding: 15px; + } + QLabel { + color: #333333; + font-size: 14px; + padding: 5px; + } + """) + version_layout = QVBoxLayout(version_frame) + + # 当前版本 + current_version_label = QLabel(f"当前版本:v{self.version_manager.current_version}") + version_layout.addWidget(current_version_label) + + # 最新版本 + new_version_label = QLabel(f"最新版本:v{version_info.get('version_no', '未知')} ({version_info.get('version_name', '未知')})") + new_version_label.setStyleSheet("font-weight: bold; color: #0d6efd;") + version_layout.addWidget(new_version_label) + + # 分割线 + line = QFrame() + line.setFrameShape(QFrame.HLine) + line.setStyleSheet("background-color: #dee2e6;") + version_layout.addWidget(line) + + # 更新说明 + desc_label = QLabel("更新说明:") + desc_label.setStyleSheet("font-weight: bold; padding-top: 10px;") + version_layout.addWidget(desc_label) + + desc_text = version_info.get('description', '无') + desc_content = QLabel(desc_text if desc_text else "无更新说明") + desc_content.setWordWrap(True) + desc_content.setStyleSheet("padding: 5px 10px;") + version_layout.addWidget(desc_content) + + # 强制更新提示 + if is_force: + force_label = QLabel("* 此更新为强制更新,请立即更新!") + force_label.setStyleSheet("color: #dc3545; font-weight: bold; padding-top: 10px;") + version_layout.addWidget(force_label) + + layout.addWidget(version_frame) + + # 按钮区域 + btn_layout = QHBoxLayout() + + # 暂不更新按钮 + if not is_force: + later_btn = QPushButton("暂不更新") + later_btn.setStyleSheet(""" + QPushButton { + background-color: #6c757d; + color: white; + border: none; + padding: 8px 20px; + border-radius: 4px; + font-size: 13px; + min-width: 100px; + } + QPushButton:hover { + background-color: #5a6268; + } + """) + later_btn.clicked.connect(msg.reject) + btn_layout.addWidget(later_btn) + + # 立即更新按钮 + update_btn = QPushButton("立即更新") + update_btn.setStyleSheet(""" + QPushButton { + background-color: #0d6efd; + color: white; + border: none; + padding: 8px 20px; + border-radius: 4px; + font-size: 13px; + min-width: 100px; + font-weight: bold; + } + QPushButton:hover { + background-color: #0b5ed7; + } + """) + update_btn.clicked.connect(msg.accept) + btn_layout.addWidget(update_btn) + + layout.addLayout(btn_layout) + msg.setLayout(layout) + + # 显示对话框 + if msg.exec_() == QDialog.Accepted: + self.download_update(version_info.get('download_url')) + + def download_update(self, download_url): + """下载更新""" + if not download_url: + self.show_custom_error("更新失败", "无法获取下载地址,请联系管理员") + return + + try: + # 创建下载目录 + download_dir = Path.home() / "Downloads" / "CursorHelper" + download_dir.mkdir(parents=True, exist_ok=True) + + # 下载文件名 + file_name = download_url.split('/')[-1] + save_path = download_dir / file_name + + self.show_loading_dialog("正在下载更新...") + + # 开始下载 + success, message = self.version_manager.download_update(download_url, str(save_path)) + self.hide_loading_dialog() + + if success: + self.show_custom_message( + "下载完成", + "更新包下载成功", + f"更新包已下载到:\n{save_path}\n\n请关闭程序后运行更新包完成更新。", + QStyle.SP_DialogApplyButton, + "#198754" + ) + # 退出程序 + self.quit_application() + else: + self.show_custom_error("下载失败", message) + + except Exception as e: + self.hide_loading_dialog() + self.show_custom_error("下载失败", f"下载更新时发生错误: {str(e)}") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 742fcca..672808c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ altgraph>=0.17.4 pyinstaller==6.3.0 pillow==10.2.0 PyQt5==5.15.10 -pywin32==306 \ No newline at end of file +pywin32==306 +packaging>=23.2 \ No newline at end of file diff --git a/test_version_manager.py b/test_version_manager.py new file mode 100644 index 0000000..e0006f6 --- /dev/null +++ b/test_version_manager.py @@ -0,0 +1,63 @@ +import logging +import sys +from pathlib import Path +from utils.version_manager import VersionManager + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(sys.stdout), + logging.FileHandler('version_check.log') + ] +) + +def test_version_manager(): + """测试版本管理器功能""" + try: + vm = VersionManager() + logging.info(f"当前版本: {vm.current_version}") + logging.info(f"当前平台: {vm.platform}") + + # 测试获取最新版本 + logging.info("\n=== 测试获取最新版本 ===") + latest = vm.get_latest_version() + logging.info(f"最新版本信息: {latest}") + + # 测试检查更新 + logging.info("\n=== 测试检查更新 ===") + update_info = vm.check_update() + logging.info(f"更新检查结果: {update_info}") + + # 测试是否需要更新 + logging.info("\n=== 测试是否需要更新 ===") + has_update, is_force, version_info = vm.needs_update() + logging.info(f"是否有更新: {has_update}") + logging.info(f"是否强制更新: {is_force}") + logging.info(f"版本信息: {version_info}") + + # 如果有更新,测试下载功能 + if has_update and version_info: + logging.info("\n=== 测试下载更新 ===") + download_url = version_info.get('download_url') + if download_url: + save_path = Path.home() / "Downloads" / "CursorHelper" / "test_update.exe" + save_path.parent.mkdir(parents=True, exist_ok=True) + + logging.info(f"下载地址: {download_url}") + logging.info(f"保存路径: {save_path}") + + success = vm.download_update(download_url, str(save_path)) + logging.info(f"下载结果: {'成功' if success else '失败'}") + + if success: + logging.info(f"文件大小: {save_path.stat().st_size} 字节") + else: + logging.warning("未找到下载地址") + + except Exception as e: + logging.error(f"测试过程中发生错误: {str(e)}", exc_info=True) + +if __name__ == "__main__": + test_version_manager() \ No newline at end of file diff --git a/utils/version_manager.py b/utils/version_manager.py new file mode 100644 index 0000000..4c732c7 --- /dev/null +++ b/utils/version_manager.py @@ -0,0 +1,265 @@ +import os +import sys +import requests +from packaging import version +from typing import Optional, Dict, Any +import json +import logging +from urllib.parse import quote, unquote + +class VersionManager: + """版本管理器 + + 错误码说明: + - 0: 成功 + - 1: 一般性错误 + - 401: 未授权或授权失败 + - 404: 请求的资源不存在 + - 500: 服务器内部错误 + """ + + def __init__(self): + self.base_url = "https://cursorapi.nosqli.com" + self.current_version = self._get_current_version() + self.platform = "windows" if sys.platform.startswith("win") else "mac" if sys.platform.startswith("darwin") else "linux" + + def _get_current_version(self) -> str: + """获取当前版本号""" + try: + with open("version.txt", "r") as f: + return f.read().strip() + except FileNotFoundError: + return "0.0.0" + + def _handle_response(self, response: requests.Response) -> Dict[str, Any]: + """处理API响应 + + Args: + response: API响应对象 + + Returns: + Dict[str, Any]: 处理后的响应数据 + + Raises: + Exception: API调用失败时抛出异常 + """ + try: + data = response.json() + code = data.get("code") + msg = data.get("msg") or data.get("info", "未知错误") # 兼容 info 字段 + + if code == 0 or code == 1: # 兼容 code=1 的情况 + # 处理空数据情况 + if not data.get("data"): + logging.warning("API返回空数据") + return { + "code": 0, + "msg": msg, + "data": { + "has_update": False, + "is_force": False, + "version_info": None + } + } + return { + "code": 0, # 统一返回 code=0 + "msg": msg, + "data": data.get("data") + } + elif code == 401: + raise Exception("未授权或授权失败") + elif code == 404: + raise Exception("请求的资源不存在") + elif code == 500: + raise Exception("服务器内部错误") + else: + raise Exception(msg) + + except requests.exceptions.JSONDecodeError: + raise Exception("服务器响应格式错误") + + def check_update(self) -> Dict[str, Any]: + """检查是否有更新""" + try: + url = f"{self.base_url}/admin/api.version/check" + params = { + "version": self.current_version, + "platform": self.platform + } + logging.info(f"正在请求: {url}") + logging.info(f"参数: {params}") + + response = requests.get( + url, + params=params, + timeout=10 + ) + + logging.info(f"状态码: {response.status_code}") + logging.info(f"响应头: {dict(response.headers)}") + logging.info(f"响应内容: {response.text}") + + return self._handle_response(response) + except requests.exceptions.Timeout: + logging.error("检查更新超时") + return {"code": -1, "msg": "请求超时,请检查网络连接", "data": None} + except requests.exceptions.ConnectionError as e: + logging.error(f"检查更新连接失败: {str(e)}") + return {"code": -1, "msg": "连接服务器失败,请检查网络连接", "data": None} + except Exception as e: + logging.error(f"检查更新失败: {str(e)}") + return {"code": -1, "msg": str(e), "data": None} + + def get_latest_version(self) -> Dict[str, Any]: + """获取最新版本信息""" + try: + url = f"{self.base_url}/admin/api.version/latest" + params = {"platform": self.platform} + logging.info(f"正在请求: {url}") + logging.info(f"参数: {params}") + + response = requests.get( + url, + params=params, + timeout=10 + ) + + logging.info(f"状态码: {response.status_code}") + logging.info(f"响应头: {dict(response.headers)}") + logging.info(f"响应内容: {response.text}") + + return self._handle_response(response) + except requests.exceptions.Timeout: + logging.error("获取最新版本超时") + return {"code": -1, "msg": "请求超时,请检查网络连接", "data": None} + except requests.exceptions.ConnectionError as e: + logging.error(f"获取最新版本连接失败: {str(e)}") + return {"code": -1, "msg": "连接服务器失败,请检查网络连接", "data": None} + except Exception as e: + logging.error(f"获取最新版本失败: {str(e)}") + return {"code": -1, "msg": str(e), "data": None} + + def needs_update(self) -> tuple[bool, bool, Optional[Dict[str, Any]]]: + """检查是否需要更新 + + Returns: + tuple: (是否有更新, 是否强制更新, 版本信息) + """ + result = self.check_update() + if result["code"] == 0 and result["data"]: + data = result["data"] + return ( + data["has_update"], + bool(data.get("is_force")), + data.get("version_info") + ) + return False, False, None + + def download_update(self, download_url: str, save_path: str) -> tuple[bool, str]: + """下载更新文件 + + Args: + download_url: 下载地址 + save_path: 保存路径 + + Returns: + tuple[bool, str]: (是否下载成功, 错误信息) + """ + try: + if not download_url: + error_msg = "下载地址为空,请联系管理员" + logging.error(error_msg) + return False, error_msg + + # 处理下载地址中的中文字符 + url_parts = download_url.split('/') + # 只对最后一部分(文件名)进行编码 + url_parts[-1] = quote(url_parts[-1]) + encoded_url = '/'.join(url_parts) + + logging.info(f"原始下载地址: {download_url}") + logging.info(f"编码后的地址: {encoded_url}") + + # 设置请求头,模拟浏览器行为 + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', + 'Accept': '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + 'Connection': 'keep-alive' + } + + response = requests.get( + encoded_url, + stream=True, + headers=headers, + timeout=30 # 增加下载超时时间 + ) + + # 检查响应状态 + if response.status_code == 404: + error_msg = "下载地址无效,请联系管理员更新下载地址" + logging.error(error_msg) + return False, error_msg + + response.raise_for_status() + + total_size = int(response.headers.get('content-length', 0)) + if total_size == 0: + error_msg = "无法获取文件大小,下载地址可能无效,请联系管理员" + logging.error(error_msg) + return False, error_msg + + block_size = 8192 + downloaded_size = 0 + + logging.info(f"开始下载文件,总大小: {total_size} 字节") + + with open(save_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=block_size): + if chunk: + f.write(chunk) + downloaded_size += len(chunk) + # 打印下载进度 + if total_size > 0: + progress = (downloaded_size / total_size) * 100 + logging.info(f"下载进度: {progress:.2f}%") + + # 验证文件大小 + actual_size = os.path.getsize(save_path) + if actual_size != total_size: + error_msg = f"文件下载不完整: 预期{total_size}字节,实际{actual_size}字节,请重试或联系管理员" + logging.error(error_msg) + # 删除不完整文件 + try: + os.remove(save_path) + logging.info(f"已删除不完整的下载文件: {save_path}") + except Exception as clean_e: + logging.error(f"清理不完整文件失败: {str(clean_e)}") + return False, error_msg + + logging.info(f"文件下载完成: {save_path}") + return True, "下载成功" + + except requests.exceptions.Timeout: + error_msg = "下载超时,请检查网络连接后重试" + logging.error(error_msg) + return False, error_msg + except requests.exceptions.ConnectionError as e: + error_msg = "下载连接失败,请检查网络连接后重试" + logging.error(f"{error_msg}: {str(e)}") + return False, error_msg + except requests.exceptions.HTTPError as e: + error_msg = f"下载地址无效或服务器错误,请联系管理员 (HTTP {e.response.status_code})" + logging.error(error_msg) + return False, error_msg + except Exception as e: + error_msg = f"下载失败,请联系管理员: {str(e)}" + logging.error(error_msg) + # 如果下载失败,删除可能存在的不完整文件 + try: + if os.path.exists(save_path): + os.remove(save_path) + logging.info(f"已删除不完整的下载文件: {save_path}") + except Exception as clean_e: + logging.error(f"清理不完整文件失败: {str(clean_e)}") + return False, error_msg \ No newline at end of file diff --git a/version.txt b/version.txt index fbcbf73..a423d42 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.4.0 \ No newline at end of file +3.4.2 \ No newline at end of file diff --git a/version_check.log b/version_check.log new file mode 100644 index 0000000..774d54a --- /dev/null +++ b/version_check.log @@ -0,0 +1,127 @@ +2025-02-13 13:30:48,255 - INFO - ǰ汾: 3.4.1 +2025-02-13 13:30:48,255 - INFO - ǰƽ̨: windows +2025-02-13 13:30:48,255 - INFO - +=== Իȡ°汾 === +2025-02-13 13:30:49,989 - INFO - °汾Ϣ: {'code': 0, 'info': 'ް汾Ϣ', 'data': {}} +2025-02-13 13:30:49,989 - INFO - +=== Լ === +2025-02-13 13:30:51,712 - ERROR - ʧ: δ֪ +2025-02-13 13:30:51,713 - INFO - ¼: {'code': -1, 'msg': 'δ֪', 'data': None} +2025-02-13 13:30:51,713 - INFO - +=== ǷҪ === +2025-02-13 13:30:53,394 - ERROR - ʧ: δ֪ +2025-02-13 13:30:53,395 - INFO - Ƿи: False +2025-02-13 13:30:53,395 - INFO - ǷǿƸ: False +2025-02-13 13:30:53,395 - INFO - 汾Ϣ: None +2025-02-13 13:49:13,952 - INFO - ǰ汾: 3.4.1 +2025-02-13 13:49:13,952 - INFO - ǰƽ̨: windows +2025-02-13 13:49:13,952 - INFO - +=== Իȡ°汾 === +2025-02-13 13:49:15,718 - ERROR - ȡ°汾ʧ: δ֪ +2025-02-13 13:49:15,720 - INFO - °汾Ϣ: {'code': -1, 'msg': 'δ֪', 'data': None} +2025-02-13 13:49:15,720 - INFO - +=== Լ === +2025-02-13 13:49:17,452 - ERROR - ʧ: δ֪ +2025-02-13 13:49:17,454 - INFO - ¼: {'code': -1, 'msg': 'δ֪', 'data': None} +2025-02-13 13:49:17,454 - INFO - +=== ǷҪ === +2025-02-13 13:49:19,277 - ERROR - ʧ: δ֪ +2025-02-13 13:49:19,278 - INFO - Ƿи: False +2025-02-13 13:49:19,278 - INFO - ǷǿƸ: False +2025-02-13 13:49:19,278 - INFO - 汾Ϣ: None +2025-02-13 13:53:02,577 - INFO - ǰ汾: 3.4.1 +2025-02-13 13:53:02,577 - INFO - ǰƽ̨: windows +2025-02-13 13:53:02,577 - INFO - +=== Իȡ°汾 === +2025-02-13 13:53:02,578 - INFO - : https://cursorapi.nosqli.com/admin/api.version/latest +2025-02-13 13:53:02,578 - INFO - : {'platform': 'windows'} +2025-02-13 13:53:04,292 - INFO - ״̬: 200 +2025-02-13 13:53:04,292 - INFO - Ӧͷ: {'Server': 'nginx', 'Date': 'Thu, 13 Feb 2025 05:53:02 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-Frame-Options': 'sameorigin', 'Set-Cookie': 'ssid=c6053c5e6170796bf1c5dde92415b981; path=/; secure; HttpOnly, lang=zh-cn; path=/; secure; HttpOnly', 'Strict-Transport-Security': 'max-age=31536000', 'Alt-Svc': 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"', 'Content-Encoding': 'gzip'} +2025-02-13 13:53:04,292 - INFO - Ӧ: {"code":1,"info":"ȡɹ","data":{"id":1,"version_no":"3.4.1.4","version_name":"cursor","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/Ȫcursorv3.4.1.4.exe","is_force":0,"min_version":"","platform":"all","status":1,"description":"","create_time":"2025-02-13 13:32:35","update_time":"2025-02-13 13:32:35"}} +2025-02-13 13:53:04,292 - ERROR - ȡ°汾ʧ: δ֪ +2025-02-13 13:53:04,294 - INFO - °汾Ϣ: {'code': -1, 'msg': 'δ֪', 'data': None} +2025-02-13 13:53:04,294 - INFO - +=== Լ === +2025-02-13 13:53:04,294 - INFO - : https://cursorapi.nosqli.com/admin/api.version/check +2025-02-13 13:53:04,294 - INFO - : {'version': '3.4.1', 'platform': 'windows'} +2025-02-13 13:53:06,028 - INFO - ״̬: 200 +2025-02-13 13:53:06,028 - INFO - Ӧͷ: {'Server': 'nginx', 'Date': 'Thu, 13 Feb 2025 05:53:04 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-Frame-Options': 'sameorigin', 'Set-Cookie': 'ssid=cbb7943860ca50662d842719c53e7c73; path=/; secure; HttpOnly, lang=zh-cn; path=/; secure; HttpOnly', 'Strict-Transport-Security': 'max-age=31536000', 'Alt-Svc': 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"', 'Content-Encoding': 'gzip'} +2025-02-13 13:53:06,028 - INFO - Ӧ: {"code":1,"info":"","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/Ȫcursorv3.4.1.4.exe","is_force":0,"min_version":"","platform":"all","status":1,"description":"","create_time":"2025-02-13 13:32:35","update_time":"2025-02-13 13:32:35"}}} +2025-02-13 13:53:06,028 - ERROR - ʧ: δ֪ +2025-02-13 13:53:06,029 - INFO - ¼: {'code': -1, 'msg': 'δ֪', 'data': None} +2025-02-13 13:53:06,029 - INFO - +=== ǷҪ === +2025-02-13 13:53:06,029 - INFO - : https://cursorapi.nosqli.com/admin/api.version/check +2025-02-13 13:53:06,029 - INFO - : {'version': '3.4.1', 'platform': 'windows'} +2025-02-13 13:53:07,770 - INFO - ״̬: 200 +2025-02-13 13:53:07,770 - INFO - Ӧͷ: {'Server': 'nginx', 'Date': 'Thu, 13 Feb 2025 05:53:05 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-Frame-Options': 'sameorigin', 'Set-Cookie': 'ssid=c8004bac4b2d4c5054b69dca0311d6f7; path=/; secure; HttpOnly, lang=zh-cn; path=/; secure; HttpOnly', 'Strict-Transport-Security': 'max-age=31536000', 'Alt-Svc': 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"', 'Content-Encoding': 'gzip'} +2025-02-13 13:53:07,770 - INFO - Ӧ: {"code":1,"info":"","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/Ȫcursorv3.4.1.4.exe","is_force":0,"min_version":"","platform":"all","status":1,"description":"","create_time":"2025-02-13 13:32:35","update_time":"2025-02-13 13:32:35"}}} +2025-02-13 13:53:07,771 - ERROR - ʧ: δ֪ +2025-02-13 13:53:07,774 - INFO - Ƿи: False +2025-02-13 13:53:07,774 - INFO - ǷǿƸ: False +2025-02-13 13:53:07,774 - INFO - 汾Ϣ: None +2025-02-13 13:53:33,800 - INFO - ǰ汾: 3.4.1 +2025-02-13 13:53:33,801 - INFO - ǰƽ̨: windows +2025-02-13 13:53:33,801 - INFO - +=== Իȡ°汾 === +2025-02-13 13:53:33,801 - INFO - : https://cursorapi.nosqli.com/admin/api.version/latest +2025-02-13 13:53:33,801 - INFO - : {'platform': 'windows'} +2025-02-13 13:53:35,509 - INFO - ״̬: 200 +2025-02-13 13:53:35,510 - INFO - Ӧͷ: {'Server': 'nginx', 'Date': 'Thu, 13 Feb 2025 05:53:33 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-Frame-Options': 'sameorigin', 'Set-Cookie': 'ssid=16d07427624c6aaf6c89254d173fe273; path=/; secure; HttpOnly, lang=zh-cn; path=/; secure; HttpOnly', 'Strict-Transport-Security': 'max-age=31536000', 'Alt-Svc': 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"', 'Content-Encoding': 'gzip'} +2025-02-13 13:53:35,510 - INFO - Ӧ: {"code":1,"info":"ȡɹ","data":{"id":1,"version_no":"3.4.1.4","version_name":"cursor","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/Ȫcursorv3.4.1.4.exe","is_force":0,"min_version":"","platform":"all","status":1,"description":"","create_time":"2025-02-13 13:32:35","update_time":"2025-02-13 13:32:35"}} +2025-02-13 13:53:35,510 - ERROR - ȡ°汾ʧ: δ֪ +2025-02-13 13:53:35,513 - INFO - °汾Ϣ: {'code': -1, 'msg': 'δ֪', 'data': None} +2025-02-13 13:53:35,513 - INFO - +=== Լ === +2025-02-13 13:53:35,513 - INFO - : https://cursorapi.nosqli.com/admin/api.version/check +2025-02-13 13:53:35,513 - INFO - : {'version': '3.4.1', 'platform': 'windows'} +2025-02-13 13:53:37,280 - INFO - ״̬: 200 +2025-02-13 13:53:37,281 - INFO - Ӧͷ: {'Server': 'nginx', 'Date': 'Thu, 13 Feb 2025 05:53:35 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-Frame-Options': 'sameorigin', 'Set-Cookie': 'ssid=489a85b5766d7a30c4ba9dccda6f4967; path=/; secure; HttpOnly, lang=zh-cn; path=/; secure; HttpOnly', 'Strict-Transport-Security': 'max-age=31536000', 'Alt-Svc': 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"', 'Content-Encoding': 'gzip'} +2025-02-13 13:53:37,281 - INFO - Ӧ: {"code":1,"info":"","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/Ȫcursorv3.4.1.4.exe","is_force":0,"min_version":"","platform":"all","status":1,"description":"","create_time":"2025-02-13 13:32:35","update_time":"2025-02-13 13:32:35"}}} +2025-02-13 13:53:37,281 - ERROR - ʧ: δ֪ +2025-02-13 13:53:37,283 - INFO - ¼: {'code': -1, 'msg': 'δ֪', 'data': None} +2025-02-13 13:53:37,283 - INFO - +=== ǷҪ === +2025-02-13 13:53:37,283 - INFO - : https://cursorapi.nosqli.com/admin/api.version/check +2025-02-13 13:53:37,284 - INFO - : {'version': '3.4.1', 'platform': 'windows'} +2025-02-13 13:53:39,003 - INFO - ״̬: 200 +2025-02-13 13:53:39,003 - INFO - Ӧͷ: {'Server': 'nginx', 'Date': 'Thu, 13 Feb 2025 05:53:37 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-Frame-Options': 'sameorigin', 'Set-Cookie': 'ssid=b3619976145458f7ffd03d0438958a73; path=/; secure; HttpOnly, lang=zh-cn; path=/; secure; HttpOnly', 'Strict-Transport-Security': 'max-age=31536000', 'Alt-Svc': 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"', 'Content-Encoding': 'gzip'} +2025-02-13 13:53:39,004 - INFO - Ӧ: {"code":1,"info":"","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/Ȫcursorv3.4.1.4.exe","is_force":0,"min_version":"","platform":"all","status":1,"description":"","create_time":"2025-02-13 13:32:35","update_time":"2025-02-13 13:32:35"}}} +2025-02-13 13:53:39,004 - ERROR - ʧ: δ֪ +2025-02-13 13:53:39,005 - INFO - Ƿи: False +2025-02-13 13:53:39,005 - INFO - ǷǿƸ: False +2025-02-13 13:53:39,005 - INFO - 汾Ϣ: None +2025-02-13 13:54:24,914 - INFO - ǰ汾: 3.4.1 +2025-02-13 13:54:24,915 - INFO - ǰƽ̨: windows +2025-02-13 13:54:24,915 - INFO - +=== Իȡ°汾 === +2025-02-13 13:54:24,915 - INFO - : https://cursorapi.nosqli.com/admin/api.version/latest +2025-02-13 13:54:24,915 - INFO - : {'platform': 'windows'} +2025-02-13 13:54:26,652 - INFO - ״̬: 200 +2025-02-13 13:54:26,652 - INFO - Ӧͷ: {'Server': 'nginx', 'Date': 'Thu, 13 Feb 2025 05:54:24 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-Frame-Options': 'sameorigin', 'Set-Cookie': 'ssid=fc779e7ca81172e81a4d03cab86876a1; path=/; secure; HttpOnly, lang=zh-cn; path=/; secure; HttpOnly', 'Strict-Transport-Security': 'max-age=31536000', 'Alt-Svc': 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"', 'Content-Encoding': 'gzip'} +2025-02-13 13:54:26,652 - INFO - Ӧ: {"code":1,"info":"ȡɹ","data":{"id":1,"version_no":"3.4.1.4","version_name":"cursor","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/Ȫcursorv3.4.1.4.exe","is_force":0,"min_version":"","platform":"all","status":1,"description":"","create_time":"2025-02-13 13:32:35","update_time":"2025-02-13 13:32:35"}} +2025-02-13 13:54:26,654 - INFO - °汾Ϣ: {'code': 0, 'msg': 'ȡɹ', 'data': {'id': 1, 'version_no': '3.4.1.4', 'version_name': 'cursor', 'download_url': 'https://cursorapi.nosqli.com/upload/Ȫcursorv3.4.1.4.exe', 'is_force': 0, 'min_version': '', 'platform': 'all', 'status': 1, 'description': '', 'create_time': '2025-02-13 13:32:35', 'update_time': '2025-02-13 13:32:35'}} +2025-02-13 13:54:26,654 - INFO - +=== Լ === +2025-02-13 13:54:26,654 - INFO - : https://cursorapi.nosqli.com/admin/api.version/check +2025-02-13 13:54:26,654 - INFO - : {'version': '3.4.1', 'platform': 'windows'} +2025-02-13 13:54:28,445 - INFO - ״̬: 200 +2025-02-13 13:54:28,445 - INFO - Ӧͷ: {'Server': 'nginx', 'Date': 'Thu, 13 Feb 2025 05:54:26 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-Frame-Options': 'sameorigin', 'Set-Cookie': 'ssid=f8a3f46919c8aaa4d8f34d361ea3386a; path=/; secure; HttpOnly, lang=zh-cn; path=/; secure; HttpOnly', 'Strict-Transport-Security': 'max-age=31536000', 'Alt-Svc': 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"', 'Content-Encoding': 'gzip'} +2025-02-13 13:54:28,445 - INFO - Ӧ: {"code":1,"info":"","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/Ȫcursorv3.4.1.4.exe","is_force":0,"min_version":"","platform":"all","status":1,"description":"","create_time":"2025-02-13 13:32:35","update_time":"2025-02-13 13:32:35"}}} +2025-02-13 13:54:28,447 - INFO - ¼: {'code': 0, 'msg': '', 'data': {'has_update': True, 'is_force': 0, 'version_info': {'id': 1, 'version_no': '3.4.1.4', 'version_name': 'cursor', 'download_url': 'https://cursorapi.nosqli.com/upload/Ȫcursorv3.4.1.4.exe', 'is_force': 0, 'min_version': '', 'platform': 'all', 'status': 1, 'description': '', 'create_time': '2025-02-13 13:32:35', 'update_time': '2025-02-13 13:32:35'}}} +2025-02-13 13:54:28,447 - INFO - +=== ǷҪ === +2025-02-13 13:54:28,447 - INFO - : https://cursorapi.nosqli.com/admin/api.version/check +2025-02-13 13:54:28,447 - INFO - : {'version': '3.4.1', 'platform': 'windows'} +2025-02-13 13:54:30,144 - INFO - ״̬: 200 +2025-02-13 13:54:30,145 - INFO - Ӧͷ: {'Server': 'nginx', 'Date': 'Thu, 13 Feb 2025 05:54:28 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-Frame-Options': 'sameorigin', 'Set-Cookie': 'ssid=169a8bdefde9a16f0e9f3e32da4d8ba5; path=/; secure; HttpOnly, lang=zh-cn; path=/; secure; HttpOnly', 'Strict-Transport-Security': 'max-age=31536000', 'Alt-Svc': 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"', 'Content-Encoding': 'gzip'} +2025-02-13 13:54:30,145 - INFO - Ӧ: {"code":1,"info":"","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/Ȫcursorv3.4.1.4.exe","is_force":0,"min_version":"","platform":"all","status":1,"description":"","create_time":"2025-02-13 13:32:35","update_time":"2025-02-13 13:32:35"}}} +2025-02-13 13:54:30,146 - INFO - Ƿи: True +2025-02-13 13:54:30,146 - INFO - ǷǿƸ: False +2025-02-13 13:54:30,146 - INFO - 汾Ϣ: {'id': 1, 'version_no': '3.4.1.4', 'version_name': 'cursor', 'download_url': 'https://cursorapi.nosqli.com/upload/Ȫcursorv3.4.1.4.exe', 'is_force': 0, 'min_version': '', 'platform': 'all', 'status': 1, 'description': '', 'create_time': '2025-02-13 13:32:35', 'update_time': '2025-02-13 13:32:35'} +2025-02-13 13:54:30,146 - INFO - +=== ظ === +2025-02-13 13:54:30,148 - INFO - صַ: https://cursorapi.nosqli.com/upload/Ȫcursorv3.4.1.4.exe +2025-02-13 13:54:30,148 - INFO - ·: C:\Users\huangzhen\Downloads\CursorHelper\test_update.exe +2025-02-13 13:54:31,822 - ERROR - ظʧ: 404 Client Error: Not Found for url: https://cursorapi.nosqli.com/upload/%E5%90%AC%E6%B3%89cursor%E5%8A%A9%E6%89%8Bv3.4.1.4.exe +2025-02-13 13:54:31,823 - INFO - ؽ: ʧ diff --git a/versioncheck.doc b/versioncheck.doc new file mode 100644 index 0000000..08d23ee --- /dev/null +++ b/versioncheck.doc @@ -0,0 +1,70 @@ +版本更新API文档 +域名 +base_url: https://cursorapi.nosqli.com +# 版本更新API文档 + +# + * 公共返回参数: + * - code: 错误码,0表示成功,非0表示失败 + * - msg: 提示信息 + * - data: 返回的数据,请求失败时可能为空 + * + * 错误码说明: + * - 0: 成功 + * - 1: 一般性错误(具体错误信息见msg) + * - 401: 未授权或授权失败 + * - 404: 请求的资源不存在 + * - 500: 服务器内部错误 + * + * 版本号格式:x.x.x (例如: 3.4.1) + * 平台类型: + * - all: 全平台 + * - windows: Windows平台 + * - mac: Mac平台 + * - linux: Linux平台 + * ==================================================== + * + * 1. 获取最新版本 [GET] /admin/api.version/latest + * 请求参数: + * - platform: 平台类型(all|windows|mac|linux), 默认为all + * 返回数据: + * { + * "code": 0, + * "msg": "获取成功", + * "data": { + * "id": "1", + * "version_no": "3.4.1.4", + * "version_name": "听泉cursor助手", + * "download_url": "http://domain/upload/xxx.exe", + * "is_force": 1, // 是否强制更新(1是,0否) + * "min_version": "3.4.0.0", // 最低要求版本 + * "platform": "all", // 平台类型 + * "description": "版本描述", // 版本描述 + * "status": 1, // 状态(1启用,0禁用) + * "create_time": "2024-03-20 10:00:00" + * } + * } + * + * 2. 检查版本更新 [GET] /admin/api.version/check + * 请求参数: + * - version: 当前版本号(必填) + * - platform: 平台类型(all|windows|mac|linux), 默认为all + * 返回数据: + * { + * "code": 0, + * "msg": "检查完成", + * "data": { + * "has_update": true, // 是否有更新 + * "is_force": 1, // 是否强制更新 + * "version_info": { // 新版本信息(has_update为true时返回) + * // 同上面的版本信息 + * } + * } + * } + * + * 错误返回示例: + * { + * "code": 1, + * "msg": "请提供当前版本号", + * "data": null + * } \ No newline at end of file