diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8bf8e14..66124d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,12 +6,13 @@ stages: variables: NODE_ENV: production -# 仅 prod 分支触发 +# prod 和 test 分支触发 workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" when: never - if: $CI_COMMIT_BRANCH == "prod" + - if: $CI_COMMIT_BRANCH == "test" # pnpm store 缓存 cache: @@ -86,6 +87,20 @@ build_platform: - apps/platform-admin/dist/ expire_in: 1 hour +build_website: + stage: build + tags: + - rent-deploy + needs: [install_dependencies] + script: + - npm install -g pnpm + - pnpm config set store-dir .pnpm-store + - pnpm --filter @rent/official-website build + artifacts: + paths: + - apps/official-website/out/ + expire_in: 1 hour + # ==================== DEPLOY ==================== deploy_production: stage: deploy @@ -95,17 +110,42 @@ deploy_production: - build_server - build_merchant - build_platform + - build_website script: - echo "部署到生产环境..." - cd deploy/docker - - docker-compose down --remove-orphans - - docker-compose build --parallel - - docker-compose up -d + - docker-compose -f docker-compose.prod.yml down --remove-orphans + - docker-compose -f docker-compose.prod.yml build --parallel + - docker-compose -f docker-compose.prod.yml up -d - docker image prune -f - echo "等待服务启动..." - sleep 10 - - docker-compose ps + - docker-compose -f docker-compose.prod.yml ps environment: name: production - only: - - prod + rules: + - if: $CI_COMMIT_BRANCH == "prod" + +deploy_test: + stage: deploy + tags: + - rent-deploy + needs: + - build_server + - build_merchant + - build_platform + - build_website + script: + - echo "部署到测试环境..." + - cd deploy/docker + - docker-compose -f docker-compose.test.yml down --remove-orphans + - docker-compose -f docker-compose.test.yml build --parallel + - docker-compose -f docker-compose.test.yml up -d + - docker image prune -f + - echo "等待服务启动..." + - sleep 10 + - docker-compose -f docker-compose.test.yml ps + environment: + name: test + rules: + - if: $CI_COMMIT_BRANCH == "test" diff --git a/Makefile b/Makefile index 38a708f..fc39114 100644 --- a/Makefile +++ b/Makefile @@ -1,43 +1,100 @@ -.PHONY: help install build up down restart logs ps deploy clean +.PHONY: help install \ + prod-build prod-up prod-down prod-restart prod-logs prod-logs-server prod-ps prod-deploy \ + test-build test-up test-down test-restart test-logs test-logs-server test-ps test-deploy \ + gateway-up gateway-down gateway-reload \ + init deploy clean -DOCKER_COMPOSE = docker-compose -f deploy/docker/docker-compose.yml +DOCKER_PROD = docker-compose -f deploy/docker/docker-compose.prod.yml +DOCKER_TEST = docker-compose -f deploy/docker/docker-compose.test.yml +DOCKER_GATEWAY = docker-compose -f deploy/docker/docker-compose.gateway.yml help: ## 显示帮助信息 @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \ - awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' install: ## 安装依赖 npm install -g pnpm pnpm install -build: ## 构建所有 Docker 镜像 - $(DOCKER_COMPOSE) build --parallel +# ===== 生产环境 ===== +prod-build: ## 构建生产环境镜像 + $(DOCKER_PROD) build --parallel -up: ## 启动所有服务 - $(DOCKER_COMPOSE) up -d +prod-up: ## 启动生产环境 + $(DOCKER_PROD) up -d -down: ## 停止所有服务 - $(DOCKER_COMPOSE) down +prod-down: ## 停止生产环境 + $(DOCKER_PROD) down -restart: ## 重启所有服务 - $(DOCKER_COMPOSE) restart +prod-restart: ## 重启生产环境 + $(DOCKER_PROD) restart -logs: ## 查看所有服务日志 - $(DOCKER_COMPOSE) logs -f --tail=100 +prod-logs: ## 查看生产环境日志 + $(DOCKER_PROD) logs -f --tail=100 -logs-server: ## 查看后端服务日志 - $(DOCKER_COMPOSE) logs -f --tail=100 server +prod-logs-server: ## 查看生产环境后端日志 + $(DOCKER_PROD) logs -f --tail=100 server -ps: ## 查看运行中的容器 - $(DOCKER_COMPOSE) ps +prod-ps: ## 查看生产环境容器 + $(DOCKER_PROD) ps -deploy: down build up ## 完整部署(停止 → 构建 → 启动) +prod-deploy: prod-down prod-build prod-up ## 完整部署生产环境 @echo "" - @echo "部署完成!" - @echo " 商家后台: http://localhost:8081" - @echo " 平台后台: http://localhost:8082" - @echo " API 服务: http://localhost:3000" + @echo "生产环境部署完成!" + @echo " API: http://api.pinzhuhui.com" + @echo " 官网: http://www.pinzhuhui.com" + @echo " 商家后台: http://merchant.pinzhuhui.com" + @echo " 平台后台: http://platform-admin.pinzhuhui.com" + +# ===== 测试环境 ===== +test-build: ## 构建测试环境镜像 + $(DOCKER_TEST) build --parallel + +test-up: ## 启动测试环境 + $(DOCKER_TEST) up -d + +test-down: ## 停止测试环境 + $(DOCKER_TEST) down + +test-restart: ## 重启测试环境 + $(DOCKER_TEST) restart + +test-logs: ## 查看测试环境日志 + $(DOCKER_TEST) logs -f --tail=100 + +test-logs-server: ## 查看测试环境后端日志 + $(DOCKER_TEST) logs -f --tail=100 server + +test-ps: ## 查看测试环境容器 + $(DOCKER_TEST) ps + +test-deploy: test-down test-build test-up ## 完整部署测试环境 + @echo "" + @echo "测试环境部署完成!" + @echo " API: http://api-test.pinzhuhui.com" + @echo " 官网: http://test.pinzhuhui.com" + @echo " 商家后台: http://merchant-test.pinzhuhui.com" + @echo " 平台后台: http://platform-admin-test.pinzhuhui.com" + +# ===== 网关 ===== +gateway-up: ## 启动网关 + $(DOCKER_GATEWAY) up -d + +gateway-down: ## 停止网关 + $(DOCKER_GATEWAY) down + +gateway-reload: ## 重载网关配置 + docker exec rent-gateway nginx -s reload + +# ===== 快捷命令 ===== +init: gateway-up ## 首次初始化(启动网关 + 创建网络) + @echo "网络已创建,网关已启动。" + @echo "接下来运行 make prod-deploy 或 make test-deploy" + +deploy: prod-deploy ## 别名:部署生产环境 clean: ## 清理所有容器、数据卷和悬空镜像 - $(DOCKER_COMPOSE) down -v + $(DOCKER_PROD) down -v + $(DOCKER_TEST) down -v + $(DOCKER_GATEWAY) down docker image prune -f diff --git a/deploy/README.md b/deploy/README.md index e22cd35..1a032af 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -1,102 +1,139 @@ # 部署指南 +## 架构概览 + +``` +Internet → Gateway Nginx (80/443) + ├─ rent-prod 网络 + │ ├─ rent-prod-mysql (3306) + │ ├─ rent-prod-redis (6379) + │ ├─ rent-prod-server (3000) + │ ├─ rent-prod-merchant (8081→80) + │ ├─ rent-prod-platform (8082→80) + │ └─ rent-prod-website (8083→80) + └─ rent-test 网络 + ├─ rent-test-mysql (3307→3306) + ├─ rent-test-redis (6380→6379) + ├─ rent-test-server (3001→3000) + ├─ rent-test-merchant (9081→80) + ├─ rent-test-platform (9082→80) + └─ rent-test-website (9083→80) +``` + +## 域名规划 + +| 环境 | 用途 | 域名 | +|------|------|------| +| 生产 | API | api.pinzhuhui.com | +| 生产 | 官网 | www.pinzhuhui.com | +| 生产 | 商家后台 | merchant.pinzhuhui.com | +| 生产 | 平台后台 | platform-admin.pinzhuhui.com | +| 测试 | API | api-test.pinzhuhui.com | +| 测试 | 官网 | test.pinzhuhui.com | +| 测试 | 商家后台 | merchant-test.pinzhuhui.com | +| 测试 | 平台后台 | platform-admin-test.pinzhuhui.com | + ## 目录结构 -``` +```text deploy/ ├── docker/ -│ ├── docker-compose.yml # Docker Compose 编排文件 -│ ├── Dockerfile.server # 后端服务镜像 -│ ├── Dockerfile.merchant # 商家后台镜像 -│ ├── Dockerfile.platform # 平台后台镜像 -│ └── .env.example # 环境变量模板 -└── nginx/ - ├── api.conf # API 反向代理配置 - ├── merchant.conf # 商家后台 Nginx 配置 - └── platform.conf # 平台后台 Nginx 配置 -``` - -## 服务端口 - -| 服务 | 端口 | 说明 | -|------|------|------| -| MySQL | 3306 | 数据库 | -| Redis | 6379 | 缓存 | -| Server | 3000 | 后端 API | -| Merchant Admin | 8081 | 商家管理后台 | -| Platform Admin | 8082 | 平台管理后台 | - ---- - -## 一、快速部署(手动) - -### 1. 配置环境变量 - -```bash -cd deploy/docker -cp .env.example .env -# 编辑 .env,填入真实的密码和密钥 -vi .env -``` - -### 2. 构建并启动 - -```bash -# 方式一:使用 Makefile(推荐) -make deploy - -# 方式二:直接使用 docker-compose -docker-compose -f deploy/docker/docker-compose.yml up -d --build -``` - -### 3. 常用命令 - -```bash -make help # 查看所有命令 -make logs # 查看日志 -make logs-server # 查看后端日志 -make ps # 查看容器状态 -make restart # 重启服务 -make down # 停止服务 -make clean # 清理所有数据(包括数据库) +│ ├── docker-compose.prod.yml # 生产环境 +│ ├── docker-compose.test.yml # 测试环境 +│ ├── docker-compose.gateway.yml # Gateway 网关 +│ ├── Dockerfile.server # 后端镜像 +│ ├── Dockerfile.merchant # 商家后台镜像 +│ ├── Dockerfile.platform # 平台后台镜像 +│ ├── Dockerfile.website # 官网镜像 +│ ├── .env.example # 环境变量模板 +│ └── .env # 实际配置(不入仓库) +├── nginx/ +│ ├── gateway/gateway.conf # Gateway 域名路由 +│ ├── api.conf # 后端 Nginx 配置 +│ ├── merchant.conf # 商家后台 Nginx 配置 +│ └── platform.conf # 平台后台 Nginx 配置 +scripts/ +│ └── setup-server.sh # 首次初始化脚本 ``` --- -## 二、GitLab CI/CD 自动化部署 +## 一、首次部署(服务器初始化) + +### 第 1 步:运行初始化脚本 + +```bash +# 将代码克隆到服务器 +git clone <你的GitLab仓库地址> ~/rent-platform +cd ~/rent-platform + +# 运行初始化(安装 Docker、创建网络、启动网关) +bash scripts/setup-server.sh +``` + +脚本会自动完成: +- 安装 Docker、Docker Compose、Git、pnpm +- 创建 `.env` 文件并提示你填写密码 +- 创建 Docker 网络(rent-prod、rent-test) +- 启动 Gateway Nginx + +### 第 2 步:配置 DNS + +在你的域名管理面板添加 A 记录,将以下域名指向服务器 IP: + +```text +# 生产环境 +api.pinzhuhui.com → 服务器IP +www.pinzhuhui.com → 服务器IP +merchant.pinzhuhui.com → 服务器IP +platform-admin.pinzhuhui.com → 服务器IP + +# 测试环境 +api-test.pinzhuhui.com → 服务器IP +test.pinzhuhui.com → 服务器IP +merchant-test.pinzhuhui.com → 服务器IP +platform-admin-test.pinzhuhui.com → 服务器IP +``` + +### 第 3 步:部署服务 + +```bash +# 部署生产环境 +make prod-deploy + +# 部署测试环境 +make test-deploy +``` + +--- + +## 二、GitLab CI/CD 自动化 ### 前提条件 -- 服务器已安装 Docker 和 Docker Compose -- 服务器已部署 GitLab 实例 -- 项目代码已推送到 GitLab 仓库 +- 服务器已运行初始化脚本 +- 代码已推送到 GitLab 仓库 ### 第 1 步:安装 GitLab Runner ```bash -# 添加 GitLab Runner 仓库 curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash - -# 安装 sudo apt install gitlab-runner ``` ### 第 2 步:注册 Runner -1. 在 GitLab 中打开项目 → **Settings → CI/CD → Runners** -2. 复制 registration token -3. 在服务器上执行注册: +1. GitLab → **Settings → CI/CD → Runners** → 复制 token +2. 在服务器执行: ```bash sudo gitlab-runner register ``` -按提示填写: - | 项目 | 值 | |------|------| -| GitLab URL | `http://你的GitLab域名/` | -| Token | 从 Settings 页面复制的 token | +| GitLab URL | `http://你的GitLab地址/` | +| Token | 从页面复制 | | Description | `rent-deploy-runner` | | Tags | `rent-deploy` | | Executor | `shell` | @@ -104,91 +141,110 @@ sudo gitlab-runner register ### 第 3 步:配置 Runner 权限 ```bash -# 让 gitlab-runner 用户可以执行 docker 命令 sudo usermod -aG docker gitlab-runner - -# 全局安装 pnpm sudo -u gitlab-runner npm install -g pnpm - -# 验证 -sudo -u gitlab-runner docker ps -sudo -u gitlab-runner pnpm --version ``` ### 第 4 步:配置 CI/CD 变量 -在 GitLab 中打开 **Settings → CI/CD → Variables**,添加: +GitLab → **Settings → CI/CD → Variables**: -| 变量名 | 说明 | 勾选 Masked | -|--------|------|-------------| -| `PROD_DB_PASSWORD` | MySQL root 密码 | ✅ | -| `PROD_JWT_SECRET` | JWT 签名密钥 | ✅ | +| 变量名 | 说明 | Masked | +|--------|------|--------| +| `PROD_DB_PASSWORD` | 生产数据库密码 | ✅ | +| `PROD_JWT_SECRET` | 生产 JWT 密钥 | ✅ | +| `TEST_DB_PASSWORD` | 测试数据库密码 | ✅ | +| `TEST_JWT_SECRET` | 测试 JWT 密钥 | ✅ | -### 第 5 步:创建环境变量文件 +### 第 5 步:推送代码触发部署 ```bash -# 在服务器项目目录下创建 .env(只需一次) -cd /path/to/repo/deploy/docker -cp .env.example .env -vi .env -# 填入 PROD_DB_PASSWORD 和 PROD_JWT_SECRET 的真实值 -``` +# 推送 test 分支 → 自动部署测试环境 +git push origin test -### 第 6 步:推送代码触发部署 - -```bash +# 推送 prod 分支 → 自动部署生产环境 git push origin prod ``` -推送后 GitLab 会自动运行流水线: - -1. **install** 阶段 — 安装依赖 -2. **build** 阶段 — 并行构建 server / merchant / platform -3. **deploy** 阶段 — 停止旧服务 → 构建镜像 → 启动新服务 - -可以在 GitLab → **CI/CD → Pipelines** 页面查看实时日志。 +流水线:install → build(并行构建 4 个应用)→ deploy --- -## 三、故障排查 +## 三、常用命令 + +```bash +make help # 查看所有命令 + +# 生产环境 +make prod-deploy # 完整部署(停止→构建→启动) +make prod-logs # 查看日志 +make prod-logs-server # 查看后端日志 +make prod-ps # 查看容器状态 +make prod-restart # 重启 + +# 测试环境 +make test-deploy # 完整部署 +make test-logs # 查看日志 +make test-ps # 查看容器状态 +make test-restart # 重启 + +# 网关 +make gateway-up # 启动网关 +make gateway-reload # 重载网关配置 + +# 其他 +make clean # 清理所有环境(包括数据) +``` + +--- + +## 四、故障排查 ### 查看服务状态 ```bash -make ps +make prod-ps # 生产环境 +make test-ps # 测试环境 ``` ### 查看日志 ```bash -# 所有服务 -make logs +make prod-logs # 生产环境所有日志 +make prod-logs-server # 生产环境后端日志 +make test-logs # 测试环境所有日志 -# 仅后端 -make logs-server - -# 特定容器 -docker logs rent-mysql -docker logs rent-redis -docker logs rent-server +# 直接查看某个容器 +docker logs rent-prod-server +docker logs rent-test-mysql ``` -### 数据库连接失败 +### 数据库连接 ```bash -# 检查 MySQL 是否就绪 -docker exec rent-mysql mysqladmin ping -h localhost -u root -p +# 生产环境 +docker exec -it rent-prod-mysql mysql -u root -p rent_platform -# 进入 MySQL 手动检查 -docker exec -it rent-mysql mysql -u root -p rent_platform +# 测试环境 +docker exec -it rent-test-mysql mysql -u root -p rent_platform ``` -### 重建某个服务 +### 重建单个服务 ```bash -# 只重建后端 -docker-compose -f deploy/docker/docker-compose.yml up -d --build server +# 只重建生产环境后端 +docker-compose -f deploy/docker/docker-compose.prod.yml up -d --build server -# 只重建商家后台 -docker-compose -f deploy/docker/docker-compose.yml up -d --build merchant-admin +# 只重建测试环境商家后台 +docker-compose -f deploy/docker/docker-compose.test.yml up -d --build merchant-admin +``` + +### 网关问题 + +```bash +# 检查网关状态 +docker logs rent-gateway + +# 重载网关配置(修改了 gateway.conf 后) +make gateway-reload ``` diff --git a/deploy/docker/.env.example b/deploy/docker/.env.example index 93205d4..57abd4f 100644 --- a/deploy/docker/.env.example +++ b/deploy/docker/.env.example @@ -1,9 +1,7 @@ -# ===== 数据库 ===== -DB_PASSWORD=change_me_in_production +# ===== 生产环境 ===== +PROD_DB_PASSWORD=change_me_in_production +PROD_JWT_SECRET=change_me_in_production -# ===== JWT ===== -JWT_SECRET=change_me_in_production - -# ===== 服务配置 ===== -NODE_ENV=production -PORT=3000 +# ===== 测试环境 ===== +TEST_DB_PASSWORD=change_me_in_test +TEST_JWT_SECRET=change_me_in_test diff --git a/deploy/docker/Dockerfile.website b/deploy/docker/Dockerfile.website new file mode 100644 index 0000000..2dda7b4 --- /dev/null +++ b/deploy/docker/Dockerfile.website @@ -0,0 +1,29 @@ +# 官网 - Next.js 静态导出 +FROM node:18-alpine AS website-builder +WORKDIR /app +COPY pnpm-workspace.yaml pnpm-lock.yaml package.json ./ +COPY packages/ ./packages/ +COPY apps/official-website/ ./apps/official-website/ +RUN npm install -g pnpm && pnpm install --frozen-lockfile +RUN cd apps/official-website && pnpm run build + +FROM nginx:alpine AS website +COPY --from=website-builder /app/apps/official-website/out /usr/share/nginx/html +RUN echo 'server { \ + listen 80; \ + server_name _; \ + root /usr/share/nginx/html; \ + index index.html; \ + location / { \ + try_files $uri $uri.html $uri/ =404; \ + } \ + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { \ + expires 30d; \ + add_header Cache-Control "public, immutable"; \ + } \ + gzip on; \ + gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript; \ + gzip_min_length 1024; \ +}' > /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/deploy/docker/docker-compose.gateway.yml b/deploy/docker/docker-compose.gateway.yml new file mode 100644 index 0000000..c078d6c --- /dev/null +++ b/deploy/docker/docker-compose.gateway.yml @@ -0,0 +1,21 @@ +version: '3.8' + +services: + gateway: + image: nginx:alpine + container_name: rent-gateway + restart: always + ports: + - "80:80" + - "443:443" + volumes: + - ../nginx/gateway/gateway.conf:/etc/nginx/nginx.conf:ro + networks: + - rent-prod + - rent-test + +networks: + rent-prod: + external: true + rent-test: + external: true diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.prod.yml similarity index 64% rename from deploy/docker/docker-compose.yml rename to deploy/docker/docker-compose.prod.yml index 0ed8287..8ff04d5 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.prod.yml @@ -3,17 +3,17 @@ version: '3.8' services: mysql: image: mysql:8.0 - container_name: rent-mysql + container_name: rent-prod-mysql restart: always environment: - MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-rent123456} + MYSQL_ROOT_PASSWORD: ${PROD_DB_PASSWORD:-rent123456} MYSQL_DATABASE: rent_platform MYSQL_CHARSET: utf8mb4 MYSQL_COLLATION: utf8mb4_unicode_ci ports: - "3306:3306" volumes: - - mysql_data:/var/lib/mysql + - mysql_prod_data:/var/lib/mysql - ../../database/migrations:/docker-entrypoint-initdb.d command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci healthcheck: @@ -21,27 +21,31 @@ services: interval: 10s timeout: 5s retries: 5 + networks: + - rent-prod redis: image: redis:7-alpine - container_name: rent-redis + container_name: rent-prod-redis restart: always ports: - "6379:6379" volumes: - - redis_data:/data + - redis_prod_data:/data command: redis-server --appendonly yes healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 + networks: + - rent-prod server: build: context: ../.. dockerfile: deploy/docker/Dockerfile.server - container_name: rent-server + container_name: rent-prod-server restart: always ports: - "3000:3000" @@ -51,39 +55,60 @@ services: DB_HOST: mysql DB_PORT: 3306 DB_USERNAME: root - DB_PASSWORD: ${DB_PASSWORD:-rent123456} + DB_PASSWORD: ${PROD_DB_PASSWORD:-rent123456} DB_DATABASE: rent_platform REDIS_HOST: redis REDIS_PORT: 6379 - JWT_SECRET: ${JWT_SECRET:-change_this_in_production} + JWT_SECRET: ${PROD_JWT_SECRET:-change_this_in_production} depends_on: mysql: condition: service_healthy redis: condition: service_healthy + networks: + - rent-prod merchant-admin: build: context: ../.. dockerfile: deploy/docker/Dockerfile.merchant - container_name: rent-merchant-admin + container_name: rent-prod-merchant restart: always ports: - "8081:80" depends_on: - server + networks: + - rent-prod platform-admin: build: context: ../.. dockerfile: deploy/docker/Dockerfile.platform - container_name: rent-platform-admin + container_name: rent-prod-platform restart: always ports: - "8082:80" depends_on: - server + networks: + - rent-prod + + website: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile.website + container_name: rent-prod-website + restart: always + ports: + - "8083:80" + networks: + - rent-prod volumes: - mysql_data: - redis_data: + mysql_prod_data: + redis_prod_data: + +networks: + rent-prod: + name: rent-prod diff --git a/deploy/docker/docker-compose.test.yml b/deploy/docker/docker-compose.test.yml new file mode 100644 index 0000000..70e50dc --- /dev/null +++ b/deploy/docker/docker-compose.test.yml @@ -0,0 +1,114 @@ +version: '3.8' + +services: + mysql: + image: mysql:8.0 + container_name: rent-test-mysql + restart: always + environment: + MYSQL_ROOT_PASSWORD: ${TEST_DB_PASSWORD:-rent123456} + MYSQL_DATABASE: rent_platform + MYSQL_CHARSET: utf8mb4 + MYSQL_COLLATION: utf8mb4_unicode_ci + ports: + - "3307:3306" + volumes: + - mysql_test_data:/var/lib/mysql + - ../../database/migrations:/docker-entrypoint-initdb.d + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - rent-test + + redis: + image: redis:7-alpine + container_name: rent-test-redis + restart: always + ports: + - "6380:6379" + volumes: + - redis_test_data:/data + command: redis-server --appendonly yes + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - rent-test + + server: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile.server + container_name: rent-test-server + restart: always + ports: + - "3001:3000" + environment: + NODE_ENV: test + PORT: 3000 + DB_HOST: mysql + DB_PORT: 3306 + DB_USERNAME: root + DB_PASSWORD: ${TEST_DB_PASSWORD:-rent123456} + DB_DATABASE: rent_platform + REDIS_HOST: redis + REDIS_PORT: 6379 + JWT_SECRET: ${TEST_JWT_SECRET:-change_this_in_test} + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + networks: + - rent-test + + merchant-admin: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile.merchant + container_name: rent-test-merchant + restart: always + ports: + - "9081:80" + depends_on: + - server + networks: + - rent-test + + platform-admin: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile.platform + container_name: rent-test-platform + restart: always + ports: + - "9082:80" + depends_on: + - server + networks: + - rent-test + + website: + build: + context: ../.. + dockerfile: deploy/docker/Dockerfile.website + container_name: rent-test-website + restart: always + ports: + - "9083:80" + networks: + - rent-test + +volumes: + mysql_test_data: + redis_test_data: + +networks: + rent-test: + name: rent-test diff --git a/deploy/nginx/gateway/gateway.conf b/deploy/nginx/gateway/gateway.conf new file mode 100644 index 0000000..0f49a28 --- /dev/null +++ b/deploy/nginx/gateway/gateway.conf @@ -0,0 +1,170 @@ +worker_processes auto; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + sendfile on; + keepalive_timeout 65; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + # Gzip + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript; + gzip_min_length 1024; + + # 速率限制 + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/s; + + # ===== 生产环境 Upstream ===== + upstream prod_api { + server rent-prod-server:3000; + } + upstream prod_merchant { + server rent-prod-merchant:80; + } + upstream prod_platform { + server rent-prod-platform:80; + } + upstream prod_website { + server rent-prod-website:80; + } + + # ===== 测试环境 Upstream ===== + upstream test_api { + server rent-test-server:3000; + } + upstream test_merchant { + server rent-test-merchant:80; + } + upstream test_platform { + server rent-test-platform:80; + } + upstream test_website { + server rent-test-website:80; + } + + # ===== 生产环境 ===== + + server { + listen 80; + server_name api.pinzhuhui.com; + + location / { + limit_req zone=api_limit burst=50 nodelay; + proxy_pass http://prod_api; + 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; + client_max_body_size 50m; + } + } + + server { + listen 80; + server_name www.pinzhuhui.com; + + location / { + proxy_pass http://prod_website; + 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; + } + } + + server { + listen 80; + server_name merchant.pinzhuhui.com; + + location / { + proxy_pass http://prod_merchant; + 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; + } + } + + server { + listen 80; + server_name platform-admin.pinzhuhui.com; + + location / { + proxy_pass http://prod_platform; + 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; + } + } + + # ===== 测试环境 ===== + + server { + listen 80; + server_name api-test.pinzhuhui.com; + + location / { + limit_req zone=api_limit burst=50 nodelay; + proxy_pass http://test_api; + 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; + client_max_body_size 50m; + } + } + + server { + listen 80; + server_name test.pinzhuhui.com; + + location / { + proxy_pass http://test_website; + 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; + } + } + + server { + listen 80; + server_name merchant-test.pinzhuhui.com; + + location / { + proxy_pass http://test_merchant; + 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; + } + } + + server { + listen 80; + server_name platform-admin-test.pinzhuhui.com; + + location / { + proxy_pass http://test_platform; + 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; + } + } + + # 兜底:未知域名返回 444 + server { + listen 80 default_server; + return 444; + } +} diff --git a/scripts/setup-server.sh b/scripts/setup-server.sh new file mode 100644 index 0000000..878ab7d --- /dev/null +++ b/scripts/setup-server.sh @@ -0,0 +1,107 @@ +#!/bin/bash +set -e + +echo "============================================" +echo " 品居会 - 服务器首次初始化" +echo "============================================" +echo "" + +# ===== 1. 系统更新 ===== +echo ">>> 更新系统包..." +sudo apt update && sudo apt upgrade -y + +# ===== 2. 安装 Docker ===== +if ! command -v docker &> /dev/null; then + echo ">>> 安装 Docker..." + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker $USER + echo "Docker 已安装。可能需要重新登录以生效。" +else + echo ">>> Docker 已安装,跳过。" +fi + +# ===== 3. 验证 Docker Compose ===== +if ! docker compose version &> /dev/null; then + echo ">>> 安装 Docker Compose 插件..." + sudo apt install -y docker-compose-plugin +else + echo ">>> Docker Compose 已安装,跳过。" +fi + +# ===== 4. 安装 Git ===== +if ! command -v git &> /dev/null; then + echo ">>> 安装 Git..." + sudo apt install -y git +else + echo ">>> Git 已安装,跳过。" +fi + +# ===== 5. 安装 pnpm ===== +if ! command -v pnpm &> /dev/null; then + echo ">>> 安装 pnpm..." + npm install -g pnpm 2>/dev/null || { echo "需要先安装 Node.js"; exit 1; } +else + echo ">>> pnpm 已安装,跳过。" +fi + +# ===== 6. 配置环境变量 ===== +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +ENV_FILE="$PROJECT_DIR/deploy/docker/.env" + +if [ ! -f "$ENV_FILE" ]; then + echo ">>> 创建 .env 文件..." + cp "$PROJECT_DIR/deploy/docker/.env.example" "$ENV_FILE" + echo "" + echo " *** 重要 ***" + echo " 请编辑 $ENV_FILE 填入真实密码和密钥。" + echo " 必填变量:" + echo " - PROD_DB_PASSWORD 生产环境数据库密码" + echo " - PROD_JWT_SECRET 生产环境 JWT 密钥" + echo " - TEST_DB_PASSWORD 测试环境数据库密码" + echo " - TEST_JWT_SECRET 测试环境 JWT 密钥" + echo "" + read -p " 按 Enter 打开编辑器,或 Ctrl+C 退出手动编辑..." + ${EDITOR:-vi} "$ENV_FILE" +else + echo ">>> .env 文件已存在,跳过。" +fi + +# ===== 7. 创建 Docker 网络 ===== +echo ">>> 创建 Docker 网络..." +docker network create rent-prod 2>/dev/null || echo " rent-prod 网络已存在。" +docker network create rent-test 2>/dev/null || echo " rent-test 网络已存在。" + +# ===== 8. 启动网关 ===== +echo ">>> 启动 Gateway Nginx..." +cd "$PROJECT_DIR" +docker compose -f deploy/docker/docker-compose.gateway.yml up -d + +echo "" +echo "============================================" +echo " 初始化完成!" +echo "============================================" +echo "" +echo "后续步骤:" +echo " 1. 确认 .env 中的密码已正确填写" +echo " 2. 部署生产环境:make prod-deploy" +echo " 3. 部署测试环境:make test-deploy" +echo "" +echo " 配置以下 DNS 记录指向本服务器 IP:" +echo "" +echo " 生产环境:" +echo " api.pinzhuhui.com" +echo " www.pinzhuhui.com" +echo " merchant.pinzhuhui.com" +echo " platform-admin.pinzhuhui.com" +echo "" +echo " 测试环境:" +echo " api-test.pinzhuhui.com" +echo " test.pinzhuhui.com" +echo " merchant-test.pinzhuhui.com" +echo " platform-admin-test.pinzhuhui.com" +echo "" +echo " 4. (可选)配置 SSL 证书:" +echo " sudo apt install certbot" +echo " sudo certbot certonly --standalone -d api.pinzhuhui.com -d www.pinzhuhui.com ..." +echo ""