#!/bin/bash # 遇到错误立即退出 set -e # 配置变量 REGISTRY="harbor.3prism.ai" PROJECT="fundamental_analysis" VERSION="latest" NAMESPACE="$REGISTRY/$PROJECT" # 颜色输出 GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' # No Color # 清理工作目录函数 function cleanup { echo -e "\n${YELLOW}>>> 清理临时文件...${NC}" rm -rf ./dist_bin rm -rf ./temp_build_context # 尝试删除构建容器(如果存在) docker rm -f fundamental-builder-extract 2>/dev/null || true } trap cleanup EXIT echo -e "${GREEN}=== 开始优化的构建部署流程 ===${NC}" echo -e "目标仓库: $NAMESPACE" # ========================================== # 阶段 1: 全局构建 (Build Once) # ========================================== echo -e "\n${YELLOW}>>> [阶段 1/3] 全局构建: 编译所有 Rust 服务...${NC}" echo "使用 Dockerfile: docker/Dockerfile.builder" # 检查是否需要重新构建 (这一步可以进一步优化,但为了简单起见,我们总是构建,依赖Docker层缓存) docker build -t fundamental-workspace-builder -f docker/Dockerfile.builder . # 提取二进制文件 echo -e "${YELLOW}>>> 正在提取二进制文件...${NC}" mkdir -p ./dist_bin # 创建临时容器 docker create --name fundamental-builder-extract fundamental-workspace-builder # 从容器中复制 target/release 目录下的二进制文件 # 注意: 这里我们复制整个 release 目录可能会太大,我们只复制二进制文件 # 但是 docker cp 不支持通配符复制特定文件列表,所以我们先全部复制出来,或者我们知道名字 # 定义二进制文件映射 (服务目录 -> 二进制名称) # 如果二进制名称与目录名一致,则只需列出目录名 declare -A SERVICE_BIN_MAP SERVICE_BIN_MAP=( ["data-persistence-service"]="data-persistence-service-server" ["api-gateway"]="api-gateway" ["alphavantage-provider-service"]="alphavantage-provider-service" ["tushare-provider-service"]="tushare-provider-service" ["finnhub-provider-service"]="finnhub-provider-service" ["yfinance-provider-service"]="yfinance-provider-service" ["report-generator-service"]="report-generator-service" ["workflow-orchestrator-service"]="workflow-orchestrator-service" # ["mock-provider-service"]="mock-provider-service" # Skipped for Prod ) for SERVICE_DIR in "${!SERVICE_BIN_MAP[@]}"; do BINARY_NAME="${SERVICE_BIN_MAP[$SERVICE_DIR]}" echo "提取: $BINARY_NAME" docker cp "fundamental-builder-extract:/usr/src/app/target/release/$BINARY_NAME" "./dist_bin/$BINARY_NAME" done # 删除临时容器 docker rm -f fundamental-builder-extract echo -e "${GREEN}√ 二进制提取完成${NC}" # ========================================== # 阶段 2: 前端构建 (Frontend) # ========================================== echo -e "\n${YELLOW}>>> [阶段 2/3] 构建前端服务...${NC}" FRONTEND_IMAGE="$NAMESPACE/frontend:$VERSION" docker build -t "$FRONTEND_IMAGE" -f docker/Dockerfile.frontend.prod . echo -e "${YELLOW}>>> 推送前端镜像...${NC}" docker push "$FRONTEND_IMAGE" echo -e "${GREEN}√ 前端处理完成${NC}" # ========================================== # 阶段 3: 打包与分发 (Package Many) # ========================================== echo -e "\n${YELLOW}>>> [阶段 3/3] 打包并推送后端微服务...${NC}" TOTAL_SIZE=0 for SERVICE_DIR in "${!SERVICE_BIN_MAP[@]}"; do BINARY_NAME="${SERVICE_BIN_MAP[$SERVICE_DIR]}" IMAGE_NAME="$NAMESPACE/$SERVICE_DIR:$VERSION" echo -e "\n------------------------------------------------" echo -e "${YELLOW}处理服务: $SERVICE_DIR${NC}" # 准备构建上下文 CONTEXT_DIR="./temp_build_context/$SERVICE_DIR" rm -rf "$CONTEXT_DIR" mkdir -p "$CONTEXT_DIR" mkdir -p "$CONTEXT_DIR/assets" # 1. 复制二进制文件并重命名为 app cp "./dist_bin/$BINARY_NAME" "$CONTEXT_DIR/app" # 2. 复制配置目录 (如果需要) # data-persistence-service 等服务需要根目录的 config cp -r config "$CONTEXT_DIR/config" # 3. 复制服务特定的资产 (Assets) # 3.1 Migrations if [ -d "services/$SERVICE_DIR/migrations" ]; then echo " - 包含 migrations" mkdir -p "$CONTEXT_DIR/assets/migrations" cp -r "services/$SERVICE_DIR/migrations/"* "$CONTEXT_DIR/assets/migrations/" fi # 3.2 Templates if [ -d "services/$SERVICE_DIR/templates" ]; then echo " - 包含 templates" mkdir -p "$CONTEXT_DIR/assets/templates" cp -r "services/$SERVICE_DIR/templates/"* "$CONTEXT_DIR/assets/templates/" fi # 3.3 Cookies if [ -f "services/$SERVICE_DIR/cookies.txt" ]; then echo " - 包含 cookies.txt" cp "services/$SERVICE_DIR/cookies.txt" "$CONTEXT_DIR/assets/cookies.txt" fi # 3.4 Web Assets (e.g. data-persistence-service assets folder if exists) if [ -d "services/$SERVICE_DIR/assets" ]; then echo " - 包含 web assets" cp -r "services/$SERVICE_DIR/assets/"* "$CONTEXT_DIR/assets/" fi # 4. 构建极简镜像 # 不需要传递构建参数,因为文件已经准备好了 docker build -t "$IMAGE_NAME" -f docker/Dockerfile.dist "$CONTEXT_DIR" # 5. 推送 echo -e "${YELLOW} 推送 $SERVICE_DIR 到 Harbor ...${NC}" docker push "$IMAGE_NAME" # 统计大小 SIZE_BYTES=$(docker inspect "$IMAGE_NAME" --format='{{.Size}}') TOTAL_SIZE=$(echo "$TOTAL_SIZE + $SIZE_BYTES" | bc) done TOTAL_SIZE_MB=$(echo "scale=2; $TOTAL_SIZE / 1024 / 1024" | bc) echo -e "\n${GREEN}=== 所有镜像处理完成 ===${NC}" echo -e "${GREEN}后端总大小: ${TOTAL_SIZE_MB} MB${NC}" # ========================================== # 阶段 4: 生成部署文件 # ========================================== echo -e "\n${YELLOW}>>> 正在生成服务器部署文件 docker-compose.server.yml ...${NC}" cat > docker-compose.server.yml </dev/null || exit 1"] interval: 10s timeout: 5s retries: 5 networks: - app-network restart: always api-gateway: image: $NAMESPACE/api-gateway:$VERSION container_name: api-gateway environment: SERVER_PORT: 4000 NATS_ADDR: nats://nats:4222 DATA_PERSISTENCE_SERVICE_URL: http://data-persistence-service:3000 REPORT_GENERATOR_SERVICE_URL: http://report-generator-service:8004 RUST_LOG: info,axum=info RUST_BACKTRACE: "1" depends_on: nats: condition: service_started data-persistence-service: condition: service_healthy networks: - app-network healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:4000/health >/dev/null || exit 1"] interval: 10s timeout: 5s retries: 5 restart: always alphavantage-provider-service: image: $NAMESPACE/alphavantage-provider-service:$VERSION container_name: alphavantage-provider-service volumes: - workflow_data:/mnt/workflow_data environment: SERVER_PORT: 8000 NATS_ADDR: nats://nats:4222 DATA_PERSISTENCE_SERVICE_URL: http://data-persistence-service:3000 API_GATEWAY_URL: http://api-gateway:4000 WORKFLOW_DATA_PATH: /mnt/workflow_data SERVICE_HOST: alphavantage-provider-service RUST_LOG: info RUST_BACKTRACE: "1" depends_on: - nats - data-persistence-service networks: - app-network restart: always tushare-provider-service: image: $NAMESPACE/tushare-provider-service:$VERSION container_name: tushare-provider-service volumes: - workflow_data:/mnt/workflow_data environment: SERVER_PORT: 8001 NATS_ADDR: nats://nats:4222 DATA_PERSISTENCE_SERVICE_URL: http://data-persistence-service:3000 TUSHARE_API_URL: http://api.waditu.com API_GATEWAY_URL: http://api-gateway:4000 WORKFLOW_DATA_PATH: /mnt/workflow_data SERVICE_HOST: tushare-provider-service RUST_LOG: info RUST_BACKTRACE: "1" depends_on: - nats - data-persistence-service networks: - app-network restart: always finnhub-provider-service: image: $NAMESPACE/finnhub-provider-service:$VERSION container_name: finnhub-provider-service volumes: - workflow_data:/mnt/workflow_data environment: SERVER_PORT: 8002 NATS_ADDR: nats://nats:4222 DATA_PERSISTENCE_SERVICE_URL: http://data-persistence-service:3000 FINNHUB_API_URL: https://finnhub.io/api/v1 API_GATEWAY_URL: http://api-gateway:4000 WORKFLOW_DATA_PATH: /mnt/workflow_data SERVICE_HOST: finnhub-provider-service RUST_LOG: info RUST_BACKTRACE: "1" depends_on: - nats - data-persistence-service networks: - app-network restart: always yfinance-provider-service: image: $NAMESPACE/yfinance-provider-service:$VERSION container_name: yfinance-provider-service volumes: - workflow_data:/mnt/workflow_data environment: SERVER_PORT: 8003 NATS_ADDR: nats://nats:4222 DATA_PERSISTENCE_SERVICE_URL: http://data-persistence-service:3000 API_GATEWAY_URL: http://api-gateway:4000 WORKFLOW_DATA_PATH: /mnt/workflow_data SERVICE_HOST: yfinance-provider-service RUST_LOG: info RUST_BACKTRACE: "1" depends_on: - nats - data-persistence-service networks: - app-network dns: - 8.8.8.8 - 8.8.4.4 restart: always report-generator-service: image: $NAMESPACE/report-generator-service:$VERSION container_name: report-generator-service volumes: - workflow_data:/mnt/workflow_data environment: SERVER_PORT: 8004 NATS_ADDR: nats://nats:4222 DATA_PERSISTENCE_SERVICE_URL: http://data-persistence-service:3000 GOTENBERG_URL: http://gotenberg:3000 WORKFLOW_DATA_PATH: /mnt/workflow_data RUST_LOG: info RUST_BACKTRACE: "1" depends_on: - nats - data-persistence-service - gotenberg networks: - app-network restart: always workflow-orchestrator-service: image: $NAMESPACE/workflow-orchestrator-service:$VERSION container_name: workflow-orchestrator-service volumes: - workflow_data:/mnt/workflow_data environment: SERVER_PORT: 8005 NATS_ADDR: nats://nats:4222 DATA_PERSISTENCE_SERVICE_URL: http://data-persistence-service:3000 WORKFLOW_DATA_PATH: /mnt/workflow_data RUST_LOG: info RUST_BACKTRACE: "1" depends_on: - nats - data-persistence-service networks: - app-network restart: always gotenberg: image: gotenberg/gotenberg:8 container_name: gotenberg networks: - app-network restart: always frontend: image: $NAMESPACE/frontend:$VERSION container_name: fundamental-frontend ports: - "8080:80" # Map host 8080 to container 80 (Nginx) depends_on: api-gateway: condition: service_healthy networks: - app-network restart: always volumes: workflow_data: pgdata: nats_data: networks: app-network: YAML echo -e "${GREEN}生成完成: docker-compose.server.yml${NC}" echo -e "请执行以下步骤更新远端服务器:" echo -e "1. 将 docker-compose.server.yml 复制到服务器" echo -e "2. 在服务器执行: docker-compose -f docker-compose.server.yml pull (拉取最新镜像)" echo -e "3. 在服务器执行: docker-compose -f docker-compose.server.yml up -d (重启服务)" echo -e " 或者一键命令: docker-compose -f docker-compose.server.yml up -d --pull always"