refactor: 清理不需要的代码文件,添加.gitignore,优化项目结构

This commit is contained in:
赵杰 Jie Zhao (雄狮汽车科技)
2025-09-16 17:05:50 +01:00
parent 9451945e08
commit 9ca36042e3
65 changed files with 3370 additions and 10809 deletions

View File

@@ -1,5 +1,6 @@
#!/bin/bash
# TSP智能助手部署脚本
# 支持多环境部署、版本管理、自动备份
set -e # 遇到错误立即退出
@@ -246,18 +247,57 @@ rollback() {
fi
}
# 版本检查
check_version() {
log_info "检查版本信息..."
if [ -f "version.json" ]; then
local version=$(python3 -c "import json; print(json.load(open('version.json'))['version'])" 2>/dev/null || echo "unknown")
log_info "当前版本: $version"
else
log_warn "版本文件不存在"
fi
}
# 创建部署包
create_deployment_package() {
local package_name="tsp_assistant_$(date +%Y%m%d_%H%M%S).tar.gz"
log_info "创建部署包: $package_name"
# 排除不需要的文件
tar --exclude='.git' \
--exclude='__pycache__' \
--exclude='*.pyc' \
--exclude='.env' \
--exclude='logs/*' \
--exclude='backups/*' \
--exclude='dev_deploy' \
-czf "$package_name" .
log_info "部署包创建完成: $package_name"
echo "$package_name"
}
# 主函数
main() {
case ${1:-deploy} in
deploy)
check_version
deploy "$2" "$3" "$4"
;;
rollback)
rollback "$2"
;;
package)
create_deployment_package
;;
*)
echo "用法: $0 {deploy|rollback} [environment] [domain] [port]"
echo "用法: $0 {deploy|rollback|package} [environment] [domain] [port]"
echo "环境: development, staging, production"
echo ""
echo "命令说明:"
echo " deploy - 部署到指定环境"
echo " rollback - 回滚到指定备份"
echo " package - 创建部署包"
exit 1
;;
esac

285
scripts/quick_update.bat Normal file
View File

