AI 全栈开发实战(2):技术选型与项目初始化 —— 搭建 KNow 项目骨架与 Docker 环境
·
前言
上一篇完成了产品定义和架构设计。今天开始真正动手搭项目——从零初始化整个项目骨架,确保所有服务能一键运行。
我们会创建完整的项目目录、配置 docker-compose 编排所有依赖服务、搭建 FastAPI 后端骨架和 React 前端项目。
1. 项目初始化
1.1 创建项目目录
mkdir know && cd know
git init
mkdir -p backend/app/{models,routers,services,schemas,tasks}
mkdir -p frontend/src/{components,pages,hooks,api,lib}
mkdir -p docs
touch backend/Dockerfile backend/requirements.txt
touch frontend/Dockerfile frontend/package.json
touch docker-compose.yml .env.example .gitignore
1.2 .gitignore
# .gitignore
__pycache__/
*.py[cod]
.env
venv/
node_modules/
dist/
.minio/
.postgres/
.idea/
*.db
2. Docker 服务编排
2.1 docker-compose.yml
# docker-compose.yml
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: know
POSTGRES_USER: know
POSTGRES_PASSWORD: know_secret
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U know"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
qdrant:
image: qdrant/qdrant:latest
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_data:/qdrant/storage
minio:
image: minio/minio:latest
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: know_admin
MINIO_ROOT_PASSWORD: know_secret_2024
MINIO_DEFAULT_BUCKETS: know-files
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 10s
retries: 3
backend:
build: ./backend
ports:
- "8000:8000"
env_file: .env
volumes:
- ./backend/app:/app/app
- ./backend/alembic.ini:/app/alembic.ini
- ./backend/migrations:/app/migrations
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
qdrant:
condition: service_started
minio:
condition: service_started
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
celery_worker:
build: ./backend
env_file: .env
volumes:
- ./backend/app:/app/app
depends_on:
- backend
- redis
command: celery -A app.tasks worker --loglevel=info
frontend:
build: ./frontend
ports:
- "3000:3000"
volumes:
- ./frontend/src:/app/src
depends_on:
- backend
command: npm run dev -- --host 0.0.0.0
volumes:
postgres_data:
redis_data:
qdrant_data:
minio_data:
2.2 环境变量
# .env.example
# Database
DATABASE_URL=postgresql+asyncpg://know:know_secret@postgres:5432/know
# Redis
REDIS_URL=redis://redis:6379/0
# Qdrant
QDRANT_HOST=qdrant
QDRANT_PORT=6333
# MinIO
MINIO_ENDPOINT=minio:9000
MINIO_ACCESS_KEY=know_admin
MINIO_SECRET_KEY=know_secret_2024
MINIO_BUCKET=know-files
# LLM
LLM_API_KEY=sk-your-key-here
LLM_BASE_URL=https://api.deepseek.com/v1
LLM_MODEL=deepseek-chat
# Embedding
EMBEDDING_MODEL=BAAI/bge-small-zh-v1.5
EMBEDDING_DIMENSION=512
# JWT
JWT_SECRET=your-jwt-secret-change-in-production
JWT_ALGORITHM=HS256
JWT_EXPIRATION_HOURS=72
# App
APP_NAME=KNow
APP_ENV=development
LOG_LEVEL=info
复制一份用于开发:
cp .env.example .env
3. 后端项目搭建
3.1 依赖
# backend/requirements.txt
# Web
fastapi==0.115.0
uvicorn[standard]==0.30.0
python-multipart==0.0.12
# Database
sqlalchemy[asyncio]==2.0.35
asyncpg==0.30.0
alembic==1.13.0
# AI
openai==1.50.0
sentence-transformers==3.1.0
qdrant-client==1.12.0
# Task Queue
celery==5.4.0
redis==5.2.0
# Storage
minio==7.2.0
# Auth
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-multipart==0.0.12
# Utils
pydantic==2.9.0
pydantic-settings==2.5.0
python-dotenv==1.0.0
httpx==0.27.0
PyMuPDF==1.24.0
python-magic==0.4.27
3.2 配置文件
# backend/app/config.py
from pydantic_settings import BaseSettings
from typing import Optional
import os
class Settings(BaseSettings):
# App
APP_NAME: str = "KNow"
APP_ENV: str = "development"
LOG_LEVEL: str = "info"
# Database
DATABASE_URL: str = "postgresql+asyncpg://know:know_secret@postgres:5432/know"
# Redis
REDIS_URL: str = "redis://redis:6379/0"
# Qdrant
QDRANT_HOST: str = "qdrant"
QDRANT_PORT: int = 6333
# MinIO
MINIO_ENDPOINT: str = "minio:9000"
MINIO_ACCESS_KEY: str = "know_admin"
MINIO_SECRET_KEY: str = "know_secret_2024"
MINIO_BUCKET: str = "know-files"
# LLM
LLM_API_KEY: Optional[str] = None
LLM_BASE_URL: str = "https://api.deepseek.com/v1"
LLM_MODEL: str = "deepseek-chat"
# Embedding
EMBEDDING_MODEL: str = "BAAI/bge-small-zh-v1.5"
EMBEDDING_DIMENSION: int = 512
# JWT
JWT_SECRET: str = "your-jwt-secret"
JWT_ALGORITHM: str = "HS256"
JWT_EXPIRATION_HOURS: int = 72
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
settings = Settings()
3.3 数据库模型
# backend/app/database.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase
from app.config import settings
engine = create_async_engine(settings.DATABASE_URL, echo=False)
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
class Base(DeclarativeBase):
pass
async def get_db():
async with async_session() as session:
try:
yield session
finally:
await session.close()
async def init_db():
"""创建所有表。生产环境应该用 Alembic 迁移。"""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
3.4 数据模型
# backend/app/models/__init__.py
from app.models.user import User
from app.models.knowledge_base import KnowledgeBase
from app.models.document import Document, DocumentChunk
from app.models.conversation import Conversation, Message, Citation
__all__ = [
"User", "KnowledgeBase", "Document", "DocumentChunk",
"Conversation", "Message", "Citation",
]
# backend/app/models/user.py
import uuid
from datetime import datetime
from sqlalchemy import Column, String, Boolean, DateTime
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.database import Base
class User(Base):
__tablename__ = "users"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
email = Column(String(255), unique=True, nullable=False, index=True)
password_hash = Column(String(255), nullable=False)
nickname = Column(String(100), nullable=False)
avatar_url = Column(String(500), default="")
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
knowledge_bases = relationship("KnowledgeBase", back_populates="user")
conversations = relationship("Conversation", back_populates="user")
# backend/app/models/knowledge_base.py
import uuid
from datetime import datetime
from sqlalchemy import Column, String, Text, Integer, DateTime, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.database import Base
class KnowledgeBase(Base):
__tablename__ = "knowledge_bases"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
name = Column(String(200), nullable=False)
description = Column(Text, default="")
embedding_model = Column(String(100), default="BAAI/bge-small-zh-v1.5")
chunk_size = Column(Integer, default=512)
chunk_overlap = Column(Integer, default=128)
document_count = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user = relationship("User", back_populates="knowledge_bases")
documents = relationship("Document", back_populates="knowledge_base",
cascade="all, delete-orphan")
3.5 FastAPI 入口
# backend/app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.config import settings
from app.database import init_db
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理。"""
print(f"Starting {settings.APP_NAME}...")
await init_db()
print("Database initialized")
yield
print("Shutting down...")
app = FastAPI(
title=settings.APP_NAME,
lifespan=lifespan,
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", "http://frontend:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/api/health")
async def health_check():
return {"status": "ok", "app": settings.APP_NAME, "env": settings.APP_ENV}
# 注册路由(后续添加)
# from app.routers import auth, knowledge_bases, documents, conversations
# app.include_router(auth.router, prefix="/api/auth", tags=["Auth"])
# app.include_router(knowledge_bases.router, prefix="/api/knowledge-bases", tags=["Knowledge Bases"])
3.6 Dockerfile
# backend/Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
libmagic1 \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 下载 Embedding 模型(减少首次请求延迟)
RUN python -c "from sentence_transformers import SentenceTransformer; \
SentenceTransformer('BAAI/bge-small-zh-v1.5')"
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
4. 前端项目搭建
4.1 初始化 React + Vite 项目
cd know/frontend
npm create vite@latest . -- --template react-ts
npm install
4.2 安装依赖
# UI 框架
npm install tailwindcss @tailwindcss/vite lucide-react
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu
npm install @radix-ui/react-tabs @radix-ui/react-toast
# 路由
npm install react-router-dom
# HTTP 客户端
npm install @tanstack/react-query axios
# 表单
npm install react-hook-form @hookform/resolvers zod
# 其他
npm install react-markdown remark-gfm date-fns clsx tailwind-merge
4.3 Vite 配置
// frontend/vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "path";
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
server: {
port: 3000,
proxy: {
"/api": {
target: "http://backend:8000",
changeOrigin: true,
},
},
},
});
4.4 前端目录结构
frontend/src/
├── main.tsx # 入口
├── App.tsx # 路由配置
├── index.css # 全局样式(Tailwind)
├── lib/
│ ├── utils.ts # 工具函数
│ └── api.ts # Axios 实例
├── hooks/
│ └── useAuth.ts # 认证 hook
├── api/
│ ├── auth.ts # 认证 API
│ └── knowledgeBase.ts # 知识库 API
├── components/
│ └── ui/ # UI 组件(shadcn)
│ ├── button.tsx
│ ├── input.tsx
│ ├── dialog.tsx
│ └── ...
├── pages/
│ ├── Login.tsx
│ ├── Register.tsx
│ ├── Dashboard.tsx
│ ├── KnowledgeBase.tsx
│ └── Chat.tsx
└── types/
└── index.ts # TypeScript 类型定义
4.5 shadcn/ui 配置
npx shadcn@latest init
npx shadcn@latest add button input card dialog toast
4.6 Dockerfile
# frontend/Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
4.7 基础 API 客户端
// frontend/src/lib/api.ts
import axios from "axios";
const api = axios.create({
baseURL: "/api",
timeout: 30000,
headers: {
"Content-Type": "application/json",
},
});
// 请求拦截器——自动添加 Token
api.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器——处理 401
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem("token");
window.location.href = "/login";
}
return Promise.reject(error);
}
);
export default api;
4.8 路由配置
// frontend/src/App.tsx
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import Login from "./pages/Login";
import Register from "./pages/Register";
import Dashboard from "./pages/Dashboard";
import KnowledgeBaseDetail from "./pages/KnowledgeBaseDetail";
import Chat from "./pages/Chat";
import Layout from "./components/Layout";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route element={<Layout />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/knowledge-bases/:id" element={<KnowledgeBaseDetail />} />
<Route path="/chat" element={<Chat />} />
<Route path="/chat/:id" element={<Chat />} />
</Route>
<Route path="*" element={<Navigate to="/dashboard" />} />
</Routes>
</BrowserRouter>
);
}
export default App;
5. 验证项目能跑起来
5.1 一键启动
# 在项目根目录
cp .env.example .env
# 编辑 .env,填入 LLM_API_KEY
docker compose up --build
5.2 验证所有服务
# 后端健康检查
curl http://localhost:8000/api/health
# {"status":"ok","app":"KNow","env":"development"}
# PostgreSQL
docker compose exec postgres pg_isready -U know
# Redis
docker compose exec redis redis-cli ping
# Qdrant
curl http://localhost:6333/collections
# MinIO
curl http://localhost:9000/minio/health/live
# 前端
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000
# 200
5.3 验证成功后看到的
CONTAINER ID IMAGE STATUS PORTS
a1b2c3d4e5f6 know-backend Up 2 minutes 0.0.0.0:8000->8000/tcp
b2c3d4e5f6a7 know-frontend Up 2 minutes 0.0.0.0:3000->3000/tcp
c3d4e5f6a7b8 postgres:16 Up 2 minutes 0.0.0.0:5432->5432/tcp
d4e5f6a7b8c9 redis:7 Up 2 minutes 0.0.0.0:6379->6379/tcp
e5f6a7b8c9d0 qdrant:latest Up 2 minutes 0.0.0.0:6333->6333/tcp
f6a7b8c9d0e1 minio:latest Up 2 minutes 0.0.0.0:9000->9000/tcp
6. 开发工作流
6.1 日常开发
# 启动所有服务
docker compose up -d
# 查看日志
docker compose logs -f backend
docker compose logs -f frontend
# 进入后端容器
docker compose exec backend bash
# 运行数据库迁移
docker compose exec backend alembic upgrade head
# 停止所有服务
docker compose down
6.2 热重载
开发模式下:
- 后端:FastAPI 的
--reload自动监听代码变化 - 前端:Vite 的 HMR(热模块替换)毫秒级更新
- 代码修改后只需保存文件,不需要重启容器
6.3 数据库迁移
# 初始化 Alembic
docker compose exec backend alembic init migrations
# 创建新的迁移
docker compose exec backend alembic revision --autogenerate -m "add users table"
# 执行迁移
docker compose exec backend alembic upgrade head
# 回滚
docker compose exec backend alembic downgrade -1
总结
今天完成了:
| 组件 | 状态 |
|---|---|
| docker-compose 编排(6 个服务) | ✅ |
| FastAPI 后端骨架(配置、数据库、模型) | ✅ |
| React 前端骨架(Vite + Tailwind + 路由) | ✅ |
| API 客户端与拦截器 | ✅ |
| Dockerfile(前后端) | ✅ |
| 开发工作流 | ✅ |
现在运行 docker compose up --build 就能看到完整的项目跑起来了。
下一篇我们将实现用户系统——注册、登录、JWT 认证、密码加密。
本文是 《AI 全栈开发实战——做一个真正的产品》 系列的第 2 篇。
系列目录:
- ✅ 产品定义与架构设计
- ✅ 技术选型与项目初始化 ← 你在这里
- 📝 用户系统(注册/登录/JWT)
- 📝 知识库与文档管理
- 📝 文档处理 Pipeline
…本文由 Zyentor(智元界) 原创发布
本文发布于 Zyentor(智元界) —— AI 开发者社区
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)