From 8f0d01962705246aec758d0089ddb712a4ecf83c Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Tue, 31 Mar 2026 22:29:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84docker=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.dockerignore | 7 +++ backend/.env.production | 28 +++++++++ backend/Dockerfile | 25 ++++++++ backend/schema.sql | 136 ++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 78 +++++++++++++++++++++++ docker-deploy.md | 69 ++++++++++++++++++++ frontend/.dockerignore | 6 ++ frontend/Dockerfile | 29 +++++++++ frontend/nginx.conf | 32 ++++++++++ 9 files changed, 410 insertions(+) create mode 100644 backend/.dockerignore create mode 100644 backend/.env.production create mode 100644 backend/Dockerfile create mode 100644 backend/schema.sql create mode 100644 docker-compose.yml create mode 100644 docker-deploy.md create mode 100644 frontend/.dockerignore create mode 100644 frontend/Dockerfile create mode 100644 frontend/nginx.conf diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..f6d7058 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,7 @@ +__pycache__ +*.pyc +.env +.git +*.md +venv +.venv diff --git a/backend/.env.production b/backend/.env.production new file mode 100644 index 0000000..b288f0d --- /dev/null +++ b/backend/.env.production @@ -0,0 +1,28 @@ +# ACG Blog 生产环境配置 +# 复制此文件为 .env 并修改对应值 + +# 应用配置 +APP_NAME=ACG Blog +APP_VERSION=1.0.0 +DEBUG=false + +# 数据库配置 +DB_HOST=postgres +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=postgres +DB_NAME=acg_blog + +# Redis 配置 +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_DB=0 + +# JWT 配置 (请修改为随机字符串) +SECRET_KEY=your-super-secret-key-change-this-in-production +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=30 +REFRESH_TOKEN_EXPIRE_DAYS=7 + +# CORS 配置 (前端域名) +BACKEND_CORS_ORIGINS=http://localhost,https://your-domain.com diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..77a0fbc --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,25 @@ +# FastAPI Backend +FROM python:3.11-slim + +WORKDIR /app + +# 安装系统依赖 +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +# 复制依赖文件 +COPY requirements.txt . + +# 安装 Python 依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制应用代码 +COPY . . + +# 暴露端口 +EXPOSE 8000 + +# 启动命令 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/schema.sql b/backend/schema.sql new file mode 100644 index 0000000..8092bf8 --- /dev/null +++ b/backend/schema.sql @@ -0,0 +1,136 @@ +-- ACG Blog 数据库建表脚本 +-- PostgreSQL +-- 运行前请先创建数据库: CREATE DATABASE acg_blog; + +-- 启用 UUID 扩展 +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- ==================== 用户表 ==================== +CREATE TABLE IF NOT EXISTS "users" ( + "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + "username" VARCHAR(50) UNIQUE NOT NULL, + "email" VARCHAR(255) UNIQUE NOT NULL, + "password_hash" VARCHAR(255) NOT NULL, + "avatar" VARCHAR(500), + "bio" TEXT, + "is_active" BOOLEAN DEFAULT TRUE, + "is_superuser" BOOLEAN DEFAULT FALSE, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ==================== 分类表 ==================== +CREATE TABLE IF NOT EXISTS "categories" ( + "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + "name" VARCHAR(50) UNIQUE NOT NULL, + "slug" VARCHAR(50) UNIQUE NOT NULL, + "description" TEXT, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ==================== 标签表 ==================== +CREATE TABLE IF NOT EXISTS "tags" ( + "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + "name" VARCHAR(50) UNIQUE NOT NULL, + "slug" VARCHAR(50) UNIQUE NOT NULL, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ==================== 文章表 ==================== +CREATE TABLE IF NOT EXISTS "posts" ( + "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + "title" VARCHAR(200) NOT NULL, + "slug" VARCHAR(200) UNIQUE NOT NULL, + "content" TEXT NOT NULL, + "summary" TEXT, + "cover_image" VARCHAR(500), + + -- 外键关联 + "author_id" UUID NOT NULL REFERENCES "users"("id") ON DELETE CASCADE, + "category_id" UUID REFERENCES "categories"("id") ON DELETE SET NULL, + + -- 统计数据 + "view_count" INTEGER DEFAULT 0, + "like_count" INTEGER DEFAULT 0, + "comment_count" INTEGER DEFAULT 0, + + -- 状态: draft/published/archived + "status" VARCHAR(20) DEFAULT 'draft', + + -- SEO + "meta_title" VARCHAR(200), + "meta_description" TEXT, + + -- 时间戳 + "published_at" TIMESTAMP, + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 索引 +CREATE INDEX IF NOT EXISTS "idx_posts_status_published_at" ON "posts"("status", "published_at"); +CREATE INDEX IF NOT EXISTS "idx_posts_author_status" ON "posts"("author_id", "status"); + +-- ==================== 文章标签关联表 ==================== +CREATE TABLE IF NOT EXISTS "post_tags" ( + "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + "post_id" UUID NOT NULL REFERENCES "posts"("id") ON DELETE CASCADE, + "tag_id" UUID NOT NULL REFERENCES "tags"("id") ON DELETE CASCADE, + UNIQUE("post_id", "tag_id") +); + +-- ==================== 评论表 ==================== +CREATE TABLE IF NOT EXISTS "comments" ( + "id" UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + "content" TEXT NOT NULL, + "is_approved" BOOLEAN DEFAULT TRUE, + + -- 外键关联 + "author_id" UUID NOT NULL REFERENCES "users"("id") ON DELETE CASCADE, + "post_id" UUID NOT NULL REFERENCES "posts"("id") ON DELETE CASCADE, + "parent_id" UUID REFERENCES "comments"("id") ON DELETE CASCADE, + + -- 时间戳 + "created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- 索引 +CREATE INDEX IF NOT EXISTS "idx_comments_post_id" ON "comments"("post_id"); +CREATE INDEX IF NOT EXISTS "idx_comments_author_id" ON "comments"("author_id"); + +-- ==================== 初始化数据 ==================== + +-- 插入默认分类 +INSERT INTO "categories" ("name", "slug", "description") VALUES + ('动漫资讯', 'anime', '最新动漫新闻、番剧更新、业界动态'), + ('游戏攻略', 'game', '游戏通关指南、角色培养、剧情解析'), + ('二次元美图', 'pictures', '精选壁纸、Cosplay、插画作品'), + ('同人创作', 'fanwork', '同人小说、同人绘画、手办模型') +ON CONFLICT ("slug") DO NOTHING; + +-- 插入默认标签 +INSERT INTO "tags" ("name", "slug") VALUES + ('原神', 'genshin'), + ('崩坏星穹铁道', 'honkai-star-rail'), + ('我的世界', 'minecraft'), + ('EVA', 'evangelion'), + ('约定的梦幻岛', 'neverland'), + ('咒术回战', 'jujutsu-kaisen'), + ('Cosplay', 'cosplay'), + ('手办', 'figure') +ON CONFLICT ("slug") DO NOTHING; + +-- 插入管理员用户 (密码: admin123) +-- 密码哈希基于 bcrypt,使用前请替换为实际哈希值 +INSERT INTO "users" ("username", "email", "password_hash", "is_superuser") VALUES + ('admin', 'admin@acgblog.com', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.TZND60LMTUBu.K', TRUE) +ON CONFLICT ("username") DO NOTHING; + +-- ==================== 注释 ==================== +COMMENT ON TABLE "users" IS '用户表'; +COMMENT ON TABLE "categories" IS '文章分类表'; +COMMENT ON TABLE "tags" IS '文章标签表'; +COMMENT ON TABLE "posts" IS '文章表'; +COMMENT ON TABLE "post_tags" IS '文章标签关联表'; +COMMENT ON TABLE "comments" IS '评论表'; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6f90b3b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,78 @@ +version: '3.8' + +services: + # PostgreSQL 数据库 + postgres: + image: postgres:16-alpine + container_name: acg_blog_db + restart: unless-stopped + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: acg_blog + volumes: + - postgres_data:/var/lib/postgresql/data + - ./backend/schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro + ports: + - "5432:5432" + networks: + - acg_blog_network + + # Redis 缓存 + redis: + image: redis:7-alpine + container_name: acg_blog_redis + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis_data:/data + networks: + - acg_blog_network + + # 后端 API + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: acg_blog_backend + restart: unless-stopped + environment: + - DB_HOST=postgres + - DB_PORT=5432 + - DB_USER=postgres + - DB_PASSWORD=postgres + - DB_NAME=acg_blog + - REDIS_HOST=redis + - REDIS_PORT=6379 + - SECRET_KEY=your-secret-key-change-in-production + - DEBUG=false + ports: + - "8000:8000" + depends_on: + - postgres + - redis + networks: + - acg_blog_network + + # 前端 Nginx + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: acg_blog_frontend + restart: unless-stopped + ports: + - "80:80" + depends_on: + - backend + networks: + - acg_blog_network + +volumes: + postgres_data: + redis_data: + +networks: + acg_blog_network: + driver: bridge diff --git a/docker-deploy.md b/docker-deploy.md new file mode 100644 index 0000000..5687f28 --- /dev/null +++ b/docker-deploy.md @@ -0,0 +1,69 @@ +# Docker 部署指南 + +## 快速启动 + +```bash +# 启动所有服务 +docker-compose up -d + +# 查看服务状态 +docker-compose ps + +# 查看日志 +docker-compose logs -f +``` + +## 服务地址 + +| 服务 | 地址 | +|------|------| +| 前端 | http://localhost | +| 后端 API | http://localhost:8000 | +| API 文档 | http://localhost:8000/docs | +| PostgreSQL | localhost:5432 | +| Redis | localhost:6379 | + +## 初始化数据库 + +首次启动时,数据库会自动创建表结构和初始数据。 + +管理员账户: +- 用户名: `admin` +- 邮箱: `admin@acgblog.com` +- 密码: `admin123` + +**重要**: 请在部署后修改管理员密码! + +## 常用命令 + +```bash +# 重新构建镜像 +docker-compose build --no-cache + +# 停止所有服务 +docker-compose down + +# 停止并删除数据卷 +docker-compose down -v + +# 进入后端容器 +docker exec -it acg_blog_backend sh + +# 进入数据库 +docker exec -it acg_blog_db psql -U postgres -d acg_blog +``` + +## 生产环境部署 + +1. 修改 `backend/.env.production` 中的配置: + - `SECRET_KEY` - 使用随机字符串 + - `BACKEND_CORS_ORIGINS` - 改为你的域名 + +2. 修改 `docker-compose.yml` 中的端口映射(移除端口暴露,仅通过 nginx 反向代理) + +3. 使用 Nginx 或 Traefik 等反向代理配置 HTTPS + +4. 定期备份数据库: + ```bash + docker exec acg_blog_db pg_dump -U postgres acg_blog > backup.sql + ``` diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..96f8570 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,6 @@ +node_modules +dist +.git +.env +*.md +.vscode diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..d1ca399 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,29 @@ +# Vue 3 Frontend +FROM node:20-alpine AS builder + +WORKDIR /app + +# 复制依赖文件 +COPY package*.json ./ + +# 安装依赖 +RUN npm ci + +# 复制源代码 +COPY . . + +# 构建生产版本 +RUN npm run build + +# ==================== Nginx 运行阶段 ==================== +FROM nginx:alpine + +# 复制构建产物 +COPY --from=builder /app/dist /usr/share/nginx/html + +# 复制 Nginx 配置 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..b6b719f --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip 压缩 + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + gzip_min_length 1000; + + # 前端路由(SPA) + location / { + try_files $uri $uri/ /index.html; + } + + # API 代理 + location /api/ { + proxy_pass http://backend:8000/api/; + proxy_http_version 1.1; + 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 /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +}