diff --git a/.env.example b/.qenv.example similarity index 100% rename from .env.example rename to .qenv.example diff --git a/DEPLOY.md b/DEPLOY.md deleted file mode 100644 index 132374a..0000000 --- a/DEPLOY.md +++ /dev/null @@ -1,78 +0,0 @@ -# 部署与 CI/CD 说明 - -项目结构:前端 Astro 静态站 + 后端 Go 服务 + MySQL。采用 **Docker 容器化 + Gitea Actions** 上线。 - -## 一、架构 - -``` -浏览器 - │ HTTP (80) - ▼ -[frontend] nginx:80 ── 静态资源 / , 反代 /api ──▶ [backend] Go :8090 - │ - ▼ - [db] MySQL 8.0 -``` - -- **前端**:Astro 构建为静态 `dist/`,用 nginx 托管;`/api/v1` 由 nginx 反代到 `backend:8090`(同源,无跨域)。 -- **后端**:Go 二进制,监听 `8090`,连 `db` 服务。 -- **构建期取数**:品牌页等通过 SSG 内部接口(`/api/v1/ssg/*`,基址 `BASE_API_SSG`,默认 `http://localhost:8091`)在构建时拉数据;运行期浏览器走同源 `/api/v1`(基址 `BASE_API` 置空)。 - -## 二、服务器目录约定 - -```bash -/opt/fashion/ -├── test/ # 前端仓 git clone(含 docker-compose.yml / deploy.sh / Dockerfile / nginx.conf) -└── admin/ - └── backend/ # 后端仓 git clone(含 Dockerfile) -``` - -初始化(在服务器上): - -```bash -mkdir -p /opt/fashion && cd /opt/fashion -git clone <前端仓地址> test -git clone <后端仓地址> admin/backend -cd test -cp .env.example .env # 按需修改 JWT_SECRET / DB_PASSWORD -``` - -前置:服务器已安装 Docker Engine + Docker Compose v2,且当前用户可免 sudo 执行 docker。 - -## 三、手动一键上线 - -在服务器上(或本地 SSH 过去)执行: - -```bash -cd /opt/fashion/test -./deploy.sh -``` - -`deploy.sh` 会:① 拉两个仓库最新代码 → ② `docker compose up -d db backend` → ③ 轮询等待 backend 健康 → ④ `docker compose build frontend`(构建期 SSR 拉数据)→ ⑤ `docker compose up -d frontend`。 - -完成后访问服务器 `http://<服务器IP>/` 即可。 - -## 四、CI/CD(Gitea Actions) - -仓库根已放入 `.gitea/workflows/deploy.yml`。push 到 `main`/`master` 即自动 SSH 到服务器执行 `./deploy.sh`。 - -前置(Gitea 仓库 → Settings → Secrets / Actions): - -| Secret | 说明 | -| ------------- | ---------------------------- | -| `DEPLOY_HOST` | 服务器 IP / 域名 | -| `DEPLOY_USER` | SSH 用户名 | -| `DEPLOY_KEY` | 服务器私钥(对应公钥已放入 `~/.ssh/authorized_keys`) | - -> 后端仓库也放一份相同的 `.gitea/workflows/deploy.yml`,这样前后端任意仓库 push 都会触发一次完整上线(deploy.sh 会同时 pull 两个仓)。 - -## 五、HTTPS(可选) - -在服务器前置一个反向代理(如 Caddy / 已有 nginx)终止 TLS,把 80 转发到 frontend 容器,并加 `X-Forwarded-Proto`。或把 frontend 的 `ports` 换成 `443` + 挂载证书。此处从简,未内置。 - -## 六、注意事项 - -- 后端环境变量名(`DB_HOST`/`JWT_SECRET` 等)需与实际 `config` 读取一致;如后端用别的变量名,改 `docker-compose.yml` 的 `environment` 即可。 -- `JWT_SECRET` 生产务必改成随机长串;默认值是占位的。 -- 生产可去掉 `backend.ports` 的 `8090:8090` 暴露,仅由前端 nginx 在同一 compose 网络内反代访问,更安全。 -- 前端 `BASE_API` 在 Dockerfile 中置空(同源 `/api`);如需独立域名,改为绝对地址并在后端已开启 CORS(`*`)。 diff --git a/Dockerfile b/Dockerfile index b6454b0..c142459 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,9 +23,15 @@ RUN npm ci COPY . . RUN npm run build -# ---- 静态托管阶段 ---- -FROM nginx:alpine -COPY --from=build /app/dist /usr/share/nginx/html +# ---- 运行阶段 ---- +# 前端以 @astrojs/node standalone server 运行(监听 3000),nginx 反代 / 到该端口、 +# 反代 /api 到 backend:8090。这样 on-demand SSR 详情页 / sitemap / robots 才能正确产出 HTML。 +FROM node:22-alpine +RUN apk add --no-cache nginx +WORKDIR /app +COPY --from=build /app/dist ./dist COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] +ENTRYPOINT ["/entrypoint.sh"] diff --git a/astro.config.mjs b/astro.config.mjs index b8af0c3..1c48b2d 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -36,6 +36,13 @@ export default defineConfig({ mode: 'standalone' }), + // 运行期 node server 监听地址(容器内部)。nginx 反代到该端口。 + // host: true 监听 0.0.0.0,便于同容器内 nginx 以 127.0.0.1:3000 转发。 + server: { + host: true, + port: 3000, + }, + // base 地址配置已迁到 src/lib/config(dev/prod 两份 TS,缺则抛异常)。 // 仅「密钥」类仍走构建期 env(如 SSG_TOKEN),不在此声明,也不进客户端 bundle。 }); \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 9b608e3..b05b23e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,20 @@ -# 生产部署 compose(放在前端仓库根,服务器结构见 DEPLOY.md) +# 生产部署 compose(放在前端仓库根;服务器结构见仓库 README) # 前端仓: /opt/fashion/test (本文件所在) -# 后端仓: /opt/fashion/backend (注意:已对齐到当前活跃开发的后端,原 admin/backend 为旧扁平版) +# 后端仓: /opt/fashion/backend (已对齐到当前活跃开发的后端,仅支持 PostgreSQL + pgvector) services: - # ---- 数据库 ---- + # ---- 数据库 (PostgreSQL + pgvector) ---- + # 注意:后端代码仅支持 PostgreSQL + pgvector(gorm pgx 驱动),切勿换回 MySQL。 db: - image: mysql:8.0 + image: ankane/pgvector:latest # 预编译好 pgvector 的 PG 镜像(latest 当前底层为 PG17) restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-root} - MYSQL_DATABASE: ${DB_NAME:-fashionadmin} + POSTGRES_USER: fashion + POSTGRES_PASSWORD: ${DB_PASSWORD:-fashion_dev_2026} # 生产务必换强密码 + POSTGRES_DB: fashion volumes: - - db_data:/var/lib/mysql + - db_data:/var/lib/postgresql/data healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${DB_PASSWORD:-root}"] + test: ["CMD-SHELL", "pg_isready -U fashion -d fashion"] interval: 10s timeout: 5s retries: 10 @@ -29,11 +31,15 @@ services: HASHID_SECRET: ${HASHID_SECRET:-} # 公开 ID 混淆盐值:生产务必注入随机串(改值会使旧链接失效,谨慎变更) GIN_MODE: release JWT_SECRET: ${JWT_SECRET:-change-me-in-prod} + CORS_ALLOW_ORIGINS: ${CORS_ALLOW_ORIGINS:-https://xisoa.com,https://www.xisoa.com} + RATE_LIMIT_ENABLED: ${RATE_LIMIT_ENABLED:-false} + RATE_LIMIT_RPS: ${RATE_LIMIT_RPS:-20} + RATE_LIMIT_BURST: ${RATE_LIMIT_BURST:-40} DB_HOST: db - DB_PORT: 3306 - DB_USER: ${DB_USER:-root} - DB_PASSWORD: ${DB_PASSWORD:-root} - DB_NAME: ${DB_NAME:-fashionadmin} + DB_PORT: 5432 + DB_USER: ${DB_USER:-fashion} + DB_PASSWORD: ${DB_PASSWORD:-fashion_dev_2026} + DB_NAME: ${DB_NAME:-fashion} ports: - "8090:8090" # 对外公开 API(运行期由 nginx 反代);生产可去掉此发布,改由前端 nginx 直接反代 backend:8090 - "127.0.0.1:8091:8091" # ⚠️ SSG 内部端口:只绑宿主回环,外部网络不可达;nginx 也绝不反代它 @@ -46,7 +52,7 @@ services: timeout: 5s retries: 10 - # ---- 前端 (Astro 静态 + nginx) ---- + # ---- 前端 (Astro node server + nginx 反代) ---- frontend: build: context: . diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..4dd1ed2 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# 前端运行期入口:同容器内先后拉起 +# 1) node standalone server(Astro 构建产物,监听 127.0.0.1:3000,承载 SSR 详情页/静态资源) +# 2) nginx(监听 80,反代 / 到 node、/api 到 backend:8090) +set -e + +# node server 后台运行;一旦退出,整个容器随之退出(便于编排器感知故障) +node ./dist/server/entry.mjs & +NODE_PID=$! + +# 捕获 SIGTERM/SIGINT 优雅关闭 node 与 nginx +trap 'kill -TERM $NODE_PID 2>/dev/null; nginx -s stop 2>/dev/null; exit 0' TERM INT + +# nginx 前台运行(容器主进程) +nginx -g 'daemon off;' & +NGINX_PID=$! + +wait -n diff --git a/nginx.conf b/nginx.conf index e751ed6..5b6f4a3 100644 --- a/nginx.conf +++ b/nginx.conf @@ -2,12 +2,13 @@ server { listen 80; server_name _; - root /usr/share/nginx/html; - index index.html; - - # 前端静态资源 / SPA 回退 + # 前端 node server(Astro standalone,同容器内 127.0.0.1:3000):承载 SSR 详情页 / 静态资源。 location / { - try_files $uri $uri/ /index.html; + proxy_pass http://127.0.0.1:3000; + 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; } # 网关隔离:SSG 内部接口只存在于 backend 的 8091 端口(回环绑定,nginx 不反代)。 diff --git a/src/components/CollectionCard.astro b/src/components/CollectionCard.astro index 9243cf0..7bd6a5a 100644 --- a/src/components/CollectionCard.astro +++ b/src/components/CollectionCard.astro @@ -19,7 +19,7 @@