Enhance database management features by adding an API endpoint to drop all tables with double confirmation. Implement configuration file upload and download functionalities, including backup of the existing configuration. Update UI elements for improved user experience, including pagination controls and toast notifications. Adjust Docker and git ignore files to optimize build processes.
This commit is contained in:
Vendored
+313
@@ -0,0 +1,313 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
CapacityReport 镜像构建和导出脚本 (Windows)
|
||||
在有外网的 Windows 机器上运行此脚本,生成离线部署包
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
# 脚本所在目录
|
||||
SCRIPT_DIR = Path(__file__).parent.absolute()
|
||||
PROJECT_ROOT = SCRIPT_DIR.parent
|
||||
DIST_DIR = SCRIPT_DIR
|
||||
|
||||
# 颜色输出(Windows 支持)
|
||||
import platform
|
||||
if platform.system() == 'Windows':
|
||||
try:
|
||||
import ctypes
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
|
||||
except:
|
||||
pass
|
||||
|
||||
class Colors:
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
RED = '\033[91m'
|
||||
RESET = '\033[0m'
|
||||
|
||||
def print_step(msg):
|
||||
print(f"\n{Colors.GREEN}{msg}{Colors.RESET}")
|
||||
|
||||
def print_info(msg):
|
||||
print(f" {msg}")
|
||||
|
||||
def print_warning(msg):
|
||||
print(f"{Colors.YELLOW}警告: {msg}{Colors.RESET}")
|
||||
|
||||
def print_error(msg):
|
||||
print(f"{Colors.RED}错误: {msg}{Colors.RESET}")
|
||||
|
||||
def run_cmd(cmd, check=True, capture_output=False):
|
||||
"""执行命令"""
|
||||
if isinstance(cmd, str):
|
||||
cmd = cmd.split()
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
check=check,
|
||||
capture_output=capture_output,
|
||||
text=True
|
||||
)
|
||||
return result
|
||||
except subprocess.CalledProcessError as e:
|
||||
if check:
|
||||
print_error(f"命令执行失败: {' '.join(cmd)}")
|
||||
sys.exit(1)
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
print_error(f"命令未找到: {cmd[0]}")
|
||||
sys.exit(1)
|
||||
|
||||
def check_command(cmd):
|
||||
"""检查命令是否存在"""
|
||||
try:
|
||||
if cmd == "docker":
|
||||
subprocess.run([cmd, "--version"], capture_output=True, check=True)
|
||||
elif cmd == "docker-compose":
|
||||
subprocess.run([cmd, "version"], capture_output=True, check=True)
|
||||
return True
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
return False
|
||||
|
||||
def get_python_tag_from_dockerfile():
|
||||
"""从 Dockerfile 中提取 Python 镜像标签"""
|
||||
dockerfile_path = PROJECT_ROOT / "Dockerfile"
|
||||
if not dockerfile_path.exists():
|
||||
print_error(f"Dockerfile 不存在: {dockerfile_path}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(dockerfile_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
match = re.match(r'^FROM\s+python:(.+?)(?:\s|$)', line, re.IGNORECASE)
|
||||
if match:
|
||||
tag = match.group(1).strip()
|
||||
return tag
|
||||
|
||||
print_error("无法从 Dockerfile 中找到 Python 镜像标签")
|
||||
print_error("请确保 Dockerfile 中包含 'FROM python:xxx' 行")
|
||||
sys.exit(1)
|
||||
|
||||
def image_exists(image_name):
|
||||
"""检查镜像是否已存在"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["docker", "images", "--format", "{{.Repository}}:{{.Tag}}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
return image_name in result.stdout
|
||||
except:
|
||||
return False
|
||||
|
||||
def format_size(size_bytes):
|
||||
"""格式化文件大小"""
|
||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||
if size_bytes < 1024.0:
|
||||
return f"{size_bytes:.1f} {unit}"
|
||||
size_bytes /= 1024.0
|
||||
return f"{size_bytes:.1f} TB"
|
||||
|
||||
def main():
|
||||
print("=" * 50)
|
||||
print("CapacityReport 离线部署包构建脚本")
|
||||
print("=" * 50)
|
||||
print()
|
||||
|
||||
# 清理旧的构建文件
|
||||
print_step("清理旧的构建文件...")
|
||||
files_to_clean = [
|
||||
DIST_DIR / "images" / "capacity-images.tar",
|
||||
DIST_DIR / "Configure.json",
|
||||
DIST_DIR / "ReportScript.sql",
|
||||
DIST_DIR / "mysql" / "conf.d" / "custom.cnf",
|
||||
DIST_DIR / "mysql" / "init" / "01-init-db.sql",
|
||||
]
|
||||
|
||||
for file_path in files_to_clean:
|
||||
if file_path.exists():
|
||||
if file_path.is_file():
|
||||
file_path.unlink()
|
||||
print_info(f"已删除旧 {file_path.name}")
|
||||
elif file_path.is_dir():
|
||||
shutil.rmtree(file_path)
|
||||
print_info(f"已删除旧 {file_path.name}")
|
||||
|
||||
print_info("清理完成")
|
||||
|
||||
# 检查 Docker
|
||||
print_step("检查 Docker 环境...")
|
||||
if not check_command("docker"):
|
||||
print_error("未检测到 Docker,请先安装 Docker Desktop")
|
||||
sys.exit(1)
|
||||
print_info("Docker: OK")
|
||||
|
||||
# 检查 Docker Compose
|
||||
docker_compose_cmd = None
|
||||
try:
|
||||
subprocess.run(["docker", "compose", "version"], capture_output=True, check=True)
|
||||
docker_compose_cmd = "docker compose"
|
||||
print_info("Docker Compose: OK (docker compose)")
|
||||
except:
|
||||
if check_command("docker-compose"):
|
||||
docker_compose_cmd = "docker-compose"
|
||||
print_info("Docker Compose: OK (docker-compose)")
|
||||
else:
|
||||
print_error("未检测到 Docker Compose,请先安装 Docker Compose")
|
||||
sys.exit(1)
|
||||
|
||||
# 步骤 1: 拉取基础镜像
|
||||
print_step("步骤 1: 拉取基础镜像...")
|
||||
|
||||
# 获取 Python 版本
|
||||
python_tag = get_python_tag_from_dockerfile()
|
||||
python_image = f"python:{python_tag}"
|
||||
print_info(f"从 Dockerfile 检测到 Python 镜像标签: {python_tag}")
|
||||
|
||||
# 检查并拉取 Python 镜像
|
||||
if image_exists(python_image):
|
||||
print_info(f"Python 镜像 {python_image} 已存在,跳过拉取")
|
||||
else:
|
||||
print_info(f"正在拉取 {python_image}...")
|
||||
run_cmd(["docker", "pull", python_image])
|
||||
print_info(f"Python 镜像拉取完成")
|
||||
|
||||
# 检查并拉取 MySQL 镜像
|
||||
mysql_image = "mysql:8.0.44"
|
||||
if image_exists(mysql_image):
|
||||
print_info(f"MySQL 镜像 {mysql_image} 已存在,跳过拉取")
|
||||
else:
|
||||
print_info(f"正在拉取 {mysql_image}...")
|
||||
run_cmd(["docker", "pull", mysql_image])
|
||||
print_info(f"MySQL 镜像拉取完成")
|
||||
|
||||
# 步骤 2: 构建应用镜像
|
||||
print_step("步骤 2: 构建应用镜像...")
|
||||
os.chdir(PROJECT_ROOT)
|
||||
run_cmd(["docker", "build", "-t", "capacity-report-app:latest", "-f", "Dockerfile", "."])
|
||||
print_info("应用镜像构建完成")
|
||||
|
||||
# 步骤 3: 标记 MySQL 镜像
|
||||
print_step("步骤 3: 标记 MySQL 镜像...")
|
||||
run_cmd(["docker", "tag", mysql_image, "capacity-mysql:8.0.44"])
|
||||
print_info("MySQL 镜像标记完成")
|
||||
|
||||
# 步骤 4: 导出镜像
|
||||
print_step("步骤 4: 导出镜像到 dist 目录...")
|
||||
images_dir = DIST_DIR / "images"
|
||||
images_dir.mkdir(exist_ok=True)
|
||||
|
||||
tar_file = images_dir / "capacity-images.tar"
|
||||
print_info("正在导出镜像到单个 tar 文件:")
|
||||
print_info(" - capacity-report-app:latest (应用镜像)")
|
||||
print_info(" - capacity-mysql:8.0.44 (MySQL 镜像)")
|
||||
|
||||
run_cmd([
|
||||
"docker", "save",
|
||||
"capacity-report-app:latest",
|
||||
"capacity-mysql:8.0.44",
|
||||
"-o", str(tar_file)
|
||||
])
|
||||
|
||||
print_info(f"镜像导出完成: {tar_file}")
|
||||
print_info("注意: 此文件包含应用镜像和 MySQL 镜像,部署时会自动加载所有镜像")
|
||||
|
||||
# 步骤 5: 复制必要文件
|
||||
print_step("步骤 5: 复制必要文件到 dist 目录...")
|
||||
|
||||
# 创建必要的目录结构
|
||||
(DIST_DIR / "mysql" / "conf.d").mkdir(parents=True, exist_ok=True)
|
||||
(DIST_DIR / "mysql" / "init").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 复制配置文件
|
||||
print_info("正在复制配置文件...")
|
||||
|
||||
files_to_copy = [
|
||||
(PROJECT_ROOT / "Configure.json", DIST_DIR / "Configure.json"),
|
||||
(PROJECT_ROOT / "ReportScript.sql", DIST_DIR / "ReportScript.sql"),
|
||||
(PROJECT_ROOT / "mysql" / "conf.d" / "custom.cnf", DIST_DIR / "mysql" / "conf.d" / "custom.cnf"),
|
||||
(PROJECT_ROOT / "mysql" / "init" / "01-init-db.sql", DIST_DIR / "mysql" / "init" / "01-init-db.sql"),
|
||||
]
|
||||
|
||||
for src, dst in files_to_copy:
|
||||
if src.exists():
|
||||
shutil.copy2(src, dst)
|
||||
print_info(f"{src.name} 已复制")
|
||||
else:
|
||||
print_warning(f"{src.name} 不存在: {src}")
|
||||
|
||||
# 更新 Configure.json 为 Docker 环境配置
|
||||
config_file = DIST_DIR / "Configure.json"
|
||||
if config_file.exists():
|
||||
print_info("正在更新 Configure.json 为 Docker 环境配置...")
|
||||
try:
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
config['MySQL_DBInfo'] = {
|
||||
'host': 'capacity-mysql',
|
||||
'port': 3306,
|
||||
'user': 'root',
|
||||
'passwd': 'gmcc123',
|
||||
'dbname': 'CapacityReport'
|
||||
}
|
||||
|
||||
with open(config_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print_info("已更新数据库配置: host=capacity-mysql, port=3306, user=root, passwd=gmcc123")
|
||||
except Exception as e:
|
||||
print_warning(f"无法自动更新 Configure.json: {e}")
|
||||
print_warning("请手动修改数据库配置")
|
||||
|
||||
# 步骤 6: 计算文件大小
|
||||
print_step("步骤 6: 计算文件大小...")
|
||||
if tar_file.exists():
|
||||
size = tar_file.stat().st_size
|
||||
print_info(f"镜像文件大小: {format_size(size)}")
|
||||
|
||||
# 步骤 7: 清理临时目录
|
||||
print_step("步骤 7: 清理临时目录...")
|
||||
temp_dirs = [DIST_DIR / "cache", DIST_DIR / "logs"]
|
||||
for temp_dir in temp_dirs:
|
||||
if temp_dir.exists():
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
print_info(f"已删除 {temp_dir.name} 目录")
|
||||
|
||||
# 完成
|
||||
print()
|
||||
print("=" * 50)
|
||||
print("构建完成!")
|
||||
print("=" * 50)
|
||||
print()
|
||||
print(f"离线部署包位置: {DIST_DIR}")
|
||||
print()
|
||||
print("部署步骤:")
|
||||
print("1. 将整个 dist 目录传输到目标 Linux 机器")
|
||||
print("2. 在目标机器上执行: sh dist/deploy.sh")
|
||||
print()
|
||||
print("端口映射:")
|
||||
print(" - 应用端口: 19081 (原 9081)")
|
||||
print(" - MySQL 端口: 13306 (原 3306)")
|
||||
print()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n构建已取消")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print_error(f"构建失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user