diff --git a/.env.example b/.env.example index 77df9d48..f7962b98 100644 --- a/.env.example +++ b/.env.example @@ -42,6 +42,9 @@ AI_API_KEY=your-ai-api-key # ── 服务配置 ── WINDUP_HOST=127.0.0.1 WINDUP_PORT=8000 +# 前端构建期写入的后端地址。默认相对路径,由宿主机 nginx 反代给 backend; +# 只有在前端要指向外部后端时才改成完整 URL,那时后端需同步配 WINDUP_CORS_ORIGINS +VITE_API_BASE_URL=/api # 跨域来源(逗号分隔,默认覆盖 localhost:5173/3000) WINDUP_CORS_ORIGINS= # 跨域正则匹配(默认允许 *.vercel.app) diff --git a/docker-compose.yml b/docker-compose.yml index b1eaf551..f5d769c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,6 +67,25 @@ services: networks: - windup-net + # ── 前端静态产物构建任务 ── + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + args: + # 构建期烘进产物的后端地址,运行期改不动,所以放在 build.args 而非 + # environment。默认相对路径 → 与页面同源 → 不产生跨域,后端不必配来源白名单。 + VITE_API_BASE_URL: ${VITE_API_BASE_URL:-/api} + container_name: windup-frontend + restart: "no" + depends_on: + - backend + volumes: + # 构建命令直接写入宿主机 nginx 的站点目录;容器退出后产物仍保留。 + - /var/www/react-windup:/var/www/react-windup + networks: + - windup-net + networks: windup-net: driver: bridge diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 00000000..acaaa77b --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,15 @@ +# 构建上下文排除项:本地 node_modules 与 dist 加起来数百 MB, +# 全量传给 daemon 会让每次构建白等一分钟,且容器内会重新装依赖、重新构建。 +node_modules +dist +dist-ssr + +# 本地环境变量文件不进镜像;后端地址由 Dockerfile 的 build arg 传入。 +.env +.env.* + +.vercel +.git +.gitignore +.DS_Store +*.log diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 00000000..50e58805 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,28 @@ +# ── 前端构建容器 ───────────────────────────────────────────────────── +# 容器只负责生成静态产物,宿主机 nginx 直接读取挂载目录。 +# Node 版本跟 frontend-ci.yml 对齐,避免 CI 过了、镜像里构建结果不同 +FROM node:24-alpine + +# 国内网络:走镜像源,与 backend/Dockerfile 换阿里 pypi 源同因 +ENV NPM_CONFIG_REGISTRY=https://registry.npmmirror.com + +WORKDIR /app + +# 先只拷依赖清单,命中 Docker layer cache —— 改源码不触发重装依赖 +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . + +# VITE_ 前缀的变量在 vite build 时就被烘进 dist,运行期再往容器注环境变量对 +# 已构建产物无效 —— 这是前端和 backend 在配置方式上唯一的实质差别。 +# 默认值 /api 是相对路径:浏览器请求与页面同源,由宿主机 nginx 转发给后端, +# 构建镜像因此不与任何域名绑定,同一份产物可直接换环境跑。 +# 若某个环境确实要指向外部后端,构建时传 --build-arg VITE_API_BASE_URL=https://…, +# 但那样后端必须同步配 WINDUP_CORS_ORIGINS,否则浏览器预检就被拦死。 +ARG VITE_API_BASE_URL=/api +ENV VITE_API_BASE_URL=$VITE_API_BASE_URL + +# build 脚本是 tsc -b && vite build,类型不过直接构建失败。产物直接写进 +# Compose 挂载的宿主机目录;构建完成后进程退出,不启动常驻服务。 +CMD ["npm", "run", "build", "--", "--outDir", "/var/www/react-windup", "--emptyOutDir"] diff --git a/frontend/README.md b/frontend/README.md index 6b946285..0a22e3d4 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -21,6 +21,22 @@ npm run build # 构建 CI 按上面顺序全跑一遍。 +## 容器构建与宿主机部署 + +服务器由宿主机 nginx 提供静态资源并反代 `/api`。前端容器只构建产物: + +```bash +cp .env.example .env +sudo mkdir -p /var/www/react-windup +docker compose up -d --build frontend +``` + +构建命令会清空专用站点目录 `/var/www/react-windup`,再把文件直接写入宿主机;完成后容器正常退出,没有端口映射,也不常驻。宿主机 nginx 的站点根目录需指向该目录,并负责 SPA 回退与 `/api` 反代。之后每次更新仍执行上面的 Compose 命令,nginx 会直接读取新产物。 + +`VITE_API_BASE_URL` 是构建期变量,`vite build` 时就烘进产物,运行期给容器注环境变量无效。默认取 `/api` 走宿主机反代;确实要指向外部后端时,构建时传 `--build-arg VITE_API_BASE_URL=https://…`,同时后端得配 `WINDUP_CORS_ORIGINS`。 + +Vercel 部署路径不受影响,`vercel.json` 照旧。 + ## 结构 `ProjectApis` 与 `CharacterApis` 负责业务 DTO 映射。项目中心、项目工作区、资产库与角色详情已接入 PR #75 的真实接口;测试数据只存在于测试环境的 HTTP 替身中。