diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a807e63 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,38 @@ +# Reference projects (read-only, not needed in build) +new-api/ +sub2api/ + +# Temporary files +tmp/ + +# Git +.git/ +.gitignore + +# IDE +.idea/ +.vscode/ +.claude/ + +# OS files +.DS_Store +Thumbs.db + +# Frontend node_modules (rebuilt in container) +web/admin/node_modules/ +web/tenant/node_modules/ + +# Frontend dist (rebuilt in container) +web/admin/dist/ +web/tenant/dist/ + +# Local env and config +.env +manifest/config/config.yaml + +# Documentation +docs/ + +# Build artifacts +*.exe +team-api diff --git a/Makefile b/Makefile index b7407d8..2d15076 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ ifneq (,$(wildcard .env)) include .env endif -.PHONY: run build build-web build-all tidy ctrl dao service migrate-up migrate-down migrate-status migrate-reset +.PHONY: run build build-web build-all tidy ctrl dao service migrate-up migrate-down migrate-status migrate-reset docker-build docker-up docker-down docker-logs docker-rebuild # GoFrame hot-reload dev server run: @@ -68,3 +68,23 @@ migrate-status: migrate-reset: goose -dir migrations postgres $(DB_URL) reset + +# Docker commands +docker-build: + cd manifest/docker && docker compose build + +docker-up: + cd manifest/docker && docker compose up -d + +docker-down: + cd manifest/docker && docker compose down + +docker-logs: + cd manifest/docker && docker compose logs -f + +docker-rebuild: docker-down + cd manifest/docker && docker compose build --no-cache && docker compose up -d + +docker-clean: + cd manifest/docker && docker compose down -v + docker volume rm team-api-postgres_data team-api-redis_data team-api-app_logs 2>/dev/null || true diff --git a/README.md b/README.md index 0487282..5da5996 100644 --- a/README.md +++ b/README.md @@ -307,19 +307,68 @@ make migrate-status ## Docker 部署 -### 构建镜像 +项目提供多阶段 Dockerfile(前端构建 → Go 编译 → 精简运行镜像)和完整的 Docker Compose 编排,包含 PostgreSQL、Redis 和应用服务。 + +### 快速启动(推荐) ```bash -gf build -docker build -t team-api:latest -f manifest/docker/Dockerfile . -``` +# 1. 复制示例配置文件并重命名 +cp manifest/docker/config.example.yaml manifest/docker/config.yaml -### 使用 Docker Compose 运行 +# 2. 编辑配置文件(修改数据库密码、Redis 密码、JWT 密钥等) +vim manifest/docker/config.yaml -```bash +# 3. 修改 docker-compose.yaml 中的密码(与 config.yaml 保持一致) +# - POSTGRES_PASSWORD +# - Redis --requirepass + +# 4. 一键启动所有服务 docker compose -f manifest/docker/docker-compose.yaml up -d ``` +服务启动后访问 `http://localhost:18888`,首次部署会进入系统初始化向导(`/api/setup`)。 + +### 仅构建镜像 + +```bash +# 构建镜像(多阶段构建,自动编译前端和后端) +docker build -t team-api:latest -f manifest/docker/Dockerfile . + +# 指定版本号 +docker build -t team-api:v1.0.0 --build-arg VERSION=v1.0.0 -f manifest/docker/Dockerfile . +``` + +### 部署架构 + +``` +docker-compose.yaml +├── postgres (PostgreSQL 17) — 数据持久化到 volume +├── redis (Redis 7) — AOF 持久化到 volume +└── app (team-api) — 挂载 config.yaml,依赖 postgres/redis 健康检查 +``` + +### 配置说明 + +部署配置文件位于 `manifest/docker/`: + +| 文件 | 用途 | +|------|------| +| `config.yaml` | 应用运行时配置(数据库连接、Redis、JWT 等) | +| `Dockerfile` | 多阶段构建(bun 前端 → Go 编译 → Alpine 运行) | +| `docker-compose.yaml` | 服务编排(PostgreSQL + Redis + App) | + +### 健康检查 + +应用内置健康检查端点 `/api/health`,Docker 每 30 秒探测一次,启动宽限期 30 秒。 + +### 数据持久化 + +Docker Compose 使用命名卷持久化数据: + +- `postgres_data` — PostgreSQL 数据目录 +- `redis_data` — Redis AOF 持久化 +- `app_logs` — 应用日志 + ## 开发指南 ### 代码生成 diff --git a/docs/deployment-docker.md b/docs/deployment-docker.md new file mode 100644 index 0000000..1d206f4 --- /dev/null +++ b/docs/deployment-docker.md @@ -0,0 +1,270 @@ +# Team-API Docker 部署指南 + +本文档介绍如何使用 Docker 部署 Team-API。 + +## 快速开始 + +### 一、准备配置文件 + +```bash +# 1. 进入 Docker 配置目录 +cd manifest/docker + +# 2. 复制配置模板 +cp ../../manifest/config/config.example.yaml ./config.yaml + +# 3. 编辑配置文件,修改数据库和 Redis 连接信息 +vim config.yaml +``` + +**注意**:本配置不对外开放 PostgreSQL 和 Redis 端口,仅应用服务(18888 端口)对外暴露。如需外部访问数据库,请自行添加端口映射。 + + +### 二、修改 docker-compose.yaml + +编辑 `docker-compose.yaml` 文件,修改以下配置: + +1. **数据库密码**(第 23 行): + ```yaml + POSTGRES_PASSWORD: team_api_secret # 修改为强密码 + ``` + +2. **Redis 密码**(第 45 行): + ```yaml + command: redis-server --appendonly yes --requirepass redis_secret # 修改为强密码 + ``` + +3. **管理员账号**(第 76-77 行,可选): + ```yaml + INIT_ADMIN_USERNAME: admin # 修改为你的管理员用户名 + INIT_ADMIN_PASSWORD: admin123 # 修改为你的管理员密码 + ``` + +### 三、修改 config.yaml + +编辑 `config.yaml` 文件,确保数据库和 Redis 配置与 docker-compose.yaml 一致: + +```yaml +database: + default: + type: "pgsql" + link: "pgsql:team_api:team_api_secret@tcp(postgres:5432)/team_api?sslmode=disable" + # ^用户名 ^密码 ^主机名:端口 ^数据库名 + +redis: + default: + address: "redis:6379" # 注意:主机名是 "redis" 而不是 localhost + pass: "redis_secret" # 密码需与 docker-compose.yaml 中设置的一致 + db: 0 +``` + +### 四、启动服务 + +```bash +# 构建并启动所有服务 +docker compose up -d + +# 查看日志 +docker compose logs -f app +``` + +### 五、访问应用 + +启动成功后,可以访问: + +- **管理后台**: http://localhost:18888/admin +- **租户控制台**: http://localhost:18888 +- **API 文档**: http://localhost:18888/api/docs + +## 配置说明 + +### docker-compose.yaml 配置项 + +| 配置项 | 说明 | 默认值 | +|--------|------|--------| +| `POSTGRES_PASSWORD` | 数据库密码 | `team_api_secret` | +| `--requirepass` | Redis 密码 | `redis_secret` | +| `INIT_ADMIN_USERNAME` | 管理员用户名 | `admin` | +| `INIT_ADMIN_PASSWORD` | 管理员密码 | `admin123` | + +### config.yaml 配置项 + +#### 数据库配置 + +```yaml +database: + default: + link: "pgsql:用户名:密码@tcp(主机名:端口)/数据库名?sslmode=disable" +``` + +**注意**:Docker 网络中,主机名使用服务名(`postgres`、`redis`),而不是 `localhost`。 + +#### Redis 配置 + +```yaml +redis: + default: + address: "redis:6379" # 主机名使用 "redis" + db: 0 +``` + +#### JWT 密钥(重要) + +```yaml +jwt: + secret: "change-me-to-a-random-secret" # 生产环境必须修改 +``` + +## 服务说明 + +Docker Compose 包含以下服务: + +| 服务 | 容器名 | 端口 | 说明 | +|------|--------|------|------| +| `postgres` | `team-api-postgres` | 5432 | PostgreSQL 数据库 | +| `redis` | `team-api-redis` | 6379 | Redis 缓存 | +| `app` | `team-api-app` | 18888 | Team-API 应用 | + +## 常用命令 + +```bash +# 启动服务 +docker compose up -d + +# 停止服务 +docker compose down + +# 查看日志 +docker compose logs -f + +# 仅查看应用日志 +docker compose logs -f app + +# 重启服务 +docker compose restart app + +# 重新构建镜像 +docker compose build --no-cache +docker compose up -d + +# 查看服务状态 +docker compose ps +``` + +## 数据持久化 + +以下数据通过 Docker 卷持久化: + +| 卷名 | 用途 | +|------|------| +| `postgres_data` | 数据库数据 | +| `redis_data` | Redis 数据 | +| `app_logs` | 应用日志 | + +## 清理数据 + +```bash +# 停止并删除容器、网络 +docker compose down + +# 删除数据卷(注意:会删除所有数据!) +docker volume rm team-api-postgres_data team-api-redis_data team-api-app_logs +``` + +## 生产环境建议 + +### 1. 修改默认密码 + +务必修改以下密码: +- `docker-compose.yaml` 中的 `POSTGRES_PASSWORD` +- `docker-compose.yaml` 中的 `INIT_ADMIN_PASSWORD` +- `config.yaml` 中的 `jwt.secret` + +### 2. 使用外部数据库 + +对于生产环境,建议使用托管数据库服务。修改 `config.yaml` 中的数据库连接信息即可。 + +### 3. 配置 HTTPS + +使用反向代理(如 Nginx)配置 HTTPS: + +```nginx +server { + listen 443 ssl http2; + server_name your-domain.com; + + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + location / { + proxy_pass http://localhost:18888; + 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; + } + + # WebSocket 支持 + location /api/admin/ws { + proxy_pass http://localhost:18888; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} +``` + +## 故障排查 + +### 应用无法启动 + +1. 检查日志:`docker compose logs app` +2. 确认 `config.yaml` 已创建且配置正确 +3. 确认数据库和 Redis 就绪:`docker compose ps` + +### 数据库连接失败 + +1. 确认 `config.yaml` 中的密码与 `docker-compose.yaml` 中的 `POSTGRES_PASSWORD` 一致 +2. 确认主机名使用的是 `postgres` 而非 `localhost` +3. 查看数据库日志:`docker compose logs postgres` + +### 无法访问应用 + +1. 确认端口未被占用:`lsof -i :18888` +2. 检查防火墙设置 +3. 验证容器状态:`docker compose ps` + +## 更新应用 + +```bash +# 拉取最新代码 +cd ../../ +git pull + +# 重新构建并启动 +cd manifest/docker +docker compose build +docker compose up -d +``` + +## 使用 Make 快捷命令 + +```bash +# 构建镜像 +make docker-build + +# 启动服务 +make docker-up + +# 停止服务 +make docker-down + +# 查看日志 +make docker-logs + +# 重新构建 +make docker-rebuild + +# 清理数据 +make docker-clean +``` diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index fa41051..1577161 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -187,8 +187,8 @@ var ( r.Middleware.Next() return } - // Allow setup endpoints - if r.URL.Path == "/setup" || r.URL.Path == "/api/setup/initialize" || r.URL.Path == "/api/setup/status" { + // Allow setup and health endpoints + if r.URL.Path == "/setup" || r.URL.Path == "/api/setup/initialize" || r.URL.Path == "/api/setup/status" || r.URL.Path == "/api/health" { r.Middleware.Next() return } @@ -217,6 +217,16 @@ var ( group.POST("/initialize", setupHandler.HandleSetupInitialize) }) + // Health check endpoint (always accessible, used by Docker/K8s probes) + s.Group("/api", func(group *ghttp.RouterGroup) { + group.GET("/health", func(r *ghttp.Request) { + r.Response.WriteJson(g.Map{ + "status": "ok", + "version": consts.Version, + }) + }) + }) + // WebSocket endpoints — 独立于 MiddlewareHandlerResponse,避免升级后中间件干扰 s.Group("/api/admin", func(g *ghttp.RouterGroup) { g.Middleware(middleware.AdminAuth) diff --git a/manifest/docker/Dockerfile b/manifest/docker/Dockerfile index d3abe8f..d38bc35 100644 --- a/manifest/docker/Dockerfile +++ b/manifest/docker/Dockerfile @@ -1,16 +1,72 @@ -FROM loads/alpine:3.8 +# Multi-stage build for Team-API -############################################################################### -# INSTALLATION -############################################################################### +# Stage 1: Frontend build +FROM oven/bun:1-alpine AS frontend -ENV WORKDIR /app -ADD resource $WORKDIR/ -ADD ./temp/linux_amd64/main $WORKDIR/main -RUN chmod +x $WORKDIR/main +WORKDIR /build/web -############################################################################### -# START -############################################################################### -WORKDIR $WORKDIR -CMD ./main +COPY web/admin/package.json web/admin/bun.lock* ./admin/ +COPY web/tenant/package.json web/tenant/bun.lock* ./tenant/ + +RUN cd admin && bun install --frozen-lockfile && \ + cd ../tenant && bun install --frozen-lockfile + +COPY VERSION /build/VERSION +COPY web/admin ./admin +COPY web/tenant ./tenant + +RUN cd admin && bun run build && \ + cd ../tenant && bun run build + +# Stage 2: Go build +FROM golang:1.25-alpine AS builder + +RUN apk add --no-cache git make gcc musl-dev + +WORKDIR /build + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +# Copy frontend dist from previous stage +COPY --from=frontend /build/web/admin/dist ./web/admin/dist +COPY --from=frontend /build/web/tenant/dist ./web/tenant/dist + +ARG VERSION=dev +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -tags embedweb -ldflags="-s -w -X github.com/qianfree/team-api/internal/consts.Version=${VERSION}" \ + -o /app/team-api main.go + +# Stage 3: Runtime +FROM alpine:3.20 + +RUN apk add --no-cache \ + ca-certificates \ + tzdata \ + curl \ + && rm -rf /var/cache/apk/* + +RUN addgroup -g 1000 team-api && \ + adduser -D -u 1000 -G team-api team-api + +WORKDIR /app + +COPY --from=builder /app/team-api /app/team-api + +# Copy i18n and migrations for runtime use +COPY --from=builder /build/manifest/i18n /app/i18n +COPY --from=builder /build/migrations /app/migrations + +RUN mkdir -p /app/config /app/logs && \ + chown -R team-api:team-api /app + +USER team-api + +EXPOSE 18888 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:18888/api/health || exit 1 + +CMD ["./team-api"] diff --git a/manifest/docker/config.example.yaml b/manifest/docker/config.example.yaml new file mode 100644 index 0000000..6694687 --- /dev/null +++ b/manifest/docker/config.example.yaml @@ -0,0 +1,68 @@ +# ============================================================================= +# Team-API 运行时配置(Docker Compose 部署示例) +# ============================================================================= +# 使用方法: +# cp config.example.yaml config.yaml +# 然后修改下方标注 "请修改" 的配置项 +# +# 注意:数据库和 Redis 的密码需要与 docker-compose.yaml 中的设置保持一致 +# ============================================================================= + +# 服务配置 +server: + address: ":18888" + openapiPath: "/api.json" + swaggerPath: "/swagger" + serverAgent: "team-api" + dumpRouterMap: false + accessLogEnabled: true + errorLogEnabled: true + +# 日志配置 +logger: + level: "all" + stdout: true + ctxKeys: ["RequestId"] + +# 数据库配置(PostgreSQL) +# 连接地址中的 postgres 是 docker-compose 中的服务名 +database: + default: + type: "pgsql" + link: "pgsql:team_api:team_api_secret@tcp(postgres:5432)/team_api?sslmode=disable" + debug: false + dryRun: false + maxIdle: 10 + maxOpen: 100 + maxLifetime: "30s" + +# Redis 配置 +# 连接地址中的 redis 是 docker-compose 中的服务名 +redis: + default: + address: "redis:6379" + pass: "redis_secret" + db: 0 + +# 缓存配置 +cache: + default: + adapter: "redis" + +# JWT 认证配置 +jwt: + secret: "team-api-jwt-secret-key-please-change-me" # 请修改为随机强密钥 + accessTokenExpire: "12h" + refreshTokenExpire: "7d" + issuer: "team-api" + adminMaxSessions: 5 + tenantMaxSessions: 10 + +# 邮件 SMTP 配置(可选,用于邮箱验证码等功能) +email: + smtp: + host: "" + port: 587 + username: "" + password: "" + from: "" diff --git a/manifest/docker/docker-compose.yaml b/manifest/docker/docker-compose.yaml index 7354735..9569886 100644 --- a/manifest/docker/docker-compose.yaml +++ b/manifest/docker/docker-compose.yaml @@ -1,14 +1,27 @@ +# ============================================================================= +# Team-API Docker Compose 配置文件 +# ============================================================================= +# 使用说明: +# 1. 修改下方 "数据库配置" 中的 POSTGRES_PASSWORD +# 2. 修改下方 "Redis 配置" 中的 --requirepass 密码 +# 3. 将 manifest/docker/config.yaml 修改为实际配置 +# (确保数据库密码和 Redis 密码与上方设置一致) +# 4. 运行: docker compose up -d +# ============================================================================= + services: + # ----------------------------------------------------------------------------- + # PostgreSQL 数据库 + # ----------------------------------------------------------------------------- postgres: - image: postgres:15-alpine + image: postgres:17-alpine container_name: team-api-postgres restart: unless-stopped - ports: - - "5432:5432" environment: POSTGRES_USER: team_api - POSTGRES_PASSWORD: team_api_secret + POSTGRES_PASSWORD: team_api_secret # 请修改为强密码 POSTGRES_DB: team_api + PGDATA: /var/lib/postgresql/data/pgdata volumes: - postgres_data:/var/lib/postgresql/data healthcheck: @@ -16,41 +29,74 @@ services: interval: 10s timeout: 5s retries: 5 + networks: + - team-api-network + # ----------------------------------------------------------------------------- + # Redis 缓存 + # ----------------------------------------------------------------------------- redis: image: redis:7-alpine container_name: team-api-redis restart: unless-stopped - ports: - - "6379:6379" + command: redis-server --appendonly yes --requirepass redis_secret # 请修改为强密码 volumes: - redis_data:/data healthcheck: - test: ["CMD", "redis-cli", "ping"] + test: ["CMD", "redis-cli", "-a", "redis_secret", "ping"] interval: 10s timeout: 5s retries: 5 + networks: + - team-api-network - minio: - image: minio/minio:latest - container_name: team-api-minio + # ----------------------------------------------------------------------------- + # Team-API 应用 + # ----------------------------------------------------------------------------- + app: + image: team-api:${VERSION:-v0.1.0} + build: + context: ../.. + dockerfile: manifest/docker/Dockerfile + args: + VERSION: latest + container_name: team-api-app restart: unless-stopped ports: - - "9000:9000" - - "9001:9001" + - "18888:18888" environment: - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin_secret + TZ: Asia/Shanghai volumes: - - minio_data:/data - command: server /data --console-address ":9001" + - ./config.yaml:/app/config/config.yaml:ro + - app_logs:/app/logs healthcheck: - test: ["CMD", "mc", "ready", "local"] - interval: 10s - timeout: 5s - retries: 5 + test: ["CMD", "curl", "-f", "http://localhost:18888/api/health"] + interval: 30s + timeout: 10s + start_period: 30s + retries: 3 + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - team-api-network +# ----------------------------------------------------------------------------- +# 数据卷定义 +# ----------------------------------------------------------------------------- volumes: postgres_data: + driver: local redis_data: - minio_data: + driver: local + app_logs: + driver: local + +# ----------------------------------------------------------------------------- +# 网络定义 +# ----------------------------------------------------------------------------- +networks: + team-api-network: + driver: bridge diff --git a/manifest/docker/docker.sh b/manifest/docker/docker.sh deleted file mode 100644 index ff393f9..0000000 --- a/manifest/docker/docker.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# This shell is executed before docker build. - - - - -