完善docker部署
This commit is contained in:
7
backend/.dockerignore
Normal file
7
backend/.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
.env
|
||||
.git
|
||||
*.md
|
||||
venv
|
||||
.venv
|
||||
28
backend/.env.production
Normal file
28
backend/.env.production
Normal file
@@ -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
|
||||
25
backend/Dockerfile
Normal file
25
backend/Dockerfile
Normal file
@@ -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"]
|
||||
136
backend/schema.sql
Normal file
136
backend/schema.sql
Normal file
@@ -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 '评论表';
|
||||
78
docker-compose.yml
Normal file
78
docker-compose.yml
Normal file
@@ -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
|
||||
69
docker-deploy.md
Normal file
69
docker-deploy.md
Normal file
@@ -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
|
||||
```
|
||||
6
frontend/.dockerignore
Normal file
6
frontend/.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.env
|
||||
*.md
|
||||
.vscode
|
||||
29
frontend/Dockerfile
Normal file
29
frontend/Dockerfile
Normal file
@@ -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;"]
|
||||
32
frontend/nginx.conf
Normal file
32
frontend/nginx.conf
Normal file
@@ -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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user