Selaa lähdekoodia

前端部署脚本

lingmin_package@163.com 3 viikkoa sitten
vanhempi
sitoutus
13ffc4ca41

+ 8 - 0
scripts/lq_db_update.sql

@@ -10,3 +10,11 @@
 -- 2026-02-02  授权码 表增加 是否删除标志位
 alter table t_oauth_authorization_codes add column `is_deleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除';
 
+-- 
+ALTER TABLE t_samp_knowledge_base 
+  CHANGE `collection_name` `collection_name1` 
+    VARCHAR(100)  NOT NULL 
+    COMMENT 'Milvus集合名称(Table Name)(父)',
+  ADD `collection_name2` 
+    VARCHAR(100)  NOT NULL 
+    COMMENT 'Milvus集合名称(Table Name)(子)';

+ 627 - 0
src/deploy/admin_front_deploy.py

@@ -0,0 +1,627 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Vue前端应用自动化部署脚本
+功能:本地压缩Vue构建文件 -> SSH上传 -> 远程执行部署脚本
+"""
+
+import os
+import sys
+import time
+import zipfile
+import paramiko
+import argparse
+import tempfile
+import hashlib
+from pathlib import Path
+from typing import Optional, Tuple, List
+import getpass
+import shutil
+
+class VueAutoDeployer:
+    def __init__(self, hostname: str, username: str, 
+                 local_source_dir: str, remote_deploy_dir: str,
+                 remote_script_path: str,
+                 password: Optional[str] = None,
+                 port: int = 22, 
+                 key_filename: Optional[str] = None):
+        """
+        初始化自动部署器
+        
+        Args:
+            hostname: 服务器地址
+            username: 用户名
+            local_source_dir: 本地Vue构建文件目录(包含assets和index.html)
+            remote_deploy_dir: 远程服务器部署目录(上传zip文件的目录)
+            remote_script_path: 远程部署脚本路径
+            password: SSH密码(可选,密钥认证不需要)
+            port: SSH端口,默认22
+            key_filename: SSH私钥文件路径(可选)
+        """
+        self.hostname = hostname
+        self.username = username
+        self.local_source_dir = os.path.expanduser(local_source_dir)
+        self.remote_deploy_dir = remote_deploy_dir
+        self.remote_script_path = remote_script_path
+        self.password = password
+        self.port = port
+        self.key_filename = key_filename
+        self.ssh_client = None
+        self.sftp_client = None
+        
+        # 压缩文件名
+        self.zip_filename = "dist-dev.zip"
+        
+        # 验证目录
+        self._validate_directories()
+        
+    def _validate_directories(self):
+        """验证本地目录是否存在且包含必要文件"""
+        print(f"检查本地目录: {self.local_source_dir}")
+        
+        if not os.path.exists(self.local_source_dir):
+            raise FileNotFoundError(f"本地目录不存在: {self.local_source_dir}")
+        
+        # 检查必要文件
+        required_files = ['index.html']
+        required_dirs = ['assets']
+        
+        missing_items = []
+        
+        for file in required_files:
+            file_path = os.path.join(self.local_source_dir, file)
+            if not os.path.exists(file_path):
+                missing_items.append(file)
+        
+        for dir_name in required_dirs:
+            dir_path = os.path.join(self.local_source_dir, dir_name)
+            if not os.path.exists(dir_path):
+                missing_items.append(dir_name)
+        
+        if missing_items:
+            raise FileNotFoundError(
+                f"本地目录缺少必要的文件/目录: {', '.join(missing_items)}\n"
+                f"请确保 {self.local_source_dir} 包含完整的Vue构建文件"
+            )
+        
+        # 显示目录内容
+        print("本地目录内容:")
+        for item in os.listdir(self.local_source_dir):
+            item_path = os.path.join(self.local_source_dir, item)
+            if os.path.isdir(item_path):
+                print(f"  📁 {item}/")
+            else:
+                print(f"  📄 {item}")
+        
+        print("✓ 本地目录验证通过")
+    
+    def _create_zip_from_source(self) -> str:
+        """
+        从源目录创建zip压缩包
+        
+        Returns:
+            zip文件的临时路径
+        """
+        print(f"\n正在创建压缩包: {self.zip_filename}")
+        
+        # 创建临时文件
+        temp_dir = tempfile.mkdtemp(prefix="vue_deploy_")
+        zip_path = os.path.join(temp_dir, self.zip_filename)
+        
+        try:
+            with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
+                # 添加index.html
+                index_path = os.path.join(self.local_source_dir, 'index.html')
+                zipf.write(index_path, 'index.html')
+                print(f"  ✓ 添加: index.html")
+                
+                # 添加assets目录
+                assets_dir = os.path.join(self.local_source_dir, 'assets')
+                if os.path.exists(assets_dir):
+                    # 遍历assets目录中的所有文件
+                    for root, dirs, files in os.walk(assets_dir):
+                        # 计算相对路径
+                        rel_path = os.path.relpath(root, self.local_source_dir)
+                        
+                        for file in files:
+                            file_path = os.path.join(root, file)
+                            arcname = os.path.join(rel_path, file)
+                            zipf.write(file_path, arcname)
+                            print(f"  ✓ 添加: {arcname}")
+                
+                # 添加其他可能的文件(css, js文件)
+                for item in os.listdir(self.local_source_dir):
+                    if item not in ['index.html', 'assets']:
+                        item_path = os.path.join(self.local_source_dir, item)
+                        if os.path.isfile(item_path) and item.endswith(('.css', '.js')):
+                            zipf.write(item_path, item)
+                            print(f"  ✓ 添加: {item}")
+            
+            # 获取压缩包信息
+            zip_size = os.path.getsize(zip_path)
+            file_count = len(zipfile.ZipFile(zip_path, 'r').namelist())
+            
+            print(f"✓ 压缩包创建完成:")
+            print(f"  文件路径: {zip_path}")
+            print(f"  文件大小: {zip_size / 1024 / 1024:.2f} MB")
+            print(f"  包含文件: {file_count} 个")
+            
+            return zip_path
+            
+        except Exception as e:
+            # 清理临时目录
+            shutil.rmtree(temp_dir, ignore_errors=True)
+            raise Exception(f"创建压缩包失败: {e}")
+    
+    def connect(self) -> bool:
+        """连接到SSH服务器"""
+        print(f"\n正在连接到服务器 {self.hostname}:{self.port}...")
+        
+        try:
+            self.ssh_client = paramiko.SSHClient()
+            self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+            
+            # 连接参数
+            connect_params = {
+                'hostname': self.hostname,
+                'port': self.port,
+                'username': self.username,
+            }
+            
+            # 认证方式
+            if self.key_filename:
+                connect_params['key_filename'] = self.key_filename
+                auth_method = "密钥认证"
+            elif self.password:
+                connect_params['password'] = self.password
+                auth_method = "密码认证"
+            else:
+                # 交互式输入密码
+                self.password = getpass.getpass(f"请输入用户 {self.username} 的密码: ")
+                connect_params['password'] = self.password
+                auth_method = "密码认证"
+            
+            print(f"使用认证方式: {auth_method}")
+            self.ssh_client.connect(**connect_params, timeout=30)
+            
+            # 测试连接
+            stdin, stdout, stderr = self.ssh_client.exec_command('echo "连接成功" && whoami && hostname')
+            output = stdout.read().decode().strip()
+            user = output.split('\n')[1] if len(output.split('\n')) > 1 else '未知'
+            hostname = output.split('\n')[2] if len(output.split('\n')) > 2 else '未知'
+            
+            print(f"✓ SSH连接成功!")
+            print(f"  服务器: {hostname}")
+            print(f"  用户: {user}")
+            
+            # 创建SFTP客户端
+            self.sftp_client = self.ssh_client.open_sftp()
+            return True
+            
+        except paramiko.AuthenticationException:
+            print("✗ SSH认证失败!请检查用户名/密码/密钥")
+            return False
+        except paramiko.SSHException as e:
+            print(f"✗ SSH连接异常: {e}")
+            return False
+        except Exception as e:
+            print(f"✗ 连接失败: {e}")
+            return False
+    
+    def disconnect(self):
+        """断开连接"""
+        if self.sftp_client:
+            self.sftp_client.close()
+        if self.ssh_client:
+            self.ssh_client.close()
+        print("✓ 已断开SSH连接")
+    
+    def execute_command(self, command: str, verbose: bool = True) -> Tuple[int, str, str]:
+        """
+        执行远程命令
+        
+        Args:
+            command: 要执行的命令
+            verbose: 是否显示输出
+            
+        Returns:
+            (返回码, 标准输出, 标准错误)
+        """
+        try:
+            if verbose:
+                print(f"执行命令: {command}")
+            
+            stdin, stdout, stderr = self.ssh_client.exec_command(command, timeout=60)
+            
+            # 读取输出
+            stdout_str = stdout.read().decode('utf-8', errors='ignore').strip()
+            stderr_str = stderr.read().decode('utf-8', errors='ignore').strip()
+            
+            # 等待命令完成并获取返回码
+            exit_status = stdout.channel.recv_exit_status()
+            
+            if verbose:
+                if stdout_str:
+                    print(f"输出:\n{stdout_str}")
+                if stderr_str and exit_status != 0:
+                    print(f"错误:\n{stderr_str}")
+                print(f"返回码: {exit_status}")
+            
+            return exit_status, stdout_str, stderr_str
+            
+        except Exception as e:
+            print(f"✗ 执行命令失败: {e}")
+            return -1, "", str(e)
+    
+    def upload_file(self, local_path: str, remote_path: str) -> bool:
+        """
+        上传文件到服务器
+        
+        Args:
+            local_path: 本地文件路径
+            remote_path: 远程文件路径
+            
+        Returns:
+            是否成功
+        """
+        try:
+            if not os.path.exists(local_path):
+                print(f"✗ 本地文件不存在: {local_path}")
+                return False
+            
+            print(f"本地文件: {local_path}")
+            file_size = os.path.getsize(local_path)
+            print(f"正在上传文件: {os.path.basename(local_path)} ({file_size/1024/1024:.2f} MB)")
+            
+            # 确保远程目录存在并检查权限
+            remote_dir = os.path.dirname(remote_path)
+            print(f"远程文件目录: {remote_dir}")
+            
+            # 创建目录
+            exit_code, stdout, stderr = self.execute_command(f"mkdir -p {remote_dir}", verbose=False)
+            if exit_code != 0:
+                print(f"✗ 创建远程目录失败: {stderr}")
+                return False
+            
+            # 检查目录权限
+            exit_code, stdout, stderr = self.execute_command(f"ls -ld {remote_dir}", verbose=False)
+            if exit_code == 0:
+                print(f"目录权限: {stdout}")
+            
+            # 检查写入权限
+            test_file = os.path.join(remote_dir, "test_write_permission.tmp")
+            exit_code, stdout, stderr = self.execute_command(f"touch {test_file} && rm -f {test_file}", verbose=False)
+            if exit_code != 0:
+                print(f"✗ 远程目录没有写入权限: {remote_dir}")
+                print(f"错误: {stderr}")
+                return False
+            print("✓ 远程目录写入权限检查通过")
+            
+            # 使用SFTP上传文件(显示进度)
+            start_time = time.time()
+            
+            def progress_callback(transferred, total):
+                elapsed = time.time() - start_time
+                if elapsed > 0:
+                    speed = transferred / elapsed / 1024  # KB/s
+                    percent = (transferred / total) * 100
+                    sys.stdout.write(f"\r  进度: {percent:.1f}% ({transferred/1024/1024:.2f}/{total/1024/1024:.2f} MB) 速度: {speed:.1f} KB/s")
+                    sys.stdout.flush()
+            
+            self.sftp_client.put(local_path, remote_path, callback=progress_callback)
+            
+            # 验证上传
+            exit_code, stdout, stderr = self.execute_command(f"ls -lh {remote_path}", verbose=False)
+            if exit_code == 0 and stdout:
+                print(f"✓ 文件验证成功: {stdout}")
+            else:
+                print(f"⚠ 文件验证失败: 文件可能未正确上传")
+                if stderr:
+                    print(f"错误: {stderr}")
+                # 检查目录内容
+                print(f"检查目录内容: {remote_dir}")
+                self.execute_command(f"ls -la {remote_dir}", verbose=True)
+                return False
+            
+            elapsed = time.time() - start_time
+            print(f"\n✓ 文件上传成功!耗时: {elapsed:.1f}秒")
+            
+            return True
+            
+        except paramiko.SFTPError as e:
+            print(f"\n✗ SFTP上传失败: {e}")
+            print("可能的原因:")
+            print("  1. 远程目录权限不足")
+            print("  2. 磁盘空间不足")
+            print("  3. 网络连接中断")
+            return False
+        except Exception as e:
+            print(f"\n✗ 文件上传失败: {e}")
+            return False
+    
+    def check_remote_prerequisites(self) -> bool:
+        """
+        检查远程服务器是否满足部署条件
+        
+        Returns:
+            是否满足条件
+        """
+        print("\n检查远程服务器部署条件...")
+        
+        checks = []
+        
+        # 检查远程目录是否存在
+        exit_code, stdout, stderr = self.execute_command(
+            f"ls -ld {self.remote_deploy_dir} 2>/dev/null || echo '目录不存在'", 
+            verbose=False
+        )
+        if "目录不存在" in stdout:
+            checks.append(("部署目录", "✗", f"{self.remote_deploy_dir} 不存在"))
+        else:
+            checks.append(("部署目录", "✓", f"{self.remote_deploy_dir}"))
+        
+        # 检查部署脚本是否存在且有执行权限
+        exit_code, stdout, stderr = self.execute_command(
+            f"ls -la {self.remote_script_path} 2>/dev/null || echo '脚本不存在'",
+            verbose=False
+        )
+        if "脚本不存在" in stdout:
+            checks.append(("部署脚本", "✗", f"{self.remote_script_path} 不存在"))
+        else:
+            # 检查执行权限
+            exit_code, stdout, stderr = self.execute_command(
+                f"test -x {self.remote_script_path} && echo '可执行' || echo '不可执行'",
+                verbose=False
+            )
+            if "可执行" in stdout:
+                checks.append(("部署脚本", "✓", "存在且可执行"))
+            else:
+                checks.append(("部署脚本", "⚠", "存在但不可执行"))
+        
+        # 检查unzip命令
+        exit_code, stdout, stderr = self.execute_command(
+            "which unzip 2>/dev/null && unzip -v 2>/dev/null | head -1",
+            verbose=False
+        )
+        if exit_code == 0:
+            checks.append(("unzip工具", "✓", stdout.strip()))
+        else:
+            checks.append(("unzip工具", "✗", "未安装"))
+        
+        # 检查zip命令
+        exit_code, stdout, stderr = self.execute_command(
+            "which zip 2>/dev/null && zip -v 2>/dev/null | head -1",
+            verbose=False
+        )
+        if exit_code == 0:
+            checks.append(("zip工具", "✓", stdout.strip()))
+        else:
+            checks.append(("zip工具", "⚠", "未安装(备份功能可能受影响)"))
+        
+        # 检查nginx目录(通常的部署目录)
+        exit_code, stdout, stderr = self.execute_command(
+            "ls -ld /usr/share/nginx/html 2>/dev/null || ls -ld /var/www/html 2>/dev/null || echo '未找到nginx目录'",
+            verbose=False
+        )
+        if "未找到nginx目录" in stdout:
+            checks.append(("nginx目录", "⚠", "未找到标准nginx目录"))
+        else:
+            checks.append(("nginx目录", "✓", stdout.strip().split()[-1]))
+        
+        # 显示检查结果
+        print("\n" + "="*60)
+        print("服务器环境检查结果:")
+        print("="*60)
+        
+        all_passed = True
+        for check_name, status, message in checks:
+            if status == "✓":
+                print(f"  {status} {check_name}: {message}")
+            elif status == "⚠":
+                print(f"  {status} {check_name}: {message}")
+            else:
+                print(f"  {status} {check_name}: {message}")
+                all_passed = False
+        
+        print("="*60)
+        
+        if not all_passed:
+            print("\n⚠ 警告: 部分检查未通过,部署可能会失败")
+            response = input("是否继续部署?(y/N): ").strip().lower()
+            return response == 'y'
+        
+        return True
+    
+    def deploy(self, cleanup_temp: bool = True) -> bool:
+        """
+        执行完整的部署流程
+        
+        Args:
+            cleanup_temp: 是否清理临时文件
+            
+        Returns:
+            是否部署成功
+        """
+        print("="*70)
+        print("Vue前端应用自动化部署流程")
+        print("="*70)
+        
+        temp_zip_path = None
+        temp_dir = None
+        
+        try:
+            # 步骤1: 本地压缩文件
+            print("\n[步骤 1/4] 本地压缩Vue构建文件")
+            print("-"*40)
+            temp_zip_path = self._create_zip_from_source()
+            temp_dir = os.path.dirname(temp_zip_path)
+            
+            # 步骤2: 连接到服务器
+            print("\n[步骤 2/4] 连接到远程服务器")
+            print("-"*40)
+            if not self.connect():
+                return False
+            
+            # 检查服务器环境
+            if not self.check_remote_prerequisites():
+                return False
+            
+            # 步骤3: 上传文件
+            print("\n[步骤 3/4] 上传文件到服务器")
+            print("-"*40)
+            remote_zip_path = os.path.join(self.remote_deploy_dir, self.zip_filename)
+            
+            if not self.upload_file(temp_zip_path, remote_zip_path):
+                return False
+            
+            # 步骤4: 执行部署脚本
+            print("\n[步骤 4/4] 执行远程部署脚本")
+            print("-"*40)
+            
+            # 构建部署命令,传递上传的zip文件路径作为参数 {remote_zip_path}
+            deploy_command = f"{self.remote_script_path} "
+            
+            print(f"执行部署命令: {deploy_command}")
+            print("-"*40)
+            
+            start_time = time.time()
+            exit_code, stdout, stderr = self.execute_command(deploy_command, verbose=True)
+            elapsed_time = time.time() - start_time
+            
+            print("-"*40)
+            print(f"部署执行完成,耗时: {elapsed_time:.1f}秒")
+            
+            if exit_code != 0:
+                print(f"✗ 部署失败!返回码: {exit_code}")
+                if stderr:
+                    print(f"错误信息:\n{stderr}")
+                return False
+            
+            print("✅ 部署成功完成!")
+            
+            # 可选: 验证部署结果
+            print("\n验证部署结果...")
+            self.execute_command("ls -la /usr/share/nginx/html/ 2>/dev/null | head -10", verbose=True)
+            
+            return True
+            
+        except Exception as e:
+            print(f"\n✗ 部署过程中发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+            return False
+            
+        finally:
+            # 清理临时文件
+            if cleanup_temp and temp_dir and os.path.exists(temp_dir):
+                try:
+                    shutil.rmtree(temp_dir)
+                    print(f"✓ 已清理临时文件: {temp_dir}")
+                except:
+                    print(f"⚠ 清理临时文件失败: {temp_dir}")
+            
+            # 断开连接
+            self.disconnect()
+
+def parse_arguments():
+    """解析命令行参数"""
+    parser = argparse.ArgumentParser(
+        description='Vue前端应用自动化部署工具',
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        epilog="""
+使用示例:
+  %(prog)s --host 192.168.1.100 --user deploy --source ./dist --remote-dir /home/deploy --script /home/deploy/deploy.sh
+  %(prog)s --host example.com --user ubuntu --key ~/.ssh/id_rsa --source ./build --remote-dir /tmp
+        """
+    )
+    
+    # 必需参数
+    parser.add_argument('--host', required=True, help='服务器地址 (例如: 192.168.1.100 或 example.com)')
+    parser.add_argument('--user', required=True, help='SSH用户名')
+    parser.add_argument('--source', required=True, help='本地Vue构建文件目录(包含assets和index.html)')
+    
+    # 可选参数
+    parser.add_argument('--remote-dir', default='/home/lq/lq_workspace/deploy_workspace/put/', 
+                       help='远程服务器上传目录 (默认: /home/lq/lq_workspace/deploy_workspace/put/)')
+    parser.add_argument('--script', default='/home/lq/lq_workspace/deploy_workspace/admin_front_deploy.sh',
+                       help='远程部署脚本路径 (默认: /home/lq/lq_workspace/deploy_workspace/admin_front_deploy.sh)')
+    parser.add_argument('--port', type=int, default=22, help='SSH端口 (默认: 22)')
+    parser.add_argument('--key', help='SSH私钥文件路径(如果使用密钥认证)')
+    parser.add_argument('--password', help='SSH密码(如果使用密码认证)')
+    parser.add_argument('--no-cleanup', action='store_true', 
+                       help='不清理临时文件(用于调试)')
+    parser.add_argument('--verbose', '-v', action='store_true', 
+                       help='显示详细输出')
+    
+    return parser.parse_args()
+
+def main():
+    """主函数"""
+    args = parse_arguments()
+    
+    # 显示欢迎信息
+    print("="*70)
+    print("🚀 Vue前端应用自动化部署工具")
+    print("="*70)
+    
+    print(f"服务器: {args.host}:{args.port}")
+    print(f"用户: {args.user}")
+    print(f"本地源目录: {args.source}")
+    print(f"远程目录: {args.remote_dir}")
+    print(f"部署脚本: {args.script}")
+    print("="*70)
+    
+    try:
+        # 创建部署器实例
+        deployer = VueAutoDeployer(
+            hostname=args.host,
+            username=args.user,
+            local_source_dir=args.source,
+            remote_deploy_dir=args.remote_dir,
+            remote_script_path=args.script,
+            password=args.password,
+            port=args.port,
+            key_filename=args.key
+        )
+        
+        # 执行部署
+        success = deployer.deploy(cleanup_temp=not args.no_cleanup)
+        
+        if success:
+            print("\n" + "="*70)
+            print("🎉 部署流程全部完成!")
+            print("="*70)
+            print("\n建议操作:")
+            print("  1. 访问网站检查是否正常显示")
+            print("  2. 检查nginx错误日志: sudo tail -f /var/log/nginx/error.log")
+            print("  3. 如果需要重启nginx: sudo systemctl restart nginx")
+            print("="*70)
+            return 0
+        else:
+            print("\n" + "="*70)
+            print("❌ 部署失败!")
+            print("="*70)
+            return 1
+            
+    except FileNotFoundError as e:
+        print(f"\n✗ 文件错误: {e}")
+        return 1
+    except KeyboardInterrupt:
+        print("\n\n⚠ 用户中断操作")
+        return 130
+    except Exception as e:
+        print(f"\n✗ 发生未预期的错误: {e}")
+        import traceback
+        traceback.print_exc()
+        return 1
+
+if __name__ == "__main__":
+    # 检查必要依赖
+    try:
+        import paramiko
+    except ImportError:
+        print("错误: 未安装paramiko库")
+        print("请安装依赖: pip install paramiko")
+        sys.exit(1)
+    
+    sys.exit(main())

+ 303 - 0
src/deploy/admin_front_deploy.sh

@@ -0,0 +1,303 @@
+#!/bin/bash
+
+# =============================================
+# Vue前端应用自动部署脚本
+# 功能:备份当前部署文件,部署新版本到nginx
+# =============================================
+
+# 设置颜色输出
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# 日志函数
+log_info() {
+    echo -e "${GREEN}[INFO] $1${NC}"
+}
+
+log_warn() {
+    echo -e "${YELLOW}[WARN] $1${NC}"
+}
+
+log_error() {
+    echo -e "${RED}[ERROR] $1${NC}"
+}
+
+# 定义目录变量
+put_tmp_directory="/home/lq/lq_workspace/deploy_workspace/put"           # 部署临时目录(存放新版本的dist-dev.zip)
+nginx_html_directory="/home/lq/nginx/html"  # nginx部署目录
+nginx_html_bak="/home/lq/lq_workspace/deploy_workspace/deploy_bak/LQAdminFrontServer"           # 备份目录
+
+
+# 处理参数
+if [ $# -eq 0 ]; then
+    # 如果没有传入参数,使用默认文件名
+    SOURCE_ZIP="$put_tmp_directory/dist-dev.zip"
+    log_info "未指定部署文件,将使用默认文件: $SOURCE_ZIP"
+
+    # 检查默认文件是否存在
+    if [ ! -f "$SOURCE_ZIP" ]; then
+        log_error "默认文件 $SOURCE_ZIP 不存在于当前目录"
+        echo "当前目录: $(pwd)"
+        echo "请指定要部署的zip文件路径,或者将dist-dev.zip放在当前目录"
+        echo ""
+        echo "使用方法:"
+        echo "  1. 不传参数: $0                    (使用当前目录的dist-dev.zip)"
+        echo "  2. 传入参数: $0 <zip文件路径>       (使用指定路径的zip文件)"
+        echo ""
+        echo "示例:"
+        echo "  $0                              # 使用当前目录的dist-dev.zip"
+        echo "  $0 /home/user/dist-dev.zip      # 使用指定路径的文件"
+        echo "  $0 /home/user/vue-app.zip       # 使用其他名称的zip文件"
+        exit 1
+    fi
+else
+    # 使用传入的参数
+    SOURCE_ZIP="$put_tmp_directory/$1"
+    log_info "使用指定的部署文件: $SOURCE_ZIP"
+fi
+
+
+
+
+# 检查源文件是否存在
+if [ ! -f "$SOURCE_ZIP" ]; then
+    log_error "文件不存在: $SOURCE_ZIP"
+    exit 1
+fi
+
+# 检查文件是否是zip格式
+if ! file "$SOURCE_ZIP" | grep -q "Zip archive"; then
+    log_error "文件不是有效的zip压缩包: $SOURCE_ZIP"
+    exit 1
+fi
+
+# 检查目录是否存在,不存在则创建
+check_and_create_dir() {
+    if [ ! -d "$1" ]; then
+        log_warn "目录不存在,正在创建: $1"
+        mkdir -p "$1"
+        if [ $? -eq 0 ]; then
+            log_info "目录创建成功: $1"
+        else
+            log_error "目录创建失败: $1"
+            exit 1
+        fi
+    fi
+}
+
+# 检查并创建所有需要的目录
+log_info "检查并创建目录..."
+check_and_create_dir "$put_tmp_directory"
+check_and_create_dir "$nginx_html_bak"
+check_and_create_dir "$nginx_html_directory"
+
+# 清理临时目录中的旧文件
+#log_info "清理临时目录..."
+#rm -f "$put_tmp_directory"/*.zip 2>/dev/null
+
+# 检查nginx部署目录是否为空
+if [ ! -d "$nginx_html_directory" ] || [ -z "$(ls -A $nginx_html_directory 2>/dev/null)" ]; then
+    log_warn "nginx部署目录为空或不存在,跳过备份步骤"
+    SKIP_BACKUP=true
+else
+    SKIP_BACKUP=false
+fi
+
+# 获取当前时间(用于备份文件名)
+current_time=$(date "+%Y-%m-%d_%H-%M-%S")
+backup_filename="dist-dev_bak_${current_time}.zip"
+backup_filepath="/tmp/${backup_filename}"
+
+# ========== 2.1 备份当前部署文件 ==========
+if [ "$SKIP_BACKUP" = false ]; then
+    log_info "正在备份当前部署文件..."
+    
+    # 切换到nginx部署目录
+    cd "$nginx_html_directory" || {
+        log_error "无法切换到nginx部署目录: $nginx_html_directory"
+        exit 1
+    }
+    
+    # 检查是否有需要备份的文件
+    if [ ! -f "index.html" ] && [ ! -d "assets" ]; then
+        log_warn "未找到index.html和assets目录,跳过备份"
+        SKIP_BACKUP=true
+    else
+        # 创建备份压缩包
+        log_info "正在创建备份文件: $backup_filename"
+        if command -v zip >/dev/null 2>&1; then
+            # 只备份index.html和assets目录
+            if [ -f "index.html" ]; then
+                zip -qr "$backup_filepath" index.html
+                log_info "已备份 index.html"
+            fi
+            
+            if [ -d "assets" ]; then
+                zip -qru "$backup_filepath" assets
+                log_info "已备份 assets 目录"
+            fi
+            
+            if [ ! -f "$backup_filepath" ]; then
+                log_error "备份文件创建失败"
+                exit 1
+            fi
+            
+            backup_size=$(du -h "$backup_filepath" | cut -f1)
+            log_info "备份完成,文件大小: $backup_size"
+        else
+            log_error "未找到zip命令,请先安装zip工具"
+            log_info "安装命令: sudo apt-get install zip 或 sudo yum install zip"
+            exit 1
+        fi
+    fi
+fi
+
+# ========== 2.2 复制备份文件到备份目录 ==========
+if [ "$SKIP_BACKUP" = false ] && [ -f "$backup_filepath" ]; then
+    log_info "正在复制备份文件到备份目录..."
+    cp "$backup_filepath" "$nginx_html_bak/"
+    if [ $? -eq 0 ]; then
+        log_info "备份文件已保存到: $nginx_html_bak/$backup_filename"
+    else
+        log_error "备份文件复制失败"
+        exit 1
+    fi
+fi
+
+# ========== 2.3 删除工作目录下的备份文件 ==========
+if [ -f "$backup_filepath" ]; then
+    log_info "清理临时备份文件..."
+    rm -f "$backup_filepath"
+    log_info "临时备份文件已清理"
+fi
+
+# ========== 2.4 复制新的dist-dev.zip到nginx部署目录 ==========
+log_info "正在复制新版本文件到nginx部署目录..."
+cp "$SOURCE_ZIP" "$nginx_html_directory/dist-dev.zip"
+
+if [ $? -ne 0 ]; then
+    log_error "文件复制失败"
+    exit 1
+fi
+
+log_info "新版本文件已复制到: $nginx_html_directory/dist-dev.zip"
+
+# ========== 2.5 解压缩并清理 ==========
+log_info "正在解压缩新版本文件..."
+
+# 切换到nginx部署目录
+cd "$nginx_html_directory" || {
+    log_error "无法切换到nginx部署目录"
+    exit 1
+}
+
+# 先删除现有的文件 再解压
+rm -f index.html && rm -rf assets
+log_info "删除nginx工作目录的文件"
+
+# 检查unzip命令
+if ! command -v unzip >/dev/null 2>&1; then
+    log_error "未找到unzip命令,请先安装unzip工具"
+    log_info "安装命令: sudo apt-get install unzip 或 sudo yum install unzip"
+    exit 1
+fi
+
+# 解压缩前备份现有文件(如果需要)
+if [ "$SKIP_BACKUP" = false ]; then
+    # 移动现有文件到临时目录
+    temp_backup_dir="/tmp/nginx_html_backup_${current_time}"
+    mkdir -p "$temp_backup_dir"
+    
+    # 移动除dist-dev.zip之外的所有文件
+    for item in *; do
+        if [ "$item" != "dist-dev.zip" ]; then
+            mv "$item" "$temp_backup_dir/" 2>/dev/null || true
+        fi
+    done
+    log_info "现有文件已临时备份到: $temp_backup_dir"
+fi
+
+# 解压缩文件
+log_info "正在解压dist-dev.zip..."
+unzip -o -q "dist-dev.zip"
+
+if [ $? -eq 0 ]; then
+    log_info "解压缩完成"
+    
+    # 检查解压后的文件
+    if [ -f "index.html" ]; then
+        log_info "✓ index.html 文件存在"
+    else
+        log_warn "⚠ 解压后未找到index.html文件"
+    fi
+    
+    if [ -d "assets" ]; then
+        assets_count=$(find assets -type f | wc -l)
+        log_info "✓ assets 目录存在,包含 $assets_count 个文件"
+    else
+        log_warn "⚠ 解压后未找到assets目录"
+    fi
+else
+    log_error "解压缩失败"
+    
+    # 如果解压失败,恢复备份的文件
+    if [ "$SKIP_BACKUP" = false ] && [ -d "$temp_backup_dir" ]; then
+        log_info "正在恢复备份文件..."
+        mv "$temp_backup_dir"/* . 2>/dev/null || true
+    fi
+    exit 1
+fi
+
+# ========== 清理临时文件和目录 ==========
+log_info "正在清理临时文件..."
+
+# 删除zip文件
+rm -f "$nginx_html_directory/dist-dev.zip"
+log_info "已删除dist-dev.zip文件"
+
+# 删除临时备份目录
+if [ -d "$temp_backup_dir" ]; then
+    rm -rf "$temp_backup_dir"
+    log_info "已清理临时备份目录"
+fi
+
+# 设置正确的权限
+log_info "设置文件权限..."
+chmod -R 755 "$nginx_html_directory" 2>/dev/null || true
+
+# 验证部署
+log_info "==================== 部署验证 ===================="
+log_info "部署目录: $nginx_html_directory"
+log_info "备份目录: $nginx_html_bak"
+log_info "部署时间: $(date)"
+log_info "备份文件: ${backup_filename:-无}"
+
+# 显示目录结构
+log_info "当前部署文件结构:"
+ls -la "$nginx_html_directory" | head -20
+
+log_info "=================================================="
+
+# 获取文件数量统计
+html_count=$(find "$nginx_html_directory" -name "*.html" -type f | wc -l)
+js_count=$(find "$nginx_html_directory" -name "*.js" -type f | wc -l)
+css_count=$(find "$nginx_html_directory" -name "*.css" -type f | wc -l)
+
+log_info "部署统计:"
+log_info "  HTML文件: $html_count 个"
+log_info "  JS文件: $js_count 个"
+log_info "  CSS文件: $css_count 个"
+
+if [ "$SKIP_BACKUP" = false ]; then
+    log_info "  🔄 备份已创建: $nginx_html_bak/$backup_filename"
+fi
+
+log_info "${GREEN}✅ Vue前端应用部署完成!${NC}"
+
+# 输出nginx重启建议
+log_info "=================================================="
+log_info "如果需要重新加载nginx配置,请执行以下命令:"
+echo "sudo nginx -s reload"
+log_info "=================================================="

+ 32 - 0
src/deploy/dempoy_README.md

@@ -0,0 +1,32 @@
+
+
+
+
+
+#### 前端部署
+    帮我生成一个python 代码用于 ssh 连接linux 服务器 上传前端zip文件到指定目录下,再执行shell脚本 完成部署
+    1、指定上传文件的源目录,将源文件中的assets、index.html 压缩为zip文件 dist-dev.zip
+    2、再将dist-dev.zip 上传到远程linux 的指定目录下   /home/lq/lq_workspace/deploy_workspace/put
+    3、再执行服务器上指定的shell脚本 开始部署           /home/lq/lq_workspace/deploy_workspace/admin_front_deploy.sh
+
+
+
+
+#### 服务器部署脚本 admin_front_deploy.sh
+    帮我生成一个 shell 脚本 用于部署vue前端应用到nginx 
+    1、定义目录变量(部署临时目录 put_tmp_directory、nginx 部署目录 nginx_html_directory、备份目录 nginx_html_bak)
+    2、处理流程 
+    2.1、先将nginx 部署目录下的 assets目录 和 index.html 使用 zip压缩工具 ,文件名:dist-dev_bak_{yyyy-MM-dd HH:mm:ss}.zip
+    2.2、将压缩好的文件 dist-dev_bak_{yyyy-MM-dd HH:mm:ss}.zip copy 到备份目录 
+    2.3、删除 工作目录下压缩的文件 dist-dev_bak_{yyyy-MM-dd HH:mm:ss}.zip
+    2.4、copy 部署临时目录 下 dist-dev.zip 到 nginx 部署目录下
+    2.5、 使用unzip 解压缩   dist-dev.zip 文件,完成后 删除  dist-dev.zip
+
+备份目录
+/home/lq/lq_workspace/deploy_workspace/deploy_bak/LQAdminFrontServer
+
+
+
+
+
+python admin_front_deploy.py --host 192.168.92.61 --user lq --source I:/wangxun_dev_workspace/lq_workspace/LQAdminFront/dist-dev