From e5e97e640bd0306b1e3801fce6825042c08a5179 Mon Sep 17 00:00:00 2001 From: Cheng Zhou Date: Tue, 31 Mar 2026 09:17:12 +0800 Subject: [PATCH] =?UTF-8?q?release(main):=20=E9=9B=86=E6=88=90=20nginx=20?= =?UTF-8?q?=E5=90=8C=E5=9F=9F=E5=85=A5=E5=8F=A3=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++--- docker-compose.yaml | 19 ++++-- ...03-27-production-compose-implementation.md | 4 +- docs/plans/2026-03-31-restore-nginx-design.md | 19 ++++++ frontend/Caddyfile | 5 -- frontend/Dockerfile | 10 +-- nginx/Dockerfile | 14 ++++ nginx/nginx.conf | 65 +++++++++++++++++++ 8 files changed, 122 insertions(+), 30 deletions(-) create mode 100644 docs/plans/2026-03-31-restore-nginx-design.md delete mode 100644 frontend/Caddyfile create mode 100644 nginx/Dockerfile create mode 100644 nginx/nginx.conf diff --git a/README.md b/README.md index b4a134db..8e16e423 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ - 生产入口:`docker-compose.yaml` - 初始化方式:`docker compose run --rm backend-init` - 启动方式:`docker compose up -d --build` -- 运行拓扑:`frontend`、`backend`、`db` -- 对外入口:仓库外部统一反向代理;建议将 `/` 转发到 `frontend`,将 `/api` 与 `/health` 转发到 `backend` +- 运行拓扑:`nginx`、`backend`、`db` +- 对外入口:`nginx` 提供前端静态资源,并同域反代后端 API - 数据库 schema 来源:Alembic migration,不再依赖 `database/init.sql` - 默认无任何 demo 数据;生产初始化只确保固定管理员 `admin@huapont.cn / admin123` 存在 - 验证方式: - `docker compose config` - - `curl -i http://127.0.0.1:4173/` - - `curl -i http://127.0.0.1:8000/health` + - `curl -i http://127.0.0.1/` + - `curl -i http://127.0.0.1/health` ## 账号与注册 - 初始化管理员:`admin@huapont.cn / admin123`(通过生产初始化命令显式创建) @@ -28,11 +28,9 @@ - 普通成员(无项目角色):仅浏览 ## 访问方式 -- 前端:`http://localhost:4173` -- 后端 API:`http://localhost:8000/api/v1/*` -- 生产环境建议通过外部反向代理提供同域访问,前端代码默认请求同域 `/api/v1/*` -- 如果本地直接访问 `http://localhost:4173`,可在 `frontend/.env` 中设置 `VITE_API_BASE_URL=http://localhost:8000`,让前端直连后端 API -- `docker compose` 构建前端容器时,默认不注入 `VITE_API_BASE_URL`,生产会走同域 `/api/v1/*`;仅在本地直连后端时才显式设置 `VITE_API_BASE_URL=http://localhost:8000` +- 前端:`http://localhost` +- 后端 API:同域 `/api/v1/*` +- `nginx` 负责托管前端静态资源,并将 `/api` 与 `/health` 转发到 `backend` ## 仓库治理文档 - 分支治理规范:`docs/branch-governance.md` diff --git a/docker-compose.yaml b/docker-compose.yaml index a537a1c5..452ba2b8 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -50,16 +50,21 @@ services: networks: - ctms_net - frontend: + nginx: build: - context: ./frontend - dockerfile: Dockerfile - args: - VITE_API_BASE_URL: ${VITE_API_BASE_URL:-} - container_name: ctms_frontend + context: . + dockerfile: nginx/Dockerfile + container_name: ctms_nginx restart: always ports: - - "4173:4173" + - "80:80" + - "443:443" + - "8888:8888" + volumes: + - ./nginx/certs:/etc/nginx/certs:ro + - ./frontend/public/favicon.ico:/usr/share/nginx/html/favicon.ico:ro + depends_on: + - backend networks: - ctms_net diff --git a/docs/plans/2026-03-27-production-compose-implementation.md b/docs/plans/2026-03-27-production-compose-implementation.md index d1688a3d..b4c2159c 100644 --- a/docs/plans/2026-03-27-production-compose-implementation.md +++ b/docs/plans/2026-03-27-production-compose-implementation.md @@ -103,8 +103,8 @@ Expected: PASS **Step 3: Check HTTP endpoints** -Run: `curl -i http://127.0.0.1:4173/` +Run: `curl -i http://127.0.0.1/` Expected: `200 OK` and HTML payload -Run: `curl -i http://127.0.0.1:8000/health` +Run: `curl -i http://127.0.0.1/health` Expected: successful backend health response diff --git a/docs/plans/2026-03-31-restore-nginx-design.md b/docs/plans/2026-03-31-restore-nginx-design.md new file mode 100644 index 00000000..6217681d --- /dev/null +++ b/docs/plans/2026-03-31-restore-nginx-design.md @@ -0,0 +1,19 @@ +# Restore In-Repo Nginx Design + +**Goal:** Restore the repository-managed Nginx entrypoint on `dev` by writing back the earlier removal diff, so frontend static hosting and backend API proxying return to a single same-origin entry. + +**Decisions** +- Restore `nginx/nginx.conf` and `nginx/Dockerfile` from the pre-removal topology. +- Replace the `frontend` runtime service in `docker-compose.yaml` with an `nginx` service that serves the built SPA and proxies `/api` and `/health` to `backend`. +- Remove the Caddy-specific runtime file because the repository will no longer use a separate frontend web server. +- Keep later non-nginx fixes that remain valid, especially the frontend API base URL fallback logic for optional local direct access. + +**Architecture** +- `db` remains unchanged. +- `backend` remains unchanged and is reachable internally on `backend:8000`. +- `nginx` becomes the public entrypoint again, serving the frontend bundle and reverse proxying API traffic to `backend`. +- The browser returns to same-origin access through the Nginx entrypoint. + +**Operational Notes** +- This is an in-place restoration based on the previous removal footprint, not a fresh redesign. +- The current Tencent Cloud/private registry removal remains intact and is not being reintroduced. diff --git a/frontend/Caddyfile b/frontend/Caddyfile deleted file mode 100644 index 38576a03..00000000 --- a/frontend/Caddyfile +++ /dev/null @@ -1,5 +0,0 @@ -:4173 - -root * /srv -try_files {path} /index.html -file_server diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 106ffcac..b145d8c4 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -2,18 +2,14 @@ FROM node:20-alpine AS build WORKDIR /app -ARG VITE_API_BASE_URL -ENV VITE_API_BASE_URL=${VITE_API_BASE_URL} - COPY package*.json ./ RUN npm ci COPY . . RUN npm run build -FROM caddy:2-alpine +FROM alpine:3.21 -COPY Caddyfile /etc/caddy/Caddyfile -COPY --from=build /app/dist /srv +WORKDIR /opt/frontend -EXPOSE 4173 +COPY --from=build /app/dist ./dist diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 00000000..d394f9fc --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,14 @@ +FROM node:20-alpine AS frontend-build + +WORKDIR /app + +COPY frontend/package*.json ./ +RUN npm ci + +COPY frontend/ ./ +RUN npm run build + +FROM nginx:1.27-alpine + +COPY nginx/nginx.conf /etc/nginx/nginx.conf +COPY --from=frontend-build /app/dist /usr/share/nginx/html diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 00000000..37ff0e79 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,65 @@ +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + resolver 127.0.0.11 valid=10s ipv6=off; + + 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; + + sendfile on; + keepalive_timeout 65; + root /usr/share/nginx/html; + index index.html; + + upstream backend { + zone backend 64k; + server backend:8000 resolve; + } + + server { + listen 80; + server_name _; + client_max_body_size 20m; + + location = /favicon.ico { + root /usr/share/nginx/html; + try_files /favicon.ico =204; + access_log off; + log_not_found off; + expires 1d; + add_header Cache-Control "public, max-age=86400" always; + } + + location /api/ { + proxy_pass http://backend; + 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 { + proxy_pass http://backend; + 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 / { + try_files $uri $uri/ /index.html; + } + } +}