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