4 Commits

12 changed files with 1492 additions and 107 deletions

View File

@@ -229,7 +229,17 @@ class AccountSwitcher:
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
os.system("taskkill /f /im Cursor.exe >nul 2>&1")
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 使用subprocess.run来执行命令并隐藏窗口
subprocess.run(
"taskkill /f /im Cursor.exe >nul 2>&1",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
# 2. 清理注册表(包括更新系统 MachineGuid
@@ -284,7 +294,17 @@ class AccountSwitcher:
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
os.system("taskkill /f /im Cursor.exe >nul 2>&1")
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 关闭Cursor
subprocess.run(
"taskkill /f /im Cursor.exe >nul 2>&1",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
# 2. 重置机器码
@@ -406,9 +426,19 @@ class AccountSwitcher:
logging.info("正在重启Cursor...")
if sys.platform == "win32":
# Windows系统
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 关闭Cursor
os.system("taskkill /f /im Cursor.exe 2>nul")
subprocess.run(
"taskkill /f /im Cursor.exe 2>nul",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
# 获取Cursor安装路径
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
@@ -421,16 +451,16 @@ class AccountSwitcher:
return False
elif sys.platform == "darwin":
# macOS系统
os.system("killall Cursor 2>/dev/null")
subprocess.run("killall Cursor 2>/dev/null", shell=True)
time.sleep(2)
os.system("open -a Cursor")
subprocess.run("open -a Cursor", shell=True)
logging.info("Cursor重启成功")
return True
elif sys.platform == "linux":
# Linux系统
os.system("pkill -f cursor")
subprocess.run("pkill -f cursor", shell=True)
time.sleep(2)
os.system("cursor &")
subprocess.run("cursor &", shell=True)
logging.info("Cursor重启成功")
return True
else:
@@ -447,12 +477,7 @@ class AccountSwitcher:
Tuple[bool, str]: (是否成功, 提示消息)
"""
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
os.system("taskkill /f /im Cursor.exe >nul 2>&1")
time.sleep(2)
# 2. 获取未使用的账号
# 1. 获取未使用的账号
endpoint = "https://cursorapi.nosqli.com/admin/api.account/getUnused"
data = {
"machine_id": self.hardware_id
@@ -504,11 +529,26 @@ class AccountSwitcher:
if not all([email, access_token, refresh_token]):
return False, "获取账号信息不完整"
# 2. 先关闭Cursor进程
if sys.platform == "win32":
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 使用subprocess.run来执行命令并隐藏窗口
subprocess.run(
"taskkill /f /im Cursor.exe >nul 2>&1",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
# 3. 更新Cursor认证信息
if not self.auth_manager.update_auth(email, access_token, refresh_token):
return False, "更新Cursor认证信息失败"
# 4. 重置机器码(包含了清理注册表、文件和重启操作
# 4. 重置机器码(使用现有的reset_machine_id方法
if not self.reset_machine_id():
return False, "重置机器码失败"
@@ -546,7 +586,17 @@ class AccountSwitcher:
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
os.system("taskkill /f /im Cursor.exe >nul 2>&1")
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 关闭Cursor
subprocess.run(
"taskkill /f /im Cursor.exe >nul 2>&1",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
# 2. 删除updater目录并创建同名文件以阻止更新

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -5,8 +5,61 @@ echo 开始打包流程...
:: 更新版本号
python update_version.py
:: 读取版本号
set /p VERSION=<version.txt
echo 当前版本: %VERSION%
:: 提取主版本号和次版本号 (3.4.4 -> 3.4)
for /f "tokens=1,2 delims=." %%a in ("%VERSION%") do (
set MAJOR_VERSION=%%a.%%b
)
echo 主版本目录: %MAJOR_VERSION%
:: 创建版本目录
set VERSION_DIR=dist\%MAJOR_VERSION%
if not exist "%VERSION_DIR%" (
mkdir "%VERSION_DIR%"
echo 创建目录: %VERSION_DIR%
)
:: 使用新的spec文件进行打包
pyinstaller --noconfirm build_nezha.spec
:: 检查源文件是否存在
echo 检查文件: dist\听泉cursor助手%VERSION%.exe
if not exist "dist\听泉cursor助手%VERSION%.exe" (
echo 错误: 打包后的文件不存在
echo 预期文件路径: dist\听泉cursor助手%VERSION%.exe
dir /b dist
pause
exit /b 1
)
:: 检查目标目录是否存在
echo 检查目标目录: %VERSION_DIR%
if not exist "%VERSION_DIR%" (
echo 错误: 目标目录不存在
pause
exit /b 1
)
:: 移动文件到版本目录
echo 移动文件:
echo 源文件: dist\听泉cursor助手%VERSION%.exe
echo 目标文件: %VERSION_DIR%\听泉cursor助手v%VERSION%.exe
move "dist\听泉cursor助手%VERSION%.exe" "%VERSION_DIR%\听泉cursor助手v%VERSION%.exe"
if errorlevel 1 (
echo 移动文件失败,请检查:
echo 1. 源文件是否存在: dist\听泉cursor助手%VERSION%.exe
echo 2. 目标目录是否可写: %VERSION_DIR%
echo 3. 目标文件是否已存在: %VERSION_DIR%\听泉cursor助手v%VERSION%.exe
dir /b dist
dir /b "%VERSION_DIR%"
pause
exit /b 1
)
echo.
echo 打包完成!
echo 文件保存在: %VERSION_DIR%\听泉cursor助手v%VERSION%.exe
pause

View File

@@ -5,6 +5,7 @@ import time
import logging
import sqlite3
from pathlib import Path
import subprocess
class CursorAuthManager:
"""Cursor认证信息管理器"""
@@ -99,9 +100,19 @@ class CursorAuthManager:
logging.info("正在重启Cursor...")
if sys.platform == "win32":
# Windows系统
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 关闭Cursor
os.system("taskkill /f /im Cursor.exe 2>nul")
subprocess.run(
"taskkill /f /im Cursor.exe 2>nul",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
# 获取Cursor安装路径
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
@@ -114,16 +125,16 @@ class CursorAuthManager:
return False
elif sys.platform == "darwin":
# macOS系统
os.system("killall Cursor 2>/dev/null")
subprocess.run("killall Cursor 2>/dev/null", shell=True)
time.sleep(2)
os.system("open -a Cursor")
subprocess.run("open -a Cursor", shell=True)
logging.info("Cursor重启成功")
return True
elif sys.platform == "linux":
# Linux系统
os.system("pkill -f cursor")
subprocess.run("pkill -f cursor", shell=True)
time.sleep(2)
os.system("cursor &")
subprocess.run("cursor &", shell=True)
logging.info("Cursor重启成功")
return True
else:

View File

@@ -10,10 +10,14 @@ from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal
from PyQt5.QtGui import QIcon, QPixmap
import time
import requests
from urllib.parse import quote
import subprocess
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 +134,177 @@ 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 DownloadProgressDialog(QDialog):
"""下载进度对话框"""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("正在下载更新")
self.setFixedSize(400, 300)
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
layout = QVBoxLayout()
# 添加图标
icon_label = QLabel()
icon_label.setPixmap(self.style().standardIcon(QStyle.SP_DesktopIcon).pixmap(32, 32))
icon_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
# 下载状态标签
self.status_label = QLabel("正在连接服务器...")
self.status_label.setAlignment(Qt.AlignCenter)
self.status_label.setStyleSheet("""
color: #0d6efd;
font-size: 14px;
font-weight: bold;
padding: 10px;
""")
layout.addWidget(self.status_label)
# 进度条
self.progress_bar = QProgressBar()
self.progress_bar.setStyleSheet("""
QProgressBar {
border: 2px solid #e9ecef;
border-radius: 5px;
text-align: center;
min-height: 20px;
background-color: #f8f9fa;
}
QProgressBar::chunk {
background-color: #0d6efd;
border-radius: 3px;
}
""")
layout.addWidget(self.progress_bar)
# 下载信息框
info_frame = QFrame()
info_frame.setStyleSheet("""
QFrame {
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
margin: 10px;
padding: 20px;
}
QLabel {
color: #495057;
font-size: 14px;
padding: 8px;
margin: 2px;
background: white;
border-radius: 4px;
border: 1px solid #e9ecef;
}
""")
info_layout = QVBoxLayout(info_frame)
info_layout.setSpacing(10) # 增加标签之间的间距
# 下载速度
self.speed_label = QLabel("下载速度: --")
self.speed_label.setMinimumHeight(35) # 设置最小高度
info_layout.addWidget(self.speed_label)
# 文件大小
self.size_label = QLabel("文件大小: --")
self.size_label.setMinimumHeight(35) # 设置最小高度
info_layout.addWidget(self.size_label)
# 预计剩余时间
self.time_label = QLabel("预计剩余时间: --")
self.time_label.setMinimumHeight(35) # 设置最小高度
info_layout.addWidget(self.time_label)
layout.addWidget(info_frame)
self.setLayout(layout)
# 初始化变量
self.start_time = time.time()
self.last_update_time = time.time()
self.last_downloaded = 0
def update_progress(self, downloaded_size, total_size):
"""更新进度信息"""
if total_size > 0:
percentage = (downloaded_size / total_size) * 100
self.progress_bar.setValue(int(percentage))
# 计算下载速度
current_time = time.time()
time_diff = current_time - self.last_update_time
if time_diff >= 0.5: # 每0.5秒更新一次
speed = (downloaded_size - self.last_downloaded) / time_diff
self.last_downloaded = downloaded_size
self.last_update_time = current_time
# 更新下载信息
speed_text = self._format_speed(speed)
self.speed_label.setText(f"下载速度: {speed_text}")
# 更新文件大小信息
total_mb = total_size / (1024 * 1024)
downloaded_mb = downloaded_size / (1024 * 1024)
self.size_label.setText(f"文件大小: {downloaded_mb:.1f} MB / {total_mb:.1f} MB")
# 更新预计剩余时间
if speed > 0:
remaining_bytes = total_size - downloaded_size
remaining_time = remaining_bytes / speed
time_text = self._format_time(remaining_time)
self.time_label.setText(f"预计剩余时间: {time_text}")
# 更新状态文本
self.status_label.setText(f"正在下载更新... {percentage:.1f}%")
def _format_speed(self, speed_bytes):
"""格式化速度显示"""
if speed_bytes > 1024 * 1024:
return f"{speed_bytes / (1024 * 1024):.1f} MB/s"
elif speed_bytes > 1024:
return f"{speed_bytes / 1024:.1f} KB/s"
else:
return f"{speed_bytes:.1f} B/s"
def _format_time(self, seconds):
"""格式化时间显示"""
if seconds < 60:
return f"{seconds:.0f}"
elif seconds < 3600:
minutes = seconds / 60
return f"{minutes:.0f}分钟"
else:
hours = seconds / 3600
return f"{hours:.1f}小时"
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.config = Config()
self.switcher = AccountSwitcher()
self.version_manager = VersionManager()
# 添加激活状态缓存
self._activation_status = None # 缓存的激活状态
@@ -148,7 +318,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 +345,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 +377,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 +394,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 = (
"<div style='background-color: #f8f9fa; padding: 15px; border-radius: 8px; border: 2px solid #0d6efd;'>"
"<p style='margin-bottom: 15px; font-size: 15px;'><b style='color: #0d6efd;'>使用步骤说明:</b></p>"
"<p style='line-height: 2.0;'>"
"<div style='padding: 15px 0;'>"
"<p style='margin-bottom: 10px; font-size: 15px;'><b style='color: #0d6efd;'>使用步骤说明:</b></p>"
"<p style='line-height: 2; font-size: 14px;'>"
"<span style='font-size: 14px; color: #dc3545;'><b>第一步:</b></span> "
"输入激活码点击<b style='color: #0d6efd;'>【激活】</b>按钮完成激活<br>"
@@ -301,68 +528,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:
@@ -658,7 +934,6 @@ class MainWindow(QMainWindow):
border-radius: 4px;
font-size: 13px;
min-width: 100px;
margin: 10px;
}
QPushButton:hover {
background-color: #bb2d3b;
@@ -1066,7 +1341,17 @@ class MainWindow(QMainWindow):
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
os.system("taskkill /f /im Cursor.exe >nul 2>&1")
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 关闭Cursor
subprocess.run(
"taskkill /f /im Cursor.exe >nul 2>&1",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
# 2. 处理updater文件
@@ -1325,3 +1610,392 @@ class MainWindow(QMainWindow):
def _request_complete(self):
"""请求完成,重置状态"""
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(
"检查更新",
"已是最新版本",
f"您当前使用的 v{self.version_manager.current_version} 已经是最新版本。",
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 = version_info.get('version_no', '未知').lstrip('v')
new_version_label = QLabel(f"最新版本v{new_version} ({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:
# 创建下载目录优先使用D盘如果不存在则使用当前程序目录
if Path("D:/").exists():
download_dir = Path("D:/CursorHelper/updates")
else:
download_dir = Path(__file__).parent.parent / "updates"
download_dir.mkdir(parents=True, exist_ok=True)
# 下载文件名
file_name = download_url.split('/')[-1]
save_path = download_dir / file_name
# 创建并显示进度对话框
progress_dialog = DownloadProgressDialog(self)
progress_dialog.show()
# 开始下载
try:
# 处理下载地址中的中文字符
url_parts = download_url.split('/')
url_parts[-1] = quote(url_parts[-1])
encoded_url = '/'.join(url_parts)
# 设置请求头
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
)
response.raise_for_status()
# 获取文件大小
total_size = int(response.headers.get('content-length', 0))
if total_size == 0:
progress_dialog.hide()
self.show_custom_error("下载失败", "无法获取文件大小,下载地址可能无效")
return False, "无法获取文件大小"
# 更新进度条范围
progress_dialog.progress_bar.setRange(0, 100)
# 下载文件
downloaded_size = 0
block_size = 8192
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)
# 更新进度
progress_dialog.update_progress(downloaded_size, total_size)
QApplication.processEvents() # 保持界面响应
# 验证文件大小
actual_size = os.path.getsize(save_path)
if actual_size != total_size:
progress_dialog.hide()
os.remove(save_path)
self.show_custom_error("下载失败", "文件下载不完整,请重试")
return False, "文件下载不完整"
progress_dialog.hide()
# 显示下载完成对话框
self._show_download_complete_dialog(save_path)
return True, "下载成功"
except requests.exceptions.Timeout:
progress_dialog.hide()
self.show_custom_error("下载失败", "下载超时,请检查网络连接后重试")
return False, "下载超时"
except requests.exceptions.RequestException as e:
progress_dialog.hide()
self.show_custom_error("下载失败", f"下载出错: {str(e)}")
return False, str(e)
except Exception as e:
progress_dialog.hide()
self.show_custom_error("下载失败", f"下载过程中发生错误: {str(e)}")
return False, str(e)
except Exception as e:
self.show_custom_error("下载失败", f"下载更新时发生错误: {str(e)}")
return False, str(e)
def _show_download_complete_dialog(self, save_path):
"""显示下载完成对话框"""
# 创建自定义消息框
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_DialogApplyButton).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: #198754;
padding: 10px;
""")
layout.addWidget(title_label)
# 添加文件信息框
info_frame = QFrame()
info_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;
}
""")
info_layout = QVBoxLayout(info_frame)
# 文件路径
path_label = QLabel(f"文件保存在:\n{save_path}")
path_label.setWordWrap(True)
path_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
info_layout.addWidget(path_label)
# 文件大小
size_mb = save_path.stat().st_size / (1024 * 1024)
size_label = QLabel(f"文件大小:{size_mb:.2f} MB")
info_layout.addWidget(size_label)
layout.addWidget(info_frame)
# 提示信息
tip_label = QLabel("请关闭程序后运行更新包完成更新。")
tip_label.setStyleSheet("color: #dc3545; font-weight: bold; padding: 10px;")
layout.addWidget(tip_label)
# 按钮区域
btn_layout = QHBoxLayout()
# 打开文件按钮
open_file_btn = QPushButton("打开文件")
open_file_btn.setStyleSheet("""
QPushButton {
background-color: #0d6efd;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
}
QPushButton:hover {
background-color: #0b5ed7;
}
""")
open_file_btn.clicked.connect(lambda: os.startfile(str(save_path)))
btn_layout.addWidget(open_file_btn)
# 打开文件夹按钮
open_dir_btn = QPushButton("打开文件夹")
open_dir_btn.setStyleSheet("""
QPushButton {
background-color: #198754;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
}
QPushButton:hover {
background-color: #157347;
}
""")
open_dir_btn.clicked.connect(lambda: os.startfile(str(save_path.parent)))
btn_layout.addWidget(open_dir_btn)
# 退出按钮
quit_btn = QPushButton("立即退出")
quit_btn.setStyleSheet("""
QPushButton {
background-color: #dc3545;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
font-weight: bold;
}
QPushButton:hover {
background-color: #bb2d3b;
}
""")
quit_btn.clicked.connect(lambda: (msg.accept(), self.quit_application()))
btn_layout.addWidget(quit_btn)
layout.addLayout(btn_layout)
msg.setLayout(layout)
msg.exec_()

View File

@@ -8,3 +8,4 @@ pyinstaller==6.3.0
pillow==10.2.0
PyQt5==5.15.10
pywin32==306
packaging>=23.2

63
test_version_manager.py Normal file
View File

@@ -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()

View File

@@ -12,6 +12,12 @@ REM 读取当前版本号
set /p VERSION=<version.txt
echo 当前正式版本: %VERSION%
REM 提取主版本号和次版本号 (3.4.4 -> 3.4)
for /f "tokens=1,2 delims=." %%a in ("%VERSION%") do (
set MAJOR_VERSION=%%a.%%b
)
echo 主版本目录: %MAJOR_VERSION%
REM 读取测试版本号(如果存在)
if exist testversion.txt (
set /p TEST_VERSION=<testversion.txt
@@ -29,7 +35,11 @@ set FULL_VERSION=%VERSION%.!TEST_VERSION!
echo 完整版本号: !FULL_VERSION!
REM 创建测试版本输出目录
if not exist "dist\test" mkdir "dist\test"
set TEST_DIR=dist\test\%MAJOR_VERSION%
if not exist "!TEST_DIR!" (
mkdir "!TEST_DIR!"
echo 创建目录: !TEST_DIR!
)
REM 清理旧文件
if exist "dist\听泉cursor助手%VERSION%.exe" del "dist\听泉cursor助手%VERSION%.exe"
@@ -38,13 +48,36 @@ if exist "build" rmdir /s /q "build"
REM 执行打包
venv\Scripts\python.exe -m PyInstaller build_nezha.spec --clean
REM 检查源文件是否存在
echo 检查文件: dist\听泉cursor助手%VERSION%.exe
if not exist "dist\听泉cursor助手%VERSION%.exe" (
echo 错误: 打包后的文件不存在
echo 预期文件路径: dist\听泉cursor助手%VERSION%.exe
dir /b dist
pause
exit /b 1
)
REM 移动并重命名文件
move "dist\听泉cursor助手%VERSION%.exe" "dist\test\听泉cursor助手v!FULL_VERSION!.exe"
echo 移动文件:
echo 源文件: dist\听泉cursor助手%VERSION%.exe
echo 目标文件: !TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
move "dist\听泉cursor助手%VERSION%.exe" "!TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe"
if errorlevel 1 (
echo 移动文件失败,请检查:
echo 1. 源文件是否存在: dist\听泉cursor助手%VERSION%.exe
echo 2. 目标目录是否可写: !TEST_DIR!
echo 3. 目标文件是否已存在: !TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
dir /b dist
dir /b "!TEST_DIR!"
pause
exit /b 1
)
echo.
echo 测试版本构建完成!
echo 版本号: v!FULL_VERSION!
echo 文件位置: dist\test\听泉cursor助手v!FULL_VERSION!.exe
echo 文件位置: !TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
REM 退出虚拟环境
deactivate

303
utils/version_manager.py Normal file
View File

@@ -0,0 +1,303 @@
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
from pathlib import Path
class VersionManager:
"""版本管理器
错误码说明:
- 0: 成功
- 1: 一般性错误
- 401: 未授权或授权失败
- 404: 请求的资源不存在
- 500: 服务器内部错误
"""
def __init__(self):
self.base_url = "https://cursorapi.nosqli.com"
# 获取项目根目录路径
self.root_path = Path(__file__).parent.parent
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:
version_file = self.root_path / "version.txt"
if not version_file.exists():
logging.error(f"版本文件不存在: {version_file}")
return "0.0.0"
with open(version_file, "r", encoding="utf-8") as f:
version = f.read().strip()
logging.info(f"当前版本: {version}")
return version
except Exception as e:
logging.error(f"读取版本号失败: {str(e)}")
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"
current_version = self.current_version.lstrip('v') # 移除可能存在的v前缀
params = {
"version": 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}")
result = self._handle_response(response)
# 确保返回的数据包含版本信息
if result["code"] == 0 and result.get("data"):
data = result["data"]
if "version_info" in data:
version_info = data["version_info"]
# 确保版本号格式一致
if "version_no" in version_info:
version_info["version_no"] = version_info["version_no"].lstrip('v')
return result
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: (是否有更新, 是否强制更新, 版本信息)
"""
try:
result = self.check_update()
if result["code"] == 0 and result["data"]:
data = result["data"]
version_info = data.get("version_info", {})
# 比较版本号(移除v前缀)
current = self.current_version.lstrip('v')
latest = version_info.get("version_no", "0.0.0").lstrip('v')
# 使用packaging.version进行版本比较
has_update = version.parse(latest) > version.parse(current)
return (
has_update,
bool(data.get("is_force")),
version_info
)
return False, False, None
except Exception as e:
logging.error(f"检查更新失败: {str(e)}")
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

View File

@@ -1 +1 @@
3.4.0
3.4.5

127
version_check.log Normal file
View File

@@ -0,0 +1,127 @@
2025-02-13 13:30:48,255 - INFO - <20><>ǰ<EFBFBD>汾: 3.4.1
2025-02-13 13:30:48,255 - INFO - <20><>ǰƽ̨: windows
2025-02-13 13:30:48,255 - INFO -
=== <20><><EFBFBD>Ի<EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD>°汾 ===
2025-02-13 13:30:49,989 - INFO - <20><><EFBFBD>°汾<C2B0><E6B1BE>Ϣ: {'code': 0, 'info': '<27><><EFBFBD>ް汾<DEB0><E6B1BE>Ϣ', 'data': {}}
2025-02-13 13:30:49,989 - INFO -
=== <20><><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ===
2025-02-13 13:30:51,712 - ERROR - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:30:51,713 - INFO - <20><><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {'code': -1, 'msg': 'δ֪<CEB4><D6AA><EFBFBD><EFBFBD>', 'data': None}
2025-02-13 13:30:51,713 - INFO -
=== <20><><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD> ===
2025-02-13 13:30:53,394 - ERROR - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:30:53,395 - INFO - <20>Ƿ<EFBFBD><C7B7>и<EFBFBD><D0B8><EFBFBD>: False
2025-02-13 13:30:53,395 - INFO - <20>Ƿ<EFBFBD>ǿ<EFBFBD>Ƹ<EFBFBD><C6B8><EFBFBD>: False
2025-02-13 13:30:53,395 - INFO - <20><EFBFBD><E6B1BE>Ϣ: None
2025-02-13 13:49:13,952 - INFO - <20><>ǰ<EFBFBD>汾: 3.4.1
2025-02-13 13:49:13,952 - INFO - <20><>ǰƽ̨: windows
2025-02-13 13:49:13,952 - INFO -
=== <20><><EFBFBD>Ի<EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD>°汾 ===
2025-02-13 13:49:15,718 - ERROR - <20><>ȡ<EFBFBD><C8A1><EFBFBD>°汾ʧ<E6B1BE><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:49:15,720 - INFO - <20><><EFBFBD>°汾<C2B0><E6B1BE>Ϣ: {'code': -1, 'msg': 'δ֪<CEB4><D6AA><EFBFBD><EFBFBD>', 'data': None}
2025-02-13 13:49:15,720 - INFO -
=== <20><><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ===
2025-02-13 13:49:17,452 - ERROR - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:49:17,454 - INFO - <20><><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {'code': -1, 'msg': 'δ֪<CEB4><D6AA><EFBFBD><EFBFBD>', 'data': None}
2025-02-13 13:49:17,454 - INFO -
=== <20><><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD> ===
2025-02-13 13:49:19,277 - ERROR - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:49:19,278 - INFO - <20>Ƿ<EFBFBD><C7B7>и<EFBFBD><D0B8><EFBFBD>: False
2025-02-13 13:49:19,278 - INFO - <20>Ƿ<EFBFBD>ǿ<EFBFBD>Ƹ<EFBFBD><C6B8><EFBFBD>: False
2025-02-13 13:49:19,278 - INFO - <20><EFBFBD><E6B1BE>Ϣ: None
2025-02-13 13:53:02,577 - INFO - <20><>ǰ<EFBFBD>汾: 3.4.1
2025-02-13 13:53:02,577 - INFO - <20><>ǰƽ̨: windows
2025-02-13 13:53:02,577 - INFO -
=== <20><><EFBFBD>Ի<EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD>°汾 ===
2025-02-13 13:53:02,578 - INFO - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: https://cursorapi.nosqli.com/admin/api.version/latest
2025-02-13 13:53:02,578 - INFO - <20><><EFBFBD><EFBFBD>: {'platform': 'windows'}
2025-02-13 13:53:04,292 - INFO - ״̬<D7B4><CCAC>: 200
2025-02-13 13:53:04,292 - INFO - <20><>Ӧͷ: {'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 - <20><>Ӧ<EFBFBD><D3A6><EFBFBD><EFBFBD>: {"code":1,"info":"<22><>ȡ<EFBFBD>ɹ<EFBFBD>","data":{"id":1,"version_no":"3.4.1.4","version_name":"cursor<6F><72><EFBFBD><EFBFBD>","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 - <20><>ȡ<EFBFBD><C8A1><EFBFBD>°汾ʧ<E6B1BE><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:53:04,294 - INFO - <20><><EFBFBD>°汾<C2B0><E6B1BE>Ϣ: {'code': -1, 'msg': 'δ֪<CEB4><D6AA><EFBFBD><EFBFBD>', 'data': None}
2025-02-13 13:53:04,294 - INFO -
=== <20><><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ===
2025-02-13 13:53:04,294 - INFO - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: https://cursorapi.nosqli.com/admin/api.version/check
2025-02-13 13:53:04,294 - INFO - <20><><EFBFBD><EFBFBD>: {'version': '3.4.1', 'platform': 'windows'}
2025-02-13 13:53:06,028 - INFO - ״̬<D7B4><CCAC>: 200
2025-02-13 13:53:06,028 - INFO - <20><>Ӧͷ: {'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 - <20><>Ӧ<EFBFBD><D3A6><EFBFBD><EFBFBD>: {"code":1,"info":"<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor<6F><72><EFBFBD><EFBFBD>","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:53:06,029 - INFO - <20><><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {'code': -1, 'msg': 'δ֪<CEB4><D6AA><EFBFBD><EFBFBD>', 'data': None}
2025-02-13 13:53:06,029 - INFO -
=== <20><><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD> ===
2025-02-13 13:53:06,029 - INFO - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: https://cursorapi.nosqli.com/admin/api.version/check
2025-02-13 13:53:06,029 - INFO - <20><><EFBFBD><EFBFBD>: {'version': '3.4.1', 'platform': 'windows'}
2025-02-13 13:53:07,770 - INFO - ״̬<D7B4><CCAC>: 200
2025-02-13 13:53:07,770 - INFO - <20><>Ӧͷ: {'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 - <20><>Ӧ<EFBFBD><D3A6><EFBFBD><EFBFBD>: {"code":1,"info":"<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor<6F><72><EFBFBD><EFBFBD>","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:53:07,774 - INFO - <20>Ƿ<EFBFBD><C7B7>и<EFBFBD><D0B8><EFBFBD>: False
2025-02-13 13:53:07,774 - INFO - <20>Ƿ<EFBFBD>ǿ<EFBFBD>Ƹ<EFBFBD><C6B8><EFBFBD>: False
2025-02-13 13:53:07,774 - INFO - <20><EFBFBD><E6B1BE>Ϣ: None
2025-02-13 13:53:33,800 - INFO - <20><>ǰ<EFBFBD>汾: 3.4.1
2025-02-13 13:53:33,801 - INFO - <20><>ǰƽ̨: windows
2025-02-13 13:53:33,801 - INFO -
=== <20><><EFBFBD>Ի<EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD>°汾 ===
2025-02-13 13:53:33,801 - INFO - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: https://cursorapi.nosqli.com/admin/api.version/latest
2025-02-13 13:53:33,801 - INFO - <20><><EFBFBD><EFBFBD>: {'platform': 'windows'}
2025-02-13 13:53:35,509 - INFO - ״̬<D7B4><CCAC>: 200
2025-02-13 13:53:35,510 - INFO - <20><>Ӧͷ: {'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 - <20><>Ӧ<EFBFBD><D3A6><EFBFBD><EFBFBD>: {"code":1,"info":"<22><>ȡ<EFBFBD>ɹ<EFBFBD>","data":{"id":1,"version_no":"3.4.1.4","version_name":"cursor<6F><72><EFBFBD><EFBFBD>","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 - <20><>ȡ<EFBFBD><C8A1><EFBFBD>°汾ʧ<E6B1BE><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:53:35,513 - INFO - <20><><EFBFBD>°汾<C2B0><E6B1BE>Ϣ: {'code': -1, 'msg': 'δ֪<CEB4><D6AA><EFBFBD><EFBFBD>', 'data': None}
2025-02-13 13:53:35,513 - INFO -
=== <20><><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ===
2025-02-13 13:53:35,513 - INFO - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: https://cursorapi.nosqli.com/admin/api.version/check
2025-02-13 13:53:35,513 - INFO - <20><><EFBFBD><EFBFBD>: {'version': '3.4.1', 'platform': 'windows'}
2025-02-13 13:53:37,280 - INFO - ״̬<D7B4><CCAC>: 200
2025-02-13 13:53:37,281 - INFO - <20><>Ӧͷ: {'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 - <20><>Ӧ<EFBFBD><D3A6><EFBFBD><EFBFBD>: {"code":1,"info":"<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor<6F><72><EFBFBD><EFBFBD>","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:53:37,283 - INFO - <20><><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {'code': -1, 'msg': 'δ֪<CEB4><D6AA><EFBFBD><EFBFBD>', 'data': None}
2025-02-13 13:53:37,283 - INFO -
=== <20><><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD> ===
2025-02-13 13:53:37,283 - INFO - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: https://cursorapi.nosqli.com/admin/api.version/check
2025-02-13 13:53:37,284 - INFO - <20><><EFBFBD><EFBFBD>: {'version': '3.4.1', 'platform': 'windows'}
2025-02-13 13:53:39,003 - INFO - ״̬<D7B4><CCAC>: 200
2025-02-13 13:53:39,003 - INFO - <20><>Ӧͷ: {'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 - <20><>Ӧ<EFBFBD><D3A6><EFBFBD><EFBFBD>: {"code":1,"info":"<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor<6F><72><EFBFBD><EFBFBD>","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>: δ֪<CEB4><D6AA><EFBFBD><EFBFBD>
2025-02-13 13:53:39,005 - INFO - <20>Ƿ<EFBFBD><C7B7>и<EFBFBD><D0B8><EFBFBD>: False
2025-02-13 13:53:39,005 - INFO - <20>Ƿ<EFBFBD>ǿ<EFBFBD>Ƹ<EFBFBD><C6B8><EFBFBD>: False
2025-02-13 13:53:39,005 - INFO - <20><EFBFBD><E6B1BE>Ϣ: None
2025-02-13 13:54:24,914 - INFO - <20><>ǰ<EFBFBD>汾: 3.4.1
2025-02-13 13:54:24,915 - INFO - <20><>ǰƽ̨: windows
2025-02-13 13:54:24,915 - INFO -
=== <20><><EFBFBD>Ի<EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD>°汾 ===
2025-02-13 13:54:24,915 - INFO - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: https://cursorapi.nosqli.com/admin/api.version/latest
2025-02-13 13:54:24,915 - INFO - <20><><EFBFBD><EFBFBD>: {'platform': 'windows'}
2025-02-13 13:54:26,652 - INFO - ״̬<D7B4><CCAC>: 200
2025-02-13 13:54:26,652 - INFO - <20><>Ӧͷ: {'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 - <20><>Ӧ<EFBFBD><D3A6><EFBFBD><EFBFBD>: {"code":1,"info":"<22><>ȡ<EFBFBD>ɹ<EFBFBD>","data":{"id":1,"version_no":"3.4.1.4","version_name":"cursor<6F><72><EFBFBD><EFBFBD>","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 - <20><><EFBFBD>°汾<C2B0><E6B1BE>Ϣ: {'code': 0, 'msg': '<27><>ȡ<EFBFBD>ɹ<EFBFBD>', 'data': {'id': 1, 'version_no': '3.4.1.4', 'version_name': 'cursor<6F><72><EFBFBD><EFBFBD>', 'download_url': 'https://cursorapi.nosqli.com/upload/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 -
=== <20><><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ===
2025-02-13 13:54:26,654 - INFO - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: https://cursorapi.nosqli.com/admin/api.version/check
2025-02-13 13:54:26,654 - INFO - <20><><EFBFBD><EFBFBD>: {'version': '3.4.1', 'platform': 'windows'}
2025-02-13 13:54:28,445 - INFO - ״̬<D7B4><CCAC>: 200
2025-02-13 13:54:28,445 - INFO - <20><>Ӧͷ: {'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 - <20><>Ӧ<EFBFBD><D3A6><EFBFBD><EFBFBD>: {"code":1,"info":"<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor<6F><72><EFBFBD><EFBFBD>","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 - <20><><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: {'code': 0, 'msg': '<27><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>', 'data': {'has_update': True, 'is_force': 0, 'version_info': {'id': 1, 'version_no': '3.4.1.4', 'version_name': 'cursor<6F><72><EFBFBD><EFBFBD>', 'download_url': 'https://cursorapi.nosqli.com/upload/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 -
=== <20><><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD> ===
2025-02-13 13:54:28,447 - INFO - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: https://cursorapi.nosqli.com/admin/api.version/check
2025-02-13 13:54:28,447 - INFO - <20><><EFBFBD><EFBFBD>: {'version': '3.4.1', 'platform': 'windows'}
2025-02-13 13:54:30,144 - INFO - ״̬<D7B4><CCAC>: 200
2025-02-13 13:54:30,145 - INFO - <20><>Ӧͷ: {'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 - <20><>Ӧ<EFBFBD><D3A6><EFBFBD><EFBFBD>: {"code":1,"info":"<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>","data":{"has_update":true,"is_force":0,"version_info":{"id":1,"version_no":"3.4.1.4","version_name":"cursor<6F><72><EFBFBD><EFBFBD>","download_url":"https:\/\/cursorapi.nosqli.com\/upload\/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 - <20>Ƿ<EFBFBD><C7B7>и<EFBFBD><D0B8><EFBFBD>: True
2025-02-13 13:54:30,146 - INFO - <20>Ƿ<EFBFBD>ǿ<EFBFBD>Ƹ<EFBFBD><C6B8><EFBFBD>: False
2025-02-13 13:54:30,146 - INFO - <20><EFBFBD><E6B1BE>Ϣ: {'id': 1, 'version_no': '3.4.1.4', 'version_name': 'cursor<6F><72><EFBFBD><EFBFBD>', 'download_url': 'https://cursorapi.nosqli.com/upload/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.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 -
=== <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ظ<EFBFBD><D8B8><EFBFBD> ===
2025-02-13 13:54:30,148 - INFO - <20><><EFBFBD>ص<EFBFBD>ַ: https://cursorapi.nosqli.com/upload/<2F><>Ȫcursor<6F><72><EFBFBD><EFBFBD>v3.4.1.4.exe
2025-02-13 13:54:30,148 - INFO - <20><><EFBFBD><EFBFBD>·<EFBFBD><C2B7>: C:\Users\huangzhen\Downloads\CursorHelper\test_update.exe
2025-02-13 13:54:31,822 - ERROR - <20><><EFBFBD>ظ<EFBFBD><D8B8><EFBFBD>ʧ<EFBFBD><CAA7>: 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 - <20><><EFBFBD>ؽ<EFBFBD><D8BD><EFBFBD>: ʧ<><CAA7>

70
versioncheck.doc Normal file
View File

@@ -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
* }