diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b2b1be6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,41 @@ +# 依赖目录 +node_modules +npm-debug.log +yarn-error.log + +# 构建产物(由 Dockerfile 内重新生成) +dist + +# Git +.git +.gitignore + +# IDE +.vscode +.idea +*.swp +*.swo + +# 文档 +README.md +*.md + +# 测试和CI +*.test.js +coverage +.github + +# 本地环境文件(生产环境变量已嵌入构建) +.env +.env.local +.env.development +.env.staging + +# 脚本(不需要进入构建上下文) +docker-push.sh +entrypoint.sh +demo.sh + +# 其他 +.DS_Store +Thumbs.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..97dd360 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# 构建阶段 +FROM node:18-alpine AS builder + +WORKDIR /app + +# 先复制包管理文件,利用缓存层 +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +# 复制源代码并构建 +COPY . . +RUN yarn build:prod + +# 提取 API 地址供运行阶段使用 +RUN grep '^VITE_BASE_API=' .env.production | sed 's/^VITE_BASE_API=[ \t]*//; s/[ \t]*$//; s/^"//; s/"$//' | tr -d '\r' > /app/.api_target + +# 运行阶段:nginx 服务静态文件 +FROM nginx:stable-alpine + +# 复制构建产物到 nginx 默认目录 +COPY --from=builder /app/dist /usr/share/nginx/html + +# 复制 API 目标地址文件和配置文件 +COPY --from=builder /app/.api_target /etc/nginx/.api_target +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# 暴露 80 端口 +EXPOSE 80 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..63fde37 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: "3.8" + +services: + aigc-frontend: + build: + context: . + dockerfile: Dockerfile + container_name: aigc-frontend + ports: + - "8080:80" + # 如需覆盖 API 地址,取消下面注释 + # environment: + # - API_TARGET=https://your-api-server.com/path + restart: unless-stopped diff --git a/docker-push.sh b/docker-push.sh new file mode 100644 index 0000000..0c726ec --- /dev/null +++ b/docker-push.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +# 进入项目目录 +cd "$(dirname "$0")" + +REGISTRY="10.153.151.42:9999" +NAMESPACE="one-feel" +IMAGE_NAME="aigc-frontend" +VERSION=$(uuidgen | tr '[:upper:]' '[:lower:]') +FULL_IMAGE="${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${VERSION}" + +echo "Building ${FULL_IMAGE} ..." +docker build -t "${FULL_IMAGE}" . + +echo "Pushing ${FULL_IMAGE} ..." +docker push "${FULL_IMAGE}" + +echo "Done: ${FULL_IMAGE}" diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..0735d01 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +# 优先使用环境变量 API_TARGET,否则从构建时提取的文件读取 VITE_BASE_API +API_TARGET=${API_TARGET:-""} + +if [ -z "$API_TARGET" ] && [ -f /etc/nginx/.api_target ]; then + API_TARGET=$(cat /etc/nginx/.api_target | tr -d '\r') +fi + +CONF_FILE="/etc/nginx/conf.d/default.conf" + +if [ -n "$API_TARGET" ]; then + # 提取路径部分(去掉协议和域名) + API_NO_PROTO=$(echo "$API_TARGET" | sed 's|^https\?://||') + API_PATH=$(echo "$API_NO_PROTO" | sed 's|^[^/]*||') + + # 如果路径为空,默认 /api + if [ -z "$API_PATH" ] || [ "$API_PATH" = "/" ]; then + API_PATH="/api" + fi + + # 确保 proxy_pass 末尾有 / + API_TARGET_SLASH="${API_TARGET%/}/" + + # 替换 nginx 配置占位符 + sed -i "s|@@API_LOCATION@@|${API_PATH}/|g" "$CONF_FILE" + sed -i "s|@@API_TARGET@@|${API_TARGET_SLASH}|g" "$CONF_FILE" +else + # 没有 API 目标,移除代理配置块 + sed -i '/# BEGIN_API_PROXY/,/# END_API_PROXY/d' "$CONF_FILE" +fi + +nginx -g "daemon off;" diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..134967a --- /dev/null +++ b/nginx.conf @@ -0,0 +1,39 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # gzip 压缩 + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + gzip_min_length 1024; + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|otf)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # API 反向代理(动态配置,启动时由 entrypoint.sh 替换占位符) + # BEGIN_API_PROXY + location @@API_LOCATION@@ { + proxy_pass @@API_TARGET@@; + proxy_set_header Host $proxy_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; + } + # END_API_PROXY + + # SPA 路由:所有请求回退到 index.html + location / { + try_files $uri $uri/ /index.html; + } + + # 安全响应头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; +}