From 1a54a55690b8b24f4053a83e402b6aada11802ea Mon Sep 17 00:00:00 2001 From: root Date: Wed, 20 Aug 2025 11:20:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E7=9A=84Windows=E6=9E=84=E5=BB=BA=E8=84=9A=E6=9C=AC=20build.ba?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支持自动检测包管理器(npm/yarn) - 包含Node.js版本检查 - 自动安装依赖和TypeScript - 构建生产版本并创建发布目录 - 提供详细的部署说明 - 支持命令行参数控制构建过程 --- src/X1.WebUI/.dockerignore | 19 +++ src/X1.WebUI/Dockerfile | 32 +++++ src/X1.WebUI/build.bat | 248 +++++++++++++++++++++++++++++++++ src/X1.WebUI/deploy.sh | 207 +++++++++++++++++++++++++++ src/X1.WebUI/nginx.conf | 74 ++++++++++ src/X1.WebUI/package-lock.json | 9 +- src/X1.WebUI/package.json | 2 +- src/X1.WebUI/yarn.lock | 8 +- 8 files changed, 589 insertions(+), 10 deletions(-) create mode 100644 src/X1.WebUI/.dockerignore create mode 100644 src/X1.WebUI/Dockerfile create mode 100644 src/X1.WebUI/build.bat create mode 100644 src/X1.WebUI/deploy.sh create mode 100644 src/X1.WebUI/nginx.conf diff --git a/src/X1.WebUI/.dockerignore b/src/X1.WebUI/.dockerignore new file mode 100644 index 0000000..aa3b856 --- /dev/null +++ b/src/X1.WebUI/.dockerignore @@ -0,0 +1,19 @@ +node_modules +npm-debug.log +.git +.gitignore +README.md +.env +.env.local +.env.development +.env.test +.nyc_output +coverage +.vscode +.idea +*.log +dist +.DS_Store +Thumbs.db +build.bat +deploy.sh diff --git a/src/X1.WebUI/Dockerfile b/src/X1.WebUI/Dockerfile new file mode 100644 index 0000000..a3ad88e --- /dev/null +++ b/src/X1.WebUI/Dockerfile @@ -0,0 +1,32 @@ +# 使用官方Node.js 18 Alpine镜像作为构建环境 +FROM node:18-alpine AS builder + +# 设置工作目录 +WORKDIR /app + +# 复制package.json和package-lock.json +COPY package*.json ./ + +# 安装依赖 +RUN npm ci --only=production + +# 复制源代码 +COPY . . + +# 构建生产版本 +RUN npm run build + +# 使用nginx Alpine镜像作为运行环境 +FROM nginx:alpine + +# 复制构建产物到nginx目录 +COPY --from=builder /app/dist /usr/share/nginx/html + +# 复制nginx配置文件 +COPY nginx.conf /etc/nginx/nginx.conf + +# 暴露端口 +EXPOSE 80 + +# 启动nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/src/X1.WebUI/build.bat b/src/X1.WebUI/build.bat new file mode 100644 index 0000000..a326001 --- /dev/null +++ b/src/X1.WebUI/build.bat @@ -0,0 +1,248 @@ +@echo off +setlocal enabledelayedexpansion +chcp 65001 >nul + +REM 设置错误时退出 +set "EXIT_CODE=0" + +REM 解析命令行参数 +call :parse_arguments %* + +REM 主函数 +call :main +goto :end + +REM 主函数 +:main +call :log_info "Starting X1.WebUI build process..." + +call :check_node_version +if !EXIT_CODE! neq 0 goto :end + +call :detect_package_manager +if !EXIT_CODE! neq 0 goto :end + +call :install_dependencies +if !EXIT_CODE! neq 0 goto :end + +call :build_project +if !EXIT_CODE! neq 0 goto :end + +call :create_publish_dir +call :show_publish_info +call :show_deployment_instructions + +call :log_success "Build completed!" +call :log_info "Please upload the publish directory contents to server /home/hyh/x1_web/" +goto :eof + +REM 检查Node.js版本 +:check_node_version +call :log_info "Checking Node.js version..." +node --version >nul 2>&1 +if errorlevel 1 ( + call :log_error "Node.js not installed, please install Node.js 18+" + set "EXIT_CODE=1" + goto :end +) + +for /f "tokens=*" %%i in ('node --version') do set "NODE_VERSION=%%i" +call :log_success "Node.js version check passed: !NODE_VERSION!" +goto :eof + +REM 解析命令行参数 +:parse_arguments +set "PACKAGE_MANAGER=" +set "SHOW_HELP=false" + +:parse_loop +if "%~1"=="" goto :parse_done +if /i "%~1"=="--npm" ( + set "PACKAGE_MANAGER=npm" + call :log_info "Package manager set to npm via command line" +) else if /i "%~1"=="--yarn" ( + set "PACKAGE_MANAGER=yarn" + call :log_info "Package manager set to yarn via command line" +) else if /i "%~1"=="--help" ( + set "SHOW_HELP=true" +) else ( + call :log_error "Unknown parameter: %~1" + call :show_help + set "EXIT_CODE=1" + goto :end +) +shift +goto :parse_loop + +:parse_done +if "!SHOW_HELP!"=="true" ( + call :show_help + set "EXIT_CODE=0" + goto :end +) +goto :eof + +REM 显示帮助信息 +:show_help +echo. +echo Usage: build.bat [options] +echo. +echo Options: +echo --npm Force using npm package manager +echo --yarn Force using yarn package manager +echo --help Show this help message +echo. +echo Examples: +echo build.bat - Auto detect package manager +echo build.bat --npm - Force using npm +echo build.bat --yarn - Force using yarn +echo build.bat --help - Show help +echo. +goto :eof + +REM 检测包管理器 +:detect_package_manager +call :log_info "Detecting package manager..." +if not "!PACKAGE_MANAGER!"=="" ( + call :log_info "Package manager already set to: !PACKAGE_MANAGER!" + goto :eof +) + +if exist "yarn.lock" ( + set "PACKAGE_MANAGER=yarn" + call :log_info "Found yarn.lock, using yarn" +) else if exist "package-lock.json" ( + set "PACKAGE_MANAGER=npm" + call :log_info "Found package-lock.json, using npm" +) else ( + set "PACKAGE_MANAGER=npm" + call :log_info "No lock file found, defaulting to npm" +) +goto :eof + +REM 安装依赖 +:install_dependencies +call :log_info "Installing project dependencies..." +if not exist "node_modules" ( + if "!PACKAGE_MANAGER!"=="yarn" ( + call yarn install --frozen-lockfile + ) else ( + call npm ci + ) +) else ( + if "!PACKAGE_MANAGER!"=="yarn" ( + call yarn install --frozen-lockfile --production + ) else ( + call npm ci --only=production + ) +) +if errorlevel 1 ( + call :log_error "Dependency installation failed" + set "EXIT_CODE=1" + goto :end +) +call :log_success "Dependencies installed successfully" +goto :eof + +REM 构建项目 +:build_project +call :log_info "Building production version..." + + REM 直接尝试构建,如果失败则安装TypeScript +call :log_info "Attempting to build project..." +if "!PACKAGE_MANAGER!"=="yarn" ( + call yarn build +) else ( + call npm run build +) +if errorlevel 1 ( + call :log_warning "Build failed, checking if TypeScript is needed..." + call :log_info "Installing TypeScript as dev dependency..." + if "!PACKAGE_MANAGER!"=="yarn" ( + call yarn add -D typescript + ) else ( + call npm install --save-dev typescript + ) + + call :log_info "Retrying build after TypeScript installation..." + if "!PACKAGE_MANAGER!"=="yarn" ( + call yarn build + ) else ( + call npm run build + ) + if errorlevel 1 ( + call :log_error "Project build failed even after TypeScript installation" + set "EXIT_CODE=1" + goto :end + ) +) +call :log_success "Project build completed" +goto :eof + +REM 创建发布目录 +:create_publish_dir +call :log_info "Creating publish directory..." +if exist "publish" rmdir /s /q "publish" +mkdir "publish" + +REM 复制构建产物到发布目录根目录 +xcopy "dist\*" "publish\" /E /Y /Q + +REM 复制Dockerfile和nginx配置到发布目录根目录 +copy "Dockerfile" "publish\" >nul +copy "nginx.conf" "publish\" >nul + +REM 复制部署脚本到发布目录根目录 +copy "deploy.sh" "publish\" >nul + +REM 注意:.dockerignore 文件不需要复制到发布目录,只在本地构建时使用 + +call :log_success "Publish directory created: publish/" +goto :eof + +REM 显示文件大小 +:show_publish_info +call :log_info "Publish directory info:" +for /f "tokens=1" %%i in ('dir publish /s ^| find "个文件"') do echo File count: %%i +echo. +call :log_info "Publish directory contents:" +dir publish /B +echo. +call :log_info "Publish directory structure:" +tree publish /F +goto :eof + +REM 显示部署说明 +:show_deployment_instructions +call :log_info "Deployment instructions:" +echo. +echo 1. Upload all contents of publish directory to server /home/hyh/x1_web/ +echo 2. Ensure the following files are in /home/hyh/x1_web/ root directory: +echo - Dockerfile +echo - nginx.conf +echo - deploy.sh +echo - index.html +echo - assets/ directory +echo 3. On server run: chmod +x deploy.sh ^&^& ./deploy.sh +echo. +goto :eof + +REM 日志函数(简化版本,不使用颜色) +:log_info +echo [INFO] %~1 +goto :eof + +:log_success +echo [SUCCESS] %~1 +goto :eof + +:log_warning +echo [WARNING] %~1 +goto :eof + +:log_error +echo [ERROR] %~1 +goto :eof + +:end +exit /b %EXIT_CODE% diff --git a/src/X1.WebUI/deploy.sh b/src/X1.WebUI/deploy.sh new file mode 100644 index 0000000..16bcce6 --- /dev/null +++ b/src/X1.WebUI/deploy.sh @@ -0,0 +1,207 @@ +#!/bin/bash + +# 设置错误时退出 +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 日志函数 +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 默认配置 +IMAGE_NAME="x1-webui" +CONTAINER_NAME="x1-webui-container" +PORT="9905" +REMOVE_MODE=false + +# 解析命令行参数 +parse_arguments() { + while [[ $# -gt 0 ]]; do + case $1 in + -p|--port) + PORT="$2" + shift 2 + ;; + -i|--image) + IMAGE_NAME="$2" + shift 2 + ;; + -c|--container) + CONTAINER_NAME="$2" + shift 2 + ;; + -r|--remove) + REMOVE_MODE=true + shift + ;; + -h|--help) + echo "用法: $0 [选项]" + echo "选项:" + echo " -p, --port PORT 指定端口 (默认: 9905)" + echo " -i, --image NAME 指定镜像名称 (默认: x1-webui)" + echo " -c, --container NAME 指定容器名称 (默认: x1-webui-container)" + echo " -r, --remove 删除旧容器和镜像" + echo " -h, --help 显示帮助信息" + exit 0 + ;; + *) + log_error "未知选项: $1" + exit 1 + ;; + esac + done +} + +# 检查Docker +check_docker() { + log_info "检查Docker安装状态..." + if ! command -v docker &> /dev/null; then + log_error "Docker未安装,请先安装Docker" + exit 1 + fi + + if ! docker info &> /dev/null; then + log_error "Docker服务未运行,请启动Docker服务" + exit 1 + fi + + log_success "Docker检查通过" +} + +# 检查必要文件 +check_files() { + log_info "检查必要文件..." + + local required_files=("Dockerfile" "nginx.conf" "index.html") + for file in "${required_files[@]}"; do + if [ ! -f "$file" ]; then + log_error "缺少必要文件: $file" + exit 1 + fi + done + + log_success "文件检查通过" +} + +# 停止并删除旧容器 +stop_old_container() { + log_info "停止旧容器..." + if docker ps -a --format "table {{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then + docker stop "$CONTAINER_NAME" || true + docker rm "$CONTAINER_NAME" || true + log_success "旧容器已停止并删除" + else + log_info "没有找到旧容器" + fi +} + +# 删除旧镜像 +remove_old_image() { + if [ "$REMOVE_MODE" = true ]; then + log_info "删除旧镜像..." + if docker images --format "table {{.Repository}}" | grep -q "^${IMAGE_NAME}$"; then + docker rmi "$IMAGE_NAME" || true + log_success "旧镜像已删除" + else + log_info "没有找到旧镜像" + fi + fi +} + +# 构建Docker镜像 +build_image() { + log_info "构建Docker镜像: $IMAGE_NAME" + docker build -t "$IMAGE_NAME" . + log_success "镜像构建完成" +} + +# 运行容器 +run_container() { + log_info "启动容器: $CONTAINER_NAME" + docker run -d \ + --name "$CONTAINER_NAME" \ + -p "$PORT:80" \ + --restart unless-stopped \ + "$IMAGE_NAME" + + log_success "容器启动完成" +} + +# 健康检查 +health_check() { + log_info "等待应用启动..." + local max_attempts=30 + local attempt=1 + + while [ $attempt -le $max_attempts ]; do + if curl -f -s "http://localhost:$PORT/health" > /dev/null 2>&1; then + log_success "应用启动成功!" + return 0 + fi + + log_info "等待应用启动... ($attempt/$max_attempts)" + sleep 2 + ((attempt++)) + done + + log_warning "应用启动超时,但容器可能仍在运行" + return 1 +} + +# 显示部署信息 +show_deployment_info() { + log_success "部署完成!" + echo "" + echo "部署信息:" + echo " 镜像名称: $IMAGE_NAME" + echo " 容器名称: $CONTAINER_NAME" + echo " 访问端口: $PORT" + echo " 访问地址: http://localhost:$PORT" + echo "" + + log_info "容器状态:" + docker ps --filter "name=$CONTAINER_NAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + + echo "" + log_info "容器日志:" + docker logs --tail 10 "$CONTAINER_NAME" +} + +# 主函数 +main() { + log_info "开始部署X1.WebUI..." + + parse_arguments "$@" + check_docker + check_files + stop_old_container + remove_old_image + build_image + run_container + health_check + show_deployment_info + + log_success "部署完成!" +} + +# 执行主函数 +main "$@" diff --git a/src/X1.WebUI/nginx.conf b/src/X1.WebUI/nginx.conf new file mode 100644 index 0000000..74d0b83 --- /dev/null +++ b/src/X1.WebUI/nginx.conf @@ -0,0 +1,74 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # 日志格式 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log; + + # 基本设置 + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # 处理React Router的客户端路由 + location / { + try_files $uri $uri/ /index.html; + } + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # API代理到后端9905端口 + location /api/ { + proxy_pass http://host.docker.internal:9905; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # 健康检查 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +} diff --git a/src/X1.WebUI/package-lock.json b/src/X1.WebUI/package-lock.json index 1d54e7d..027b825 100644 --- a/src/X1.WebUI/package-lock.json +++ b/src/X1.WebUI/package-lock.json @@ -69,7 +69,7 @@ "eslint-plugin-react-refresh": "^0.4.5", "postcss": "^8.4.33", "tailwindcss": "^3.4.1", - "typescript": "^5.2.2", + "typescript": "^5.9.2", "vite": "^5.0.8" } }, @@ -6502,11 +6502,10 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.2", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/X1.WebUI/package.json b/src/X1.WebUI/package.json index fd10f65..b03cf3f 100644 --- a/src/X1.WebUI/package.json +++ b/src/X1.WebUI/package.json @@ -71,7 +71,7 @@ "eslint-plugin-react-refresh": "^0.4.5", "postcss": "^8.4.33", "tailwindcss": "^3.4.1", - "typescript": "^5.2.2", + "typescript": "^5.9.2", "vite": "^5.0.8" } } diff --git a/src/X1.WebUI/yarn.lock b/src/X1.WebUI/yarn.lock index 24315e6..37e5edc 100644 --- a/src/X1.WebUI/yarn.lock +++ b/src/X1.WebUI/yarn.lock @@ -3419,10 +3419,10 @@ type-fest@^0.20.2: resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -typescript@^5.2.2, typescript@>=4.2.0: - version "5.8.3" - resolved "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== +typescript@^5.9.2, typescript@>=4.2.0: + version "5.9.2" + resolved "https://registry.npmmirror.com/typescript/-/typescript-5.9.2.tgz" + integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== undici-types@~6.21.0: version "6.21.0"