@@ -0,0 +1,285 @@
@echo off
REM TSP智能助手快速更新脚本 (Windows)
REM 支持热更新和完整更新
setlocal enabledelayedexpansion
REM 颜色定义
set "GREEN=[32m"
set "YELLOW=[33m"
set "RED=[31m"
set "NC=[0m"
REM 配置变量
set "APP_NAME=tsp_assistant"
set "DEPLOY_PATH=."
set "BACKUP_PATH=.\backups"
set "HEALTH_URL=http://localhost:5000/api/health"
REM 解析参数
set "ACTION=%1"
set "SOURCE_PATH=%2"
set "ENVIRONMENT=%3"
if "%ACTION%"=="" (
echo 用法: %0 {check^|hot-update^|full-update^|auto-update^|rollback} [源路径] [环境]
echo.
echo 命令说明:
echo check - 检查更新可用性
echo hot-update - 热更新(不重启服务)
echo full-update - 完整更新(重启服务)
echo auto-update - 自动更新(智能选择)
echo rollback - 回滚到指定备份
echo.
echo 环境: development, staging, production
exit /b 1
)
if "%ENVIRONMENT%"=="" set "ENVIRONMENT=production"
if "%SOURCE_PATH%"=="" set "SOURCE_PATH=."
REM 日志函数
:log_info
echo %GREEN%[INFO]%NC% %~1
goto :eof
:log_warn
echo %YELLOW%[WARN]%NC% %~1
goto :eof
:log_error
echo %RED%[ERROR]%NC% %~1
goto :eof
REM 检查更新可用性
:check_update
call :log_info "检查更新可用性..."
if not exist "%SOURCE_PATH%\version.json" (
call :log_error "源路径中未找到版本文件"
exit /b 1
)
REM 比较版本
for /f "tokens=2 delims=:" %%a in ('findstr "version" "%SOURCE_PATH%\version.json"') do (
set "NEW_VERSION=%%a"
set "NEW_VERSION=!NEW_VERSION: =!"
set "NEW_VERSION=!NEW_VERSION:"=!"
set "NEW_VERSION=!NEW_VERSION:,=!"
)
if exist "version.json" (
for /f "tokens=2 delims=:" %%a in ('findstr "version" "version.json"') do (
set "CURRENT_VERSION=%%a"
set "CURRENT_VERSION=!CURRENT_VERSION: =!"
set "CURRENT_VERSION=!CURRENT_VERSION:"=!"
set "CURRENT_VERSION=!CURRENT_VERSION:,=!"
)
) else (
set "CURRENT_VERSION=unknown"
)
if "!NEW_VERSION!"=="!CURRENT_VERSION!" (
call :log_info "没有更新可用 (当前版本: !CURRENT_VERSION!)"
) else (
call :log_info "发现更新: !CURRENT_VERSION! -> !NEW_VERSION!"
)
goto :eof
REM 创建备份
:create_backup
set "TIMESTAMP=%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%"
set "TIMESTAMP=!TIMESTAMP: =0!"
set "BACKUP_NAME=%APP_NAME%_backup_!TIMESTAMP!"
call :log_info "创建备份: !BACKUP_NAME!"
if not exist "%BACKUP_PATH%" mkdir "%BACKUP_PATH%"
mkdir "%BACKUP_PATH%\!BACKUP_NAME!"
REM 备份应用文件
if exist "%DEPLOY_PATH%" (
call :log_info "备份应用文件..."
xcopy "%DEPLOY_PATH%\*" "%BACKUP_PATH%\!BACKUP_NAME!\" /E /I /Y
)
REM 备份数据库
if exist "%DEPLOY_PATH%\tsp_assistant.db" (
call :log_info "备份数据库..."
mkdir "%BACKUP_PATH%\!BACKUP_NAME!\database"
copy "%DEPLOY_PATH%\tsp_assistant.db" "%BACKUP_PATH%\!BACKUP_NAME!\database\"
)
call :log_info "备份完成: !BACKUP_NAME!"
echo !BACKUP_NAME!
goto :eof
REM 热更新
:hot_update
call :log_info "开始热更新..."
REM 支持热更新的文件列表
set "HOT_UPDATE_FILES=src\web\static\js\dashboard.js src\web\static\css\style.css src\web\templates\dashboard.html src\web\app.py"
set "UPDATED_COUNT=0"
for %%f in (%HOT_UPDATE_FILES%) do (
if exist "%SOURCE_PATH%\%%f" (
call :log_info "更新文件: %%f"
if not exist "%DEPLOY_PATH%\%%f" mkdir "%DEPLOY_PATH%\%%f" 2>nul
copy "%SOURCE_PATH%\%%f" "%DEPLOY_PATH%\%%f" /Y >nul
set /a UPDATED_COUNT+=1
)
)
if !UPDATED_COUNT! gtr 0 (
call :log_info "热更新完成,更新了 !UPDATED_COUNT! 个文件"
) else (
call :log_info "没有文件需要热更新"
)
goto :eof
REM 完整更新
:full_update
call :log_info "开始完整更新..."
REM 创建备份
call :create_backup
set "BACKUP_NAME=!BACKUP_NAME!"
REM 停止服务(如果运行中)
call :log_info "停止服务..."
taskkill /f /im python.exe 2>nul || echo 服务未运行
REM 更新文件
call :log_info "更新应用文件..."
if exist "%DEPLOY_PATH%" rmdir /s /q "%DEPLOY_PATH%"
mkdir "%DEPLOY_PATH%"
xcopy "%SOURCE_PATH%\*" "%DEPLOY_PATH%\" /E /I /Y
REM 安装依赖
call :log_info "安装依赖..."
cd "%DEPLOY_PATH%"
if exist "requirements.txt" (
pip install -r requirements.txt
)
REM 运行数据库迁移
call :log_info "运行数据库迁移..."
if exist "init_database.py" (
python init_database.py
)
REM 启动服务
call :log_info "启动服务..."
start /b python start_dashboard.py
REM 等待服务启动
call :log_info "等待服务启动..."
timeout /t 15 /nobreak >nul
REM 健康检查
call :log_info "执行健康检查..."
set "RETRY_COUNT=0"
set "MAX_RETRIES=10"
:health_check_loop
if !RETRY_COUNT! geq !MAX_RETRIES! (
call :log_error "健康检查失败,开始回滚..."
call :rollback !BACKUP_NAME!
exit /b 1
)
curl -f "%HEALTH_URL%" >nul 2>&1
if !errorlevel! equ 0 (
call :log_info "健康检查通过!"
call :log_info "更新成功!"
call :log_info "备份名称: !BACKUP_NAME!"
exit /b 0
) else (
call :log_warn "健康检查失败,重试中... (!RETRY_COUNT!/!MAX_RETRIES!)"
set /a RETRY_COUNT+=1
timeout /t 5 /nobreak >nul
goto :health_check_loop
)
REM 回滚
:rollback
set "BACKUP_NAME=%1"
if "%BACKUP_NAME%"=="" (
call :log_error "请指定备份名称"
exit /b 1
)
call :log_info "开始回滚到备份: !BACKUP_NAME!"
if not exist "%BACKUP_PATH%\!BACKUP_NAME!" (
call :log_error "备份不存在: !BACKUP_NAME!"
exit /b 1
)
REM 停止服务
call :log_info "停止服务..."
taskkill /f /im python.exe 2>nul || echo 服务未运行
REM 恢复文件
call :log_info "恢复文件..."
if exist "%DEPLOY_PATH%" rmdir /s /q "%DEPLOY_PATH%"
mkdir "%DEPLOY_PATH%"
xcopy "%BACKUP_PATH%\!BACKUP_NAME!\*" "%DEPLOY_PATH%\" /E /I /Y
REM 恢复数据库
if exist "%BACKUP_PATH%\!BACKUP_NAME!\database\tsp_assistant.db" (
call :log_info "恢复数据库..."
copy "%BACKUP_PATH%\!BACKUP_NAME!\database\tsp_assistant.db" "%DEPLOY_PATH%\"
)
REM 启动服务
call :log_info "启动服务..."
cd "%DEPLOY_PATH%"
start /b python start_dashboard.py
REM 等待服务启动
timeout /t 15 /nobreak >nul
REM 健康检查
curl -f "%HEALTH_URL%" >nul 2>&1
if !errorlevel! equ 0 (
call :log_info "回滚成功!"
) else (
call :log_error "回滚后健康检查失败"
exit /b 1
)
goto :eof
REM 自动更新
:auto_update
call :log_info "开始自动更新..."
REM 尝试热更新
call :hot_update
if !errorlevel! equ 0 (
call :log_info "热更新成功"
exit /b 0
)
REM 热更新失败,进行完整更新
call :log_info "热更新失败,进行完整更新..."
call :full_update
goto :eof
REM 主逻辑
if "%ACTION%"=="check" (
call :check_update
) else if "%ACTION%"=="hot-update" (
call :hot_update
) else if "%ACTION%"=="full-update" (
call :full_update
) else if "%ACTION%"=="auto-update" (
call :auto_update
) else if "%ACTION%"=="rollback" (
call :rollback "%SOURCE_PATH%"
) else (
call :log_error "未知操作: %ACTION%"
exit /b 1
)
endlocal

477
scripts/update_manager.py Normal file
View File

@@ -0,0 +1,477 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TSP智能助手更新管理器
支持热更新、版本管理、回滚等功能
"""
import os
import sys
import json
import shutil
import subprocess
import time
import requests
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
class UpdateManager:
"""更新管理器"""
def __init__(self, config_file: str = "update_config.json"):
self.config_file = config_file
self.config = self._load_config()
self.version_manager = None
# 初始化版本管理器
try:
from version import VersionManager
self.version_manager = VersionManager()
except ImportError:
print("警告: 版本管理器不可用")
def _load_config(self) -> Dict:
"""加载更新配置"""
default_config = {
"app_name": "tsp_assistant",
"deploy_path": "/opt/tsp_assistant",
"backup_path": "./backups",
"service_name": "tsp_assistant",
"health_url": "http://localhost:5000/api/health",
"update_timeout": 300,
"rollback_enabled": True,
"auto_backup": True,
"hot_update_enabled": True,
"environments": {
"development": {
"path": "./dev_deploy",
"service_name": "",
"auto_restart": False
},
"staging": {
"path": "/opt/tsp_assistant_staging",
"service_name": "tsp_assistant_staging",
"auto_restart": True
},
"production": {
"path": "/opt/tsp_assistant",
"service_name": "tsp_assistant",
"auto_restart": True
}
}
}
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
# 合并默认配置
default_config.update(config)
except Exception as e:
print(f"加载配置文件失败: {e}")
return default_config
def _save_config(self):
"""保存配置"""
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"保存配置文件失败: {e}")
def check_update_available(self, source_path: str) -> Tuple[bool, str, str]:
"""检查是否有更新可用"""
if not self.version_manager:
return False, "unknown", "unknown"
current_version = self.version_manager.get_version()
# 检查源路径的版本
try:
source_version_file = os.path.join(source_path, "version.json")
if os.path.exists(source_version_file):
with open(source_version_file, 'r', encoding='utf-8') as f:
source_info = json.load(f)
source_version = source_info.get("version", "unknown")
else:
return False, current_version, "unknown"
except Exception as e:
print(f"检查源版本失败: {e}")
return False, current_version, "unknown"
# 比较版本
if source_version != current_version:
return True, current_version, source_version
return False, current_version, source_version
def create_backup(self, environment: str = "production") -> str:
"""创建备份"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"{self.config['app_name']}_backup_{timestamp}"
backup_path = os.path.join(self.config["backup_path"], backup_name)
print(f"创建备份: {backup_name}")
# 创建备份目录
os.makedirs(backup_path, exist_ok=True)
# 获取部署路径
env_config = self.config["environments"].get(environment, {})
deploy_path = env_config.get("path", self.config["deploy_path"])
# 备份应用文件
if os.path.exists(deploy_path):
print("备份应用文件...")
shutil.copytree(deploy_path, os.path.join(backup_path, "app"))
# 备份数据库
db_file = os.path.join(deploy_path, "tsp_assistant.db")
if os.path.exists(db_file):
print("备份数据库...")
os.makedirs(os.path.join(backup_path, "database"), exist_ok=True)
shutil.copy2(db_file, os.path.join(backup_path, "database", "tsp_assistant.db"))
# 保存备份信息
backup_info = {
"backup_name": backup_name,
"backup_path": backup_path,
"timestamp": timestamp,
"environment": environment,
"version": self.version_manager.get_version() if self.version_manager else "unknown",
"git_commit": self._get_git_commit(deploy_path)
}
with open(os.path.join(backup_path, "backup_info.json"), 'w', encoding='utf-8') as f:
json.dump(backup_info, f, indent=2, ensure_ascii=False)
print(f"备份完成: {backup_name}")
return backup_name
def _get_git_commit(self, path: str) -> str:
"""获取Git提交哈希"""
try:
result = subprocess.run(['git', 'rev-parse', 'HEAD'],
cwd=path, capture_output=True, text=True)
return result.stdout.strip()[:8] if result.returncode == 0 else "unknown"
except:
return "unknown"
def hot_update(self, source_path: str, environment: str = "production") -> bool:
"""热更新(不重启服务)"""
if not self.config["hot_update_enabled"]:
print("热更新未启用")
return False
print("开始热更新...")
env_config = self.config["environments"].get(environment, {})
deploy_path = env_config.get("path", self.config["deploy_path"])
# 检查哪些文件可以热更新
hot_update_files = [
"src/web/static/js/dashboard.js",
"src/web/static/css/style.css",
"src/web/templates/dashboard.html",
"src/web/app.py",
"src/knowledge_base/knowledge_manager.py",
"src/dialogue/realtime_chat.py"
]
updated_files = []
for file_path in hot_update_files:
source_file = os.path.join(source_path, file_path)
target_file = os.path.join(deploy_path, file_path)
if os.path.exists(source_file):
# 检查文件是否有变化
if not os.path.exists(target_file) or not self._files_equal(source_file, target_file):
print(f"更新文件: {file_path}")
os.makedirs(os.path.dirname(target_file), exist_ok=True)
shutil.copy2(source_file, target_file)
updated_files.append(file_path)
if updated_files:
print(f"热更新完成,更新了 {len(updated_files)} 个文件")
return True
else:
print("没有文件需要热更新")
return False
def _files_equal(self, file1: str, file2: str) -> bool:
"""比较两个文件是否相等"""
try:
with open(file1, 'rb') as f1, open(file2, 'rb') as f2:
return f1.read() == f2.read()
except:
return False
def full_update(self, source_path: str, environment: str = "production",
create_backup: bool = True) -> bool:
"""完整更新(重启服务)"""
print("开始完整更新...")
env_config = self.config["environments"].get(environment, {})
deploy_path = env_config.get("path", self.config["deploy_path"])
service_name = env_config.get("service_name", self.config["service_name"])
auto_restart = env_config.get("auto_restart", True)
# 创建备份
backup_name = None
if create_backup and self.config["auto_backup"]:
backup_name = self.create_backup(environment)
try:
# 停止服务
if auto_restart and service_name:
print(f"停止服务: {service_name}")
subprocess.run(['sudo', 'systemctl', 'stop', service_name], check=True)
# 更新文件
print("更新应用文件...")
if os.path.exists(deploy_path):
shutil.rmtree(deploy_path)
os.makedirs(deploy_path, exist_ok=True)
shutil.copytree(source_path, deploy_path, dirs_exist_ok=True)
# 设置权限
subprocess.run(['sudo', 'chown', '-R', 'www-data:www-data', deploy_path], check=True)
# 安装依赖
print("安装依赖...")
requirements_file = os.path.join(deploy_path, "requirements.txt")
if os.path.exists(requirements_file):
subprocess.run(['sudo', '-u', 'www-data', 'python', '-m', 'pip', 'install', '-r', requirements_file],
cwd=deploy_path, check=True)
# 运行数据库迁移
print("运行数据库迁移...")
init_script = os.path.join(deploy_path, "init_database.py")
if os.path.exists(init_script):
subprocess.run(['sudo', '-u', 'www-data', 'python', init_script],
cwd=deploy_path, check=True)
# 启动服务
if auto_restart and service_name:
print(f"启动服务: {service_name}")
subprocess.run(['sudo', 'systemctl', 'start', service_name], check=True)
# 等待服务启动
print("等待服务启动...")
time.sleep(15)
# 健康检查
if self._health_check():
print("更新成功!")
return True
else:
print("健康检查失败,开始回滚...")
if backup_name:
self.rollback(backup_name, environment)
return False
else:
print("更新完成(未重启服务)")
return True
except Exception as e:
print(f"更新失败: {e}")
if backup_name:
print("开始回滚...")
self.rollback(backup_name, environment)
return False
def _health_check(self) -> bool:
"""健康检查"""
health_url = self.config["health_url"]
max_retries = 10
retry_count = 0
while retry_count < max_retries:
try:
response = requests.get(health_url, timeout=5)
if response.status_code == 200:
return True
except:
pass
retry_count += 1
print(f"健康检查失败,重试中... ({retry_count}/{max_retries})")
time.sleep(5)
return False
def rollback(self, backup_name: str, environment: str = "production") -> bool:
"""回滚到指定备份"""
print(f"开始回滚到备份: {backup_name}")
env_config = self.config["environments"].get(environment, {})
deploy_path = env_config.get("path", self.config["deploy_path"])
service_name = env_config.get("service_name", self.config["service_name"])
auto_restart = env_config.get("auto_restart", True)
backup_path = os.path.join(self.config["backup_path"], backup_name)
if not os.path.exists(backup_path):
print(f"备份不存在: {backup_name}")
return False
try:
# 停止服务
if auto_restart and service_name:
print(f"停止服务: {service_name}")
subprocess.run(['sudo', 'systemctl', 'stop', service_name], check=True)
# 恢复文件
print("恢复文件...")
app_backup_path = os.path.join(backup_path, "app")
if os.path.exists(app_backup_path):
if os.path.exists(deploy_path):
shutil.rmtree(deploy_path)
shutil.copytree(app_backup_path, deploy_path)
# 恢复数据库
db_backup_path = os.path.join(backup_path, "database", "tsp_assistant.db")
if os.path.exists(db_backup_path):
print("恢复数据库...")
shutil.copy2(db_backup_path, os.path.join(deploy_path, "tsp_assistant.db"))
# 设置权限
subprocess.run(['sudo', 'chown', '-R', 'www-data:www-data', deploy_path], check=True)
# 启动服务
if auto_restart and service_name:
print(f"启动服务: {service_name}")
subprocess.run(['sudo', 'systemctl', 'start', service_name], check=True)
# 等待服务启动
time.sleep(15)
# 健康检查
if self._health_check():
print("回滚成功!")
return True
else:
print("回滚后健康检查失败")
return False
else:
print("回滚完成(未重启服务)")
return True
except Exception as e:
print(f"回滚失败: {e}")
return False
def list_backups(self) -> List[Dict]:
"""列出所有备份"""
backups = []
backup_dir = self.config["backup_path"]
if os.path.exists(backup_dir):
for item in os.listdir(backup_dir):
backup_path = os.path.join(backup_dir, item)
if os.path.isdir(backup_path):
info_file = os.path.join(backup_path, "backup_info.json")
if os.path.exists(info_file):
try:
with open(info_file, 'r', encoding='utf-8') as f:
backup_info = json.load(f)
backups.append(backup_info)
except:
pass
return sorted(backups, key=lambda x: x.get("timestamp", ""), reverse=True)
def auto_update(self, source_path: str, environment: str = "production") -> bool:
"""自动更新(智能选择热更新或完整更新)"""
print("开始自动更新...")
# 检查是否有更新
has_update, current_version, new_version = self.check_update_available(source_path)
if not has_update:
print("没有更新可用")
return True
print(f"发现更新: {current_version} -> {new_version}")
# 尝试热更新
if self.hot_update(source_path, environment):
print("热更新成功")
return True
# 热更新失败,进行完整更新
print("热更新失败,进行完整更新...")
return self.full_update(source_path, environment)
def main():
"""命令行接口"""
import argparse
parser = argparse.ArgumentParser(description='TSP智能助手更新管理器')
parser.add_argument('action', choices=['check', 'hot-update', 'full-update', 'auto-update', 'rollback', 'list-backups'],
help='要执行的操作')
parser.add_argument('--source', help='源路径')
parser.add_argument('--environment', choices=['development', 'staging', 'production'],
default='production', help='目标环境')
parser.add_argument('--backup', help='备份名称(用于回滚)')
parser.add_argument('--no-backup', action='store_true', help='跳过备份')
args = parser.parse_args()
um = UpdateManager()
if args.action == 'check':
if not args.source:
print("错误: 需要指定源路径")
sys.exit(1)
has_update, current, new = um.check_update_available(args.source)
if has_update:
print(f"有更新可用: {current} -> {new}")
else:
print(f"没有更新可用 (当前版本: {current})")
elif args.action == 'hot-update':
if not args.source:
print("错误: 需要指定源路径")
sys.exit(1)
success = um.hot_update(args.source, args.environment)
sys.exit(0 if success else 1)
elif args.action == 'full-update':
if not args.source:
print("错误: 需要指定源路径")
sys.exit(1)
success = um.full_update(args.source, args.environment, not args.no_backup)
sys.exit(0 if success else 1)
elif args.action == 'auto-update':
if not args.source:
print("错误: 需要指定源路径")
sys.exit(1)
success = um.auto_update(args.source, args.environment)
sys.exit(0 if success else 1)
elif args.action == 'rollback':
if not args.backup:
print("错误: 需要指定备份名称")
sys.exit(1)
success = um.rollback(args.backup, args.environment)
sys.exit(0 if success else 1)
elif args.action == 'list-backups':
backups = um.list_backups()
if backups:
print("可用备份:")
for backup in backups:
print(f" {backup['backup_name']} - {backup['timestamp']} - {backup.get('version', 'unknown')}")
else:
print("没有找到备份")
if __name__ == "__main__":
main()