diff --git a/.gitignore b/.gitignore
index 9f3eedb..fe4b717 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,59 @@
-### Example user template template
-### Example user template
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
-# IntelliJ project files
-.idea
-*.iml
-out
-gen
+# 虚拟环境
+venv/
+env/
+ENV/
+.venv
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# 数据库
+*.db
+*.sqlite
+*.sqlite3
+
+# 环境变量
+.env
+.env.local
+
+# 日志
+*.log
+
+# 上传文件
+uploads/
+static/uploads/
+
+# 前端
+frontend/node_modules/
+frontend/dist/
+frontend/.vite/
+
+# 操作系统
+.DS_Store
+Thumbs.db
diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md
new file mode 100644
index 0000000..7c60515
--- /dev/null
+++ b/PROJECT_STRUCTURE.md
@@ -0,0 +1,190 @@
+# 项目结构说明
+
+本文档详细说明了项目的目录结构和各文件的作用。
+
+## 目录结构
+
+```
+blogweb/
+├── backend/ # 后端代码目录
+│ ├── app/ # FastAPI 应用主目录
+│ │ ├── __init__.py
+│ │ ├── main.py # FastAPI 应用入口
+│ │ │
+│ │ ├── api/ # API 路由模块
+│ │ │ ├── __init__.py
+│ │ │ └── api_v1/ # API v1 版本
+│ │ │ ├── __init__.py
+│ │ │ ├── api.py # API 路由聚合
+│ │ │ └── endpoints/ # API 端点
+│ │ │ ├── __init__.py
+│ │ │ ├── auth.py # 认证相关端点
+│ │ │ ├── users.py # 用户相关端点
+│ │ │ ├── todos.py # 待办事项端点
+│ │ │ └── posts.py # 博客文章端点
+│ │ │
+│ │ ├── core/ # 核心配置模块
+│ │ │ ├── __init__.py
+│ │ │ ├── config.py # 应用配置
+│ │ │ └── security.py # 安全相关(密码哈希、JWT)
+│ │ │
+│ │ ├── db/ # 数据库相关
+│ │ │ ├── __init__.py
+│ │ │ ├── base.py # 数据库基础配置
+│ │ │ ├── session.py # 数据库会话管理
+│ │ │ └── init_db.py # 数据库初始化脚本 ⭐
+│ │ │
+│ │ ├── models/ # 数据模型(SQLModel)
+│ │ │ ├── __init__.py
+│ │ │ ├── user.py # 用户模型
+│ │ │ ├── todo.py # 待办事项模型
+│ │ │ ├── post.py # 博客文章模型
+│ │ │ ├── transaction.py # 记账记录模型
+│ │ │ ├── media.py # 书影音收藏模型
+│ │ │ ├── tag.py # 标签模型
+│ │ │ ├── chat.py # 聊天消息模型
+│ │ │ └── upload.py # 文件上传模型
+│ │ │
+│ │ ├── schemas/ # Pydantic 模式(API 数据验证)
+│ │ │ ├── __init__.py
+│ │ │ ├── user.py # 用户相关模式
+│ │ │ ├── todo.py # 待办事项相关模式
+│ │ │ └── post.py # 博客文章相关模式
+│ │ │
+│ │ └── initial_data/ # 初始数据模块
+│ │ ├── __init__.py
+│ │ └── README.md
+│ │
+│ ├── database.py # (旧文件,可删除)
+│ ├── models.py # (旧文件,可删除)
+│ ├── init_db.py # (旧文件,可删除)
+│ └── README.md # 后端说明文档
+│
+├── frontend/ # 前端代码目录
+│ ├── src/
+│ │ ├── views/ # 页面组件
+│ │ │ ├── Home.vue # 首页
+│ │ │ ├── Login.vue # 登录页
+│ │ │ ├── Register.vue # 注册页
+│ │ │ ├── Todos.vue # 待办事项页
+│ │ │ └── Posts.vue # 博客文章页
+│ │ ├── stores/ # Pinia 状态管理
+│ │ │ └── auth.js # 认证状态
+│ │ ├── router/ # 路由配置
+│ │ │ └── index.js
+│ │ ├── App.vue # 根组件
+│ │ └── main.js # 前端入口
+│ ├── index.html # HTML 模板
+│ ├── package.json # 前端依赖配置
+│ └── vite.config.js # Vite 配置
+│
+├── main.py # 项目启动入口 ⭐
+├── requirements.txt # Python 依赖
+├── .env.example # 环境变量示例
+├── .gitignore # Git 忽略规则
+├── README.md # 项目说明文档
+├── PROJECT_STRUCTURE.md # 本文件
+└── 数据库设计说明.md # 数据库设计文档
+```
+
+## 关键文件说明
+
+### ⭐ 重要文件
+
+1. **`main.py`** - 项目启动入口
+ - 用于开发环境快速启动 FastAPI 应用
+ - 使用 uvicorn 运行 `app.main:app`
+
+2. **`backend/app/main.py`** - FastAPI 应用主文件
+ - 创建 FastAPI 应用实例
+ - 配置 CORS 中间件
+ - 注册 API 路由
+
+3. **`backend/app/db/init_db.py`** - 数据库初始化脚本 ⭐
+ - **位置已调整**:从 `backend/init_db.py` 移动到 `backend/app/db/init_db.py`
+ - 运行方式:
+ ```bash
+ python -m app.db.init_db # 创建表
+ python -m app.db.init_db --reset # 重置数据库
+ ```
+
+## 模块说明
+
+### 后端模块
+
+#### `app/core/` - 核心配置
+- `config.py`: 应用配置(数据库、安全、CORS 等)
+- `security.py`: 密码哈希、JWT 令牌生成和验证
+
+#### `app/db/` - 数据库
+- `base.py`: 数据库基础配置,导入所有模型
+- `session.py`: 数据库会话管理和依赖注入
+- `init_db.py`: 数据库初始化脚本
+
+#### `app/models/` - 数据模型
+- 使用 SQLModel 定义数据库表结构
+- 每个模型一个文件,便于维护
+
+#### `app/schemas/` - Pydantic 模式
+- 用于 API 请求和响应的数据验证
+- 定义创建、更新、响应等不同场景的模式
+
+#### `app/api/` - API 路由
+- `api_v1/api.py`: 聚合所有 API 路由
+- `api_v1/endpoints/`: 各个功能模块的端点
+
+### 前端模块
+
+#### `src/views/` - 页面组件
+- Vue 3 单文件组件
+- 每个功能对应一个页面
+
+#### `src/stores/` - 状态管理
+- 使用 Pinia 管理全局状态
+- `auth.js`: 用户认证状态
+
+#### `src/router/` - 路由
+- Vue Router 配置
+- 定义前端路由规则
+
+## 开发流程
+
+1. **初始化数据库**
+ ```bash
+ python -m app.db.init_db
+ ```
+
+2. **启动后端**
+ ```bash
+ python main.py
+ # 或
+ uvicorn app.main:app --reload
+ ```
+
+3. **启动前端**
+ ```bash
+ cd frontend
+ npm install
+ npm run dev
+ ```
+
+## 注意事项
+
+1. **数据库初始化脚本位置已调整**
+ - 旧位置:`backend/init_db.py`
+ - 新位置:`backend/app/db/init_db.py`
+ - 运行方式:`python -m app.db.init_db`
+
+2. **旧文件清理**
+ - `backend/database.py`、`backend/models.py`、`backend/init_db.py` 可以删除
+ - 新代码已迁移到 `backend/app/` 目录下
+
+3. **环境变量**
+ - 复制 `.env.example` 为 `.env`
+ - 修改其中的配置项(特别是 `SECRET_KEY`)
+
+4. **前后端分离**
+ - 后端运行在 `http://localhost:8000`
+ - 前端运行在 `http://localhost:5173`
+ - 前端通过代理访问后端 API
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c47d725
--- /dev/null
+++ b/README.md
@@ -0,0 +1,179 @@
+# 个人博客网站
+
+基于 FastAPI + Vue 3 开发的全栈个人博客网站项目。
+
+## 项目结构
+
+```
+blogweb/
+├── backend/ # 后端代码
+│ ├── app/
+│ │ ├── api/ # API 路由
+│ │ │ └── api_v1/
+│ │ │ ├── endpoints/ # API 端点
+│ │ │ └── api.py
+│ │ ├── core/ # 核心配置
+│ │ │ ├── config.py
+│ │ │ └── security.py
+│ │ ├── db/ # 数据库相关
+│ │ │ ├── base.py
+│ │ │ ├── session.py
+│ │ │ └── init_db.py
+│ │ ├── models/ # 数据模型
+│ │ ├── schemas/ # Pydantic 模式
+│ │ └── main.py # FastAPI 应用入口
+│ ├── database.py # (旧文件,可删除)
+│ ├── models.py # (旧文件,可删除)
+│ └── init_db.py # (旧文件,可删除)
+├── frontend/ # 前端代码
+│ ├── src/
+│ │ ├── views/ # 页面组件
+│ │ ├── stores/ # Pinia 状态管理
+│ │ ├── router/ # 路由配置
+│ │ ├── App.vue
+│ │ └── main.js
+│ ├── package.json
+│ └── vite.config.js
+├── main.py # 项目启动入口
+├── requirements.txt # Python 依赖
+└── README.md
+```
+
+## 功能特性
+
+### 已完成
+- ✅ 用户注册和登录(JWT 认证)
+- ✅ 待办事项管理(CRUD)
+- ✅ 博客文章发布(CRUD)
+
+### 开发中
+- 🚧 天气查询
+- 🚧 个人记账本
+- 🚧 书影音收藏
+- 🚧 实时聊天室(WebSocket)
+- 🚧 文件上传
+
+## 快速开始
+
+### 1. 安装后端依赖
+
+```bash
+pip install -r requirements.txt
+```
+
+### 2. 初始化数据库
+
+```bash
+# 从项目根目录运行
+python -m app.db.init_db
+```
+
+或者:
+
+```bash
+cd backend/app/db
+python init_db.py
+```
+
+如果需要重置数据库(⚠️ 会删除所有数据):
+
+```bash
+python -m app.db.init_db --reset
+```
+
+### 3. 配置环境变量
+
+复制 `.env.example` 为 `.env` 并修改配置:
+
+```bash
+cp .env.example .env
+```
+
+### 4. 启动后端服务
+
+```bash
+# 方式1:使用 uvicorn 直接运行
+uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
+
+# 方式2:使用项目入口文件
+python main.py
+```
+
+后端服务将在 `http://localhost:8000` 启动。
+
+API 文档:`http://localhost:8000/docs`
+
+### 5. 安装前端依赖
+
+```bash
+cd frontend
+npm install
+```
+
+### 6. 启动前端服务
+
+```bash
+npm run dev
+```
+
+前端服务将在 `http://localhost:5173` 启动。
+
+## 开发路线图
+
+本项目按照以下阶段逐步开发:
+
+1. **阶段 1**:静态页面 + TodoList(无用户)✅
+2. **阶段 2**:博客系统(公开)✅
+3. **阶段 3**:用户系统 + 权限隔离 ✅
+4. **阶段 4**:天气查询 + 个人记账本 🚧
+5. **阶段 5**:书影音收藏站 🚧
+6. **阶段 6**:聊天室(WebSocket) + 文件上传 🚧
+
+## 技术栈
+
+### 后端
+- FastAPI - 现代、快速的 Web 框架
+- SQLModel - 基于 SQLAlchemy 和 Pydantic 的 ORM
+- SQLite - 开发环境数据库
+- JWT - 用户认证
+- Pydantic - 数据验证
+
+### 前端
+- Vue 3 - 渐进式 JavaScript 框架
+- Vue Router - 路由管理
+- Pinia - 状态管理
+- Axios - HTTP 客户端
+- Vite - 构建工具
+
+## API 端点
+
+### 认证
+- `POST /api/v1/auth/register` - 用户注册
+- `POST /api/v1/auth/login` - 用户登录
+
+### 用户
+- `GET /api/v1/users/me` - 获取当前用户信息
+- `GET /api/v1/users/` - 获取用户列表
+
+### 待办事项
+- `GET /api/v1/todos/` - 获取待办事项列表
+- `POST /api/v1/todos/` - 创建待办事项
+- `GET /api/v1/todos/{id}` - 获取单个待办事项
+- `PUT /api/v1/todos/{id}` - 更新待办事项
+- `DELETE /api/v1/todos/{id}` - 删除待办事项
+
+### 博客文章
+- `GET /api/v1/posts/` - 获取文章列表
+- `POST /api/v1/posts/` - 创建文章
+- `GET /api/v1/posts/{id}` - 获取单个文章
+- `PUT /api/v1/posts/{id}` - 更新文章
+- `DELETE /api/v1/posts/{id}` - 删除文章
+
+## 数据库设计
+
+详见 `数据库设计说明.md`
+
+## 许可证
+
+MIT
+
diff --git a/SETUP_GUIDE.md b/SETUP_GUIDE.md
new file mode 100644
index 0000000..6cb1a0b
--- /dev/null
+++ b/SETUP_GUIDE.md
@@ -0,0 +1,183 @@
+# 项目设置指南
+
+## 快速开始
+
+### 1. 安装后端依赖
+
+```bash
+pip install -r requirements.txt
+```
+
+### 2. 初始化数据库
+
+```bash
+# 从项目根目录运行
+python -m app.db.init_db
+```
+
+如果需要重置数据库(⚠️ 会删除所有数据):
+
+```bash
+python -m app.db.init_db --reset
+```
+
+### 3. 配置环境变量
+
+复制 `.env.example` 为 `.env`:
+
+```bash
+# Windows
+copy .env.example .env
+
+# Linux/Mac
+cp .env.example .env
+```
+
+编辑 `.env` 文件,修改以下配置:
+
+```env
+# 必须修改(生产环境)
+SECRET_KEY=your-secret-key-change-in-production
+
+# 可选:如果需要天气功能
+OPENWEATHER_API_KEY=your-api-key
+```
+
+### 4. 启动后端服务
+
+```bash
+# 方式1:使用项目入口文件
+python main.py
+
+# 方式2:使用 uvicorn 直接运行
+uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
+```
+
+后端服务将在 `http://localhost:8000` 启动。
+
+访问 API 文档:`http://localhost:8000/docs`
+
+### 5. 安装前端依赖
+
+```bash
+cd frontend
+npm install
+```
+
+### 6. 启动前端服务
+
+```bash
+npm run dev
+```
+
+前端服务将在 `http://localhost:5173` 启动。
+
+## 项目结构说明
+
+详细的项目结构说明请查看 `PROJECT_STRUCTURE.md`。
+
+## 重要变更
+
+### 数据库初始化脚本位置调整
+
+**旧位置**:`backend/init_db.py`
+**新位置**:`backend/app/db/init_db.py`
+
+**新的运行方式**:
+```bash
+python -m app.db.init_db # 创建表
+python -m app.db.init_db --reset # 重置数据库
+```
+
+### 旧文件清理
+
+以下文件可以删除(代码已迁移到新位置):
+- `backend/database.py` → 已迁移到 `backend/app/db/session.py`
+- `backend/models.py` → 已迁移到 `backend/app/models/`
+- `backend/init_db.py` → 已迁移到 `backend/app/db/init_db.py`
+
+## 开发路线图
+
+### ✅ 已完成
+
+1. **阶段 1**:静态页面 + TodoList(无用户)
+2. **阶段 2**:博客系统(公开)
+3. **阶段 3**:用户系统 + 权限隔离
+
+### 🚧 开发中
+
+4. **阶段 4**:天气查询 + 个人记账本
+5. **阶段 5**:书影音收藏站
+6. **阶段 6**:聊天室(WebSocket) + 文件上传
+
+## 测试 API
+
+### 1. 用户注册
+
+```bash
+curl -X POST "http://localhost:8000/api/v1/auth/register" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "username": "testuser",
+ "email": "test@example.com",
+ "password": "testpass123"
+ }'
+```
+
+### 2. 用户登录
+
+```bash
+curl -X POST "http://localhost:8000/api/v1/auth/login" \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "username=testuser&password=testpass123"
+```
+
+返回的 `access_token` 用于后续 API 请求。
+
+### 3. 获取当前用户信息
+
+```bash
+curl -X GET "http://localhost:8000/api/v1/users/me" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
+```
+
+### 4. 创建待办事项
+
+```bash
+curl -X POST "http://localhost:8000/api/v1/todos/" \
+ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "title": "完成项目文档"
+ }'
+```
+
+## 常见问题
+
+### Q: 运行 `python -m app.db.init_db` 报错 "No module named 'app'"
+
+**A**: 确保从项目根目录运行,并且 `backend` 目录在 Python 路径中。或者使用:
+
+```bash
+cd backend
+python -m app.db.init_db
+```
+
+### Q: 前端无法连接到后端 API
+
+**A**: 检查:
+1. 后端服务是否在 `http://localhost:8000` 运行
+2. `frontend/vite.config.js` 中的代理配置是否正确
+3. 浏览器控制台是否有 CORS 错误
+
+### Q: 数据库文件在哪里?
+
+**A**: 数据库文件 `blogweb.db` 在项目根目录下。
+
+## 下一步
+
+1. 完善前端 UI 设计
+2. 实现剩余功能模块(天气、记账、收藏、聊天、文件上传)
+3. 添加单元测试
+4. 配置生产环境部署
+
diff --git a/backend/README.md b/backend/README.md
new file mode 100644
index 0000000..e925f0e
--- /dev/null
+++ b/backend/README.md
@@ -0,0 +1,98 @@
+# 数据库模块说明
+
+本目录包含数据库相关的所有代码。
+
+## 文件说明
+
+- `app/models/` - 数据库模型定义(9个表)
+- `app/db/session.py` - 数据库连接和配置
+- `app/db/init_db.py` - 数据库初始化脚本
+
+## 安装依赖
+
+```bash
+pip install -r ../requirements.txt
+```
+
+## 初始化数据库
+
+### 首次创建数据库
+
+运行以下命令创建数据库表:
+
+```bash
+cd backend
+python -m app.db.init_db
+```
+
+或者从项目根目录运行:
+
+```bash
+python -m backend.app.db.init_db
+```
+
+执行后会在项目根目录生成 `blogweb.db` SQLite 数据库文件。
+
+### 模型更新后更新数据库
+
+⚠️ **重要提示**:`create_all()` 只会创建**不存在的表**,**不会修改已存在表的结构**。
+
+如果 models 有更新(添加字段、修改字段类型等),有两种方式:
+
+#### 方式1:重置数据库(开发环境推荐)
+
+⚠️ **会删除所有数据**,适合开发环境:
+
+```bash
+python -m app.db.init_db --reset
+```
+
+#### 方式2:使用数据库迁移工具(生产环境推荐)
+
+对于生产环境,建议使用 **Alembic** 进行数据库迁移:
+
+```bash
+# 安装 Alembic
+pip install alembic
+
+# 初始化迁移环境
+alembic init alembic
+
+# 生成迁移脚本
+alembic revision --autogenerate -m "描述变更"
+
+# 执行迁移
+alembic upgrade head
+```
+
+## 数据库表结构
+
+根据 `数据库设计说明.md` 创建了以下9个表:
+
+1. **users** - 用户账户信息
+2. **todos** - 待办事项列表
+3. **posts** - 博客文章
+4. **transactions** - 个人记账记录
+5. **media** - 书影音收藏条目
+6. **tags** - 媒体标签
+7. **media_tags** - 媒体与标签的多对多关联表
+8. **chat_messages** - 聊天室消息记录
+9. **uploads** - 用户上传的文件元数据
+
+## 使用示例
+
+在 FastAPI 应用中使用数据库:
+
+```python
+from app.db.session import get_db
+from app.models import User, Todo
+from sqlmodel import Session, select
+
+# 在路由中使用
+@app.get("/users")
+def get_users(session: Session = Depends(get_db)):
+ statement = select(User)
+ users = session.exec(statement).all()
+ return users
+```
+
diff --git a/backend/__init__.py b/backend/__init__.py
new file mode 100644
index 0000000..92fb946
--- /dev/null
+++ b/backend/__init__.py
@@ -0,0 +1,30 @@
+"""
+Backend 模块
+"""
+from .app.db.session import engine, get_db
+from .app.db.init_db import init_db, reset_db
+from .app.models import (
+ User, Todo, Post, Transaction, Media, Tag, MediaTag,
+ ChatMessage, Upload
+)
+
+# 为了向后兼容,保留 get_session 别名
+get_session = get_db
+
+__all__ = [
+ "engine",
+ "init_db",
+ "reset_db",
+ "get_db",
+ "get_session",
+ "User",
+ "Todo",
+ "Post",
+ "Transaction",
+ "Media",
+ "Tag",
+ "MediaTag",
+ "ChatMessage",
+ "Upload",
+]
+
diff --git a/backend/app/__init__.py b/backend/app/__init__.py
new file mode 100644
index 0000000..21702a8
--- /dev/null
+++ b/backend/app/__init__.py
@@ -0,0 +1,4 @@
+"""
+FastAPI 应用包
+"""
+
diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py
new file mode 100644
index 0000000..bcfebce
--- /dev/null
+++ b/backend/app/api/__init__.py
@@ -0,0 +1,4 @@
+"""
+API 路由模块
+"""
+
diff --git a/backend/app/api/api_v1/__init__.py b/backend/app/api/api_v1/__init__.py
new file mode 100644
index 0000000..4ed9da1
--- /dev/null
+++ b/backend/app/api/api_v1/__init__.py
@@ -0,0 +1,4 @@
+"""
+API v1 路由
+"""
+
diff --git a/backend/app/api/api_v1/api.py b/backend/app/api/api_v1/api.py
new file mode 100644
index 0000000..a47e369
--- /dev/null
+++ b/backend/app/api/api_v1/api.py
@@ -0,0 +1,14 @@
+"""
+API v1 路由聚合
+"""
+from fastapi import APIRouter
+from app.api.api_v1.endpoints import auth, todos, posts, users
+
+api_router = APIRouter()
+
+# 注册各个功能模块的路由
+api_router.include_router(auth.router, prefix="/auth", tags=["认证"])
+api_router.include_router(users.router, prefix="/users", tags=["用户"])
+api_router.include_router(todos.router, prefix="/todos", tags=["待办事项"])
+api_router.include_router(posts.router, prefix="/posts", tags=["博客"])
+
diff --git a/backend/app/api/api_v1/endpoints/__init__.py b/backend/app/api/api_v1/endpoints/__init__.py
new file mode 100644
index 0000000..ab2f8d8
--- /dev/null
+++ b/backend/app/api/api_v1/endpoints/__init__.py
@@ -0,0 +1,4 @@
+"""
+API 端点模块
+"""
+
diff --git a/backend/app/api/api_v1/endpoints/posts.py b/backend/app/api/api_v1/endpoints/posts.py
new file mode 100644
index 0000000..9104c0f
--- /dev/null
+++ b/backend/app/api/api_v1/endpoints/posts.py
@@ -0,0 +1,142 @@
+"""
+博客文章相关 API 端点
+"""
+from fastapi import APIRouter, Depends, HTTPException, status
+from sqlmodel import Session, select
+from typing import List
+
+from app.db.session import get_db
+from app.models.post import Post
+from app.models.user import User
+from app.schemas.post import Post as PostSchema, PostCreate, PostUpdate
+from app.api.api_v1.endpoints.users import get_current_user
+
+router = APIRouter()
+
+
+@router.post("/", response_model=PostSchema, status_code=status.HTTP_201_CREATED)
+def create_post(
+ post_in: PostCreate,
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """创建博客文章"""
+ # 检查 slug 是否已存在(同一用户)
+ statement = select(Post).where(
+ Post.slug == post_in.slug,
+ Post.user_id == current_user.id
+ )
+ existing_post = db.exec(statement).first()
+ if existing_post:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="该 slug 已存在"
+ )
+
+ db_post = Post(
+ title=post_in.title,
+ slug=post_in.slug,
+ content=post_in.content,
+ user_id=current_user.id
+ )
+ db.add(db_post)
+ db.commit()
+ db.refresh(db_post)
+ return db_post
+
+
+@router.get("/", response_model=List[PostSchema])
+def read_posts(
+ skip: int = 0,
+ limit: int = 100,
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """获取当前用户的博客文章列表"""
+ statement = (
+ select(Post)
+ .where(Post.user_id == current_user.id)
+ .offset(skip)
+ .limit(limit)
+ )
+ posts = db.exec(statement).all()
+ return posts
+
+
+@router.get("/{post_id}", response_model=PostSchema)
+def read_post(
+ post_id: int,
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """获取单个博客文章"""
+ statement = select(Post).where(
+ Post.id == post_id,
+ Post.user_id == current_user.id
+ )
+ post = db.exec(statement).first()
+ if post is None:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail="文章不存在"
+ )
+ return post
+
+
+@router.put("/{post_id}", response_model=PostSchema)
+def update_post(
+ post_id: int,
+ post_in: PostUpdate,
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """更新博客文章"""
+ statement = select(Post).where(
+ Post.id == post_id,
+ Post.user_id == current_user.id
+ )
+ post = db.exec(statement).first()
+ if post is None:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail="文章不存在"
+ )
+
+ if post_in.title is not None:
+ post.title = post_in.title
+ if post_in.slug is not None:
+ post.slug = post_in.slug
+ if post_in.content is not None:
+ post.content = post_in.content
+
+ from datetime import datetime
+ post.updated_at = datetime.now()
+
+ db.add(post)
+ db.commit()
+ db.refresh(post)
+ return post
+
+
+@router.delete("/{post_id}", status_code=status.HTTP_204_NO_CONTENT)
+def delete_post(
+ post_id: int,
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """删除博客文章"""
+ statement = select(Post).where(
+ Post.id == post_id,
+ Post.user_id == current_user.id
+ )
+ post = db.exec(statement).first()
+ if post is None:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail="文章不存在"
+ )
+
+ db.delete(post)
+ db.commit()
+ return None
+
diff --git a/backend/app/api/api_v1/endpoints/todos.py b/backend/app/api/api_v1/endpoints/todos.py
new file mode 100644
index 0000000..c09ee4b
--- /dev/null
+++ b/backend/app/api/api_v1/endpoints/todos.py
@@ -0,0 +1,123 @@
+"""
+待办事项相关 API 端点
+"""
+from fastapi import APIRouter, Depends, HTTPException, status
+from sqlmodel import Session, select
+from typing import List
+
+from app.db.session import get_db
+from app.models.todo import Todo
+from app.models.user import User
+from app.schemas.todo import Todo as TodoSchema, TodoCreate, TodoUpdate
+from app.api.api_v1.endpoints.users import get_current_user
+
+router = APIRouter()
+
+
+@router.post("/", response_model=TodoSchema, status_code=status.HTTP_201_CREATED)
+def create_todo(
+ todo_in: TodoCreate,
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """创建待办事项"""
+ db_todo = Todo(
+ title=todo_in.title,
+ user_id=current_user.id
+ )
+ db.add(db_todo)
+ db.commit()
+ db.refresh(db_todo)
+ return db_todo
+
+
+@router.get("/", response_model=List[TodoSchema])
+def read_todos(
+ skip: int = 0,
+ limit: int = 100,
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """获取当前用户的待办事项列表"""
+ statement = (
+ select(Todo)
+ .where(Todo.user_id == current_user.id)
+ .offset(skip)
+ .limit(limit)
+ )
+ todos = db.exec(statement).all()
+ return todos
+
+
+@router.get("/{todo_id}", response_model=TodoSchema)
+def read_todo(
+ todo_id: int,
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """获取单个待办事项"""
+ statement = select(Todo).where(
+ Todo.id == todo_id,
+ Todo.user_id == current_user.id
+ )
+ todo = db.exec(statement).first()
+ if todo is None:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail="待办事项不存在"
+ )
+ return todo
+
+
+@router.put("/{todo_id}", response_model=TodoSchema)
+def update_todo(
+ todo_id: int,
+ todo_in: TodoUpdate,
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """更新待办事项"""
+ statement = select(Todo).where(
+ Todo.id == todo_id,
+ Todo.user_id == current_user.id
+ )
+ todo = db.exec(statement).first()
+ if todo is None:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail="待办事项不存在"
+ )
+
+ if todo_in.title is not None:
+ todo.title = todo_in.title
+ if todo_in.done is not None:
+ todo.done = todo_in.done
+
+ db.add(todo)
+ db.commit()
+ db.refresh(todo)
+ return todo
+
+
+@router.delete("/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
+def delete_todo(
+ todo_id: int,
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """删除待办事项"""
+ statement = select(Todo).where(
+ Todo.id == todo_id,
+ Todo.user_id == current_user.id
+ )
+ todo = db.exec(statement).first()
+ if todo is None:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail="待办事项不存在"
+ )
+
+ db.delete(todo)
+ db.commit()
+ return None
+
diff --git a/backend/app/api/api_v1/endpoints/users.py b/backend/app/api/api_v1/endpoints/users.py
new file mode 100644
index 0000000..448eb91
--- /dev/null
+++ b/backend/app/api/api_v1/endpoints/users.py
@@ -0,0 +1,63 @@
+"""
+用户相关 API 端点
+"""
+from fastapi import APIRouter, Depends, HTTPException, status
+from sqlmodel import Session, select
+from typing import List
+
+from app.db.session import get_db
+from app.models.user import User
+from app.schemas.user import User as UserSchema
+from app.core.security import decode_access_token
+from fastapi.security import OAuth2PasswordBearer
+
+router = APIRouter()
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
+
+
+async def get_current_user(
+ token: str = Depends(oauth2_scheme),
+ db: Session = Depends(get_db)
+) -> User:
+ """获取当前登录用户"""
+ payload = decode_access_token(token)
+ if payload is None:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="无效的认证凭据",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+
+ username: str = payload.get("sub")
+ if username is None:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="无效的认证凭据",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+
+ statement = select(User).where(User.username == username)
+ user = db.exec(statement).first()
+ if user is None:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="用户不存在",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
+
+ return user
+
+
+@router.get("/me", response_model=UserSchema)
+async def read_users_me(current_user: User = Depends(get_current_user)):
+ """获取当前用户信息"""
+ return current_user
+
+
+@router.get("/", response_model=List[UserSchema])
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+ """获取用户列表(示例端点)"""
+ statement = select(User).offset(skip).limit(limit)
+ users = db.exec(statement).all()
+ return users
+
diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py
new file mode 100644
index 0000000..221e79c
--- /dev/null
+++ b/backend/app/core/__init__.py
@@ -0,0 +1,13 @@
+"""
+核心配置模块
+"""
+from .config import settings
+from .security import get_password_hash, verify_password, create_access_token
+
+__all__ = [
+ "settings",
+ "get_password_hash",
+ "verify_password",
+ "create_access_token",
+]
+
diff --git a/backend/app/core/security.py b/backend/app/core/security.py
new file mode 100644
index 0000000..c20e77d
--- /dev/null
+++ b/backend/app/core/security.py
@@ -0,0 +1,44 @@
+"""
+安全相关功能:密码哈希、JWT 令牌等
+"""
+from datetime import datetime, timedelta
+from typing import Optional
+from jose import JWTError, jwt
+from passlib.context import CryptContext
+from .config import settings
+
+# 密码加密上下文
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+
+def get_password_hash(password: str) -> str:
+ """生成密码哈希"""
+ return pwd_context.hash(password)
+
+
+def verify_password(plain_password: str, hashed_password: str) -> bool:
+ """验证密码"""
+ return pwd_context.verify(plain_password, hashed_password)
+
+
+def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
+ """创建 JWT 访问令牌"""
+ to_encode = data.copy()
+ if expires_delta:
+ expire = datetime.utcnow() + expires_delta
+ else:
+ expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
+
+ to_encode.update({"exp": expire})
+ encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
+ return encoded_jwt
+
+
+def decode_access_token(token: str) -> Optional[dict]:
+ """解码 JWT 令牌"""
+ try:
+ payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
+ return payload
+ except JWTError:
+ return None
+
diff --git a/backend/app/db/__init__.py b/backend/app/db/__init__.py
new file mode 100644
index 0000000..5733b0a
--- /dev/null
+++ b/backend/app/db/__init__.py
@@ -0,0 +1,16 @@
+"""
+数据库模块
+"""
+from .base import Base
+from .session import engine, SessionLocal, get_db
+from .init_db import init_db, reset_db
+
+__all__ = [
+ "Base",
+ "engine",
+ "SessionLocal",
+ "get_db",
+ "init_db",
+ "reset_db",
+]
+
diff --git a/backend/app/db/init_db.py b/backend/app/db/init_db.py
new file mode 100644
index 0000000..6fe19a5
--- /dev/null
+++ b/backend/app/db/init_db.py
@@ -0,0 +1,71 @@
+"""
+数据库初始化脚本
+运行此脚本创建数据库表结构
+
+用法:
+ python -m app.db.init_db # 创建表(如果表已存在则跳过)
+ python -m app.db.init_db --reset # 删除所有表后重新创建(⚠️ 会丢失数据)
+"""
+import sys
+import argparse
+from sqlmodel import SQLModel
+
+from app.db.session import engine
+from app.db.base import Base # 这会导入所有模型
+
+# 导入所有模型以确保表被注册
+from app.models import (
+ User, Todo, Post, Transaction, Media, Tag, MediaTag,
+ ChatMessage, Upload
+)
+
+
+def init_db() -> None:
+ """初始化数据库,创建所有表"""
+ SQLModel.metadata.create_all(engine)
+ print("✅ 数据库表创建完成")
+
+
+def reset_db() -> None:
+ """重置数据库:删除所有表后重新创建(⚠️ 会丢失所有数据)"""
+ print("⚠️ 警告:将删除所有表和数据!")
+ SQLModel.metadata.drop_all(engine)
+ print("✅ 已删除所有表")
+ SQLModel.metadata.create_all(engine)
+ print("✅ 已重新创建所有表")
+
+
+def main() -> None:
+ """初始化数据库"""
+ parser = argparse.ArgumentParser(description="初始化数据库")
+ parser.add_argument(
+ "--reset",
+ action="store_true",
+ help="删除所有表后重新创建(⚠️ 会丢失所有数据)"
+ )
+ args = parser.parse_args()
+
+ if args.reset:
+ print("🔄 重置模式:将删除所有表后重新创建...")
+ reset_db()
+ else:
+ print("📦 创建模式:创建不存在的表...")
+ init_db()
+
+ print(f"\n✅ 数据库表操作完成!")
+ print(f"数据库文件位置: {engine.url}")
+
+ # 显示创建的表
+ print("\n数据库中的表:")
+ tables = [
+ "users", "todos", "posts", "transactions",
+ "media", "tags", "media_tags",
+ "chat_messages", "uploads"
+ ]
+ for table in tables:
+ print(f" - {table}")
+
+
+if __name__ == "__main__":
+ main()
+
diff --git a/backend/app/db/session.py b/backend/app/db/session.py
new file mode 100644
index 0000000..d7221d5
--- /dev/null
+++ b/backend/app/db/session.py
@@ -0,0 +1,26 @@
+"""
+数据库会话管理
+"""
+from sqlalchemy.engine import Engine
+from sqlalchemy import create_engine
+from sqlmodel import Session
+from typing import Generator
+from app.core.config import settings
+
+# 创建数据库引擎
+engine: Engine = create_engine(
+ settings.DATABASE_URL,
+ connect_args={"check_same_thread": False}, # SQLite 需要此参数
+ echo=True # 开发环境显示SQL语句,生产环境设为False
+)
+
+
+def get_db() -> Generator[Session, None, None]:
+ """获取数据库会话(用于依赖注入)"""
+ with Session(engine) as session:
+ yield session
+
+
+# 为了向后兼容,保留 SessionLocal 别名
+SessionLocal = Session
+
diff --git a/backend/app/initial_data/README.md b/backend/app/initial_data/README.md
new file mode 100644
index 0000000..d83eb22
--- /dev/null
+++ b/backend/app/initial_data/README.md
@@ -0,0 +1,21 @@
+# 初始数据模块
+
+此目录用于存放数据库初始化时的示例数据脚本。
+
+## 使用说明
+
+在数据库初始化后,可以运行此模块中的脚本来插入示例数据,方便开发和测试。
+
+## 示例
+
+```python
+from app.db.session import SessionLocal
+from app.models.user import User
+from app.core.security import get_password_hash
+
+def init_data():
+ db = SessionLocal()
+ # 创建示例用户等
+ ...
+```
+
diff --git a/backend/app/initial_data/__init__.py b/backend/app/initial_data/__init__.py
new file mode 100644
index 0000000..49e7982
--- /dev/null
+++ b/backend/app/initial_data/__init__.py
@@ -0,0 +1,5 @@
+"""
+初始数据模块
+用于数据库初始化时插入示例数据
+"""
+
diff --git a/backend/app/main.py b/backend/app/main.py
new file mode 100644
index 0000000..7d65256
--- /dev/null
+++ b/backend/app/main.py
@@ -0,0 +1,45 @@
+"""
+FastAPI 应用主入口
+"""
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+from app.core.config import settings
+from app.api.api_v1.api import api_router
+
+# 创建 FastAPI 应用实例
+app = FastAPI(
+ title=settings.PROJECT_NAME,
+ version=settings.VERSION,
+ openapi_url=f"{settings.API_V1_STR}/openapi.json",
+)
+
+# 配置 CORS
+if settings.BACKEND_CORS_ORIGINS:
+ app.add_middleware(
+ CORSMiddleware,
+ allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+ )
+
+# 注册 API 路由
+app.include_router(api_router, prefix=settings.API_V1_STR)
+
+
+@app.get("/")
+def root():
+ """根路径"""
+ return {
+ "message": "欢迎使用个人博客网站 API",
+ "version": settings.VERSION,
+ "docs": "/docs",
+ "api": settings.API_V1_STR
+ }
+
+
+@app.get("/health")
+def health_check():
+ """健康检查"""
+ return {"status": "ok"}
+
diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py
new file mode 100644
index 0000000..cad8a64
--- /dev/null
+++ b/backend/app/models/__init__.py
@@ -0,0 +1,23 @@
+"""
+数据库模型模块
+"""
+from .user import User
+from .todo import Todo
+from .post import Post
+from .transaction import Transaction
+from .media import Media, Tag, MediaTag
+from .chat import ChatMessage
+from .upload import Upload
+
+__all__ = [
+ "User",
+ "Todo",
+ "Post",
+ "Transaction",
+ "Media",
+ "Tag",
+ "MediaTag",
+ "ChatMessage",
+ "Upload",
+]
+
diff --git a/backend/app/models/media.py b/backend/app/models/media.py
new file mode 100644
index 0000000..4400754
--- /dev/null
+++ b/backend/app/models/media.py
@@ -0,0 +1,40 @@
+"""
+书影音收藏模型
+"""
+from datetime import datetime
+from typing import Optional, TYPE_CHECKING
+from sqlmodel import SQLModel, Field, Relationship
+from .user import User
+
+if TYPE_CHECKING:
+ from .tag import Tag
+else:
+ Tag = "Tag"
+
+
+class MediaTag(SQLModel, table=True):
+ """媒体-标签关联表"""
+ __tablename__ = "media_tags"
+
+ media_id: int = Field(foreign_key="media.id", primary_key=True)
+ tag_id: int = Field(foreign_key="tags.id", primary_key=True)
+
+
+class Media(SQLModel, table=True):
+ """书影音收藏表"""
+ __tablename__ = "media"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ title: str = Field(max_length=200)
+ media_type: str = Field(max_length=20) # book / movie / music
+ rating: Optional[float] = Field(default=None, ge=0.0, le=5.0)
+ comment: Optional[str] = Field(default=None)
+ external_id: Optional[str] = Field(default=None, max_length=100) # ISBN、IMDb ID等
+ cover_url: Optional[str] = Field(default=None, max_length=300)
+ created_at: datetime = Field(default_factory=datetime.now)
+ user_id: int = Field(foreign_key="users.id", index=True)
+
+ # 关系
+ user: Optional[User] = Relationship()
+ tags: list["Tag"] = Relationship(back_populates="media", link_model="MediaTag")
+
diff --git a/backend/app/models/post.py b/backend/app/models/post.py
new file mode 100644
index 0000000..d3c81f6
--- /dev/null
+++ b/backend/app/models/post.py
@@ -0,0 +1,24 @@
+"""
+博客文章模型
+"""
+from datetime import datetime
+from typing import Optional
+from sqlmodel import SQLModel, Field, Relationship
+from .user import User
+
+
+class Post(SQLModel, table=True):
+ """博客文章表"""
+ __tablename__ = "posts"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ title: str = Field(max_length=200)
+ slug: str = Field(max_length=200, index=True)
+ content: str # 支持 Markdown
+ created_at: datetime = Field(default_factory=datetime.now)
+ updated_at: Optional[datetime] = Field(default=None)
+ user_id: int = Field(foreign_key="users.id", index=True)
+
+ # 关系
+ user: Optional[User] = Relationship()
+
diff --git a/backend/app/models/tag.py b/backend/app/models/tag.py
new file mode 100644
index 0000000..31903ac
--- /dev/null
+++ b/backend/app/models/tag.py
@@ -0,0 +1,23 @@
+"""
+媒体标签模型
+"""
+from typing import Optional, TYPE_CHECKING
+from sqlmodel import SQLModel, Field, Relationship
+
+if TYPE_CHECKING:
+ from .media import Media, MediaTag
+else:
+ Media = "Media"
+ MediaTag = "MediaTag"
+
+
+class Tag(SQLModel, table=True):
+ """媒体标签表"""
+ __tablename__ = "tags"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ name: str = Field(max_length=50, unique=True, index=True)
+
+ # 关系
+ media: list["Media"] = Relationship(back_populates="tags", link_model="MediaTag")
+
diff --git a/backend/app/models/todo.py b/backend/app/models/todo.py
new file mode 100644
index 0000000..ec8ceea
--- /dev/null
+++ b/backend/app/models/todo.py
@@ -0,0 +1,22 @@
+"""
+待办事项模型
+"""
+from datetime import datetime
+from typing import Optional
+from sqlmodel import SQLModel, Field, Relationship
+from .user import User
+
+
+class Todo(SQLModel, table=True):
+ """待办事项表"""
+ __tablename__ = "todos"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ title: str = Field(max_length=200)
+ done: bool = Field(default=False)
+ created_at: datetime = Field(default_factory=datetime.now)
+ user_id: int = Field(foreign_key="users.id", index=True)
+
+ # 关系(可选,用于ORM查询)
+ user: Optional[User] = Relationship()
+
diff --git a/backend/app/models/transaction.py b/backend/app/models/transaction.py
new file mode 100644
index 0000000..2409b48
--- /dev/null
+++ b/backend/app/models/transaction.py
@@ -0,0 +1,25 @@
+"""
+记账记录模型
+"""
+from datetime import date
+from typing import Optional
+from decimal import Decimal
+from sqlmodel import SQLModel, Field, Relationship, Column
+from sqlalchemy import Numeric
+from .user import User
+
+
+class Transaction(SQLModel, table=True):
+ """记账记录表"""
+ __tablename__ = "transactions"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ amount: Decimal = Field(sa_column=Column(Numeric(10, 2))) # 正数为收入,负数为支出
+ category: str = Field(max_length=50)
+ description: Optional[str] = Field(default=None, max_length=200)
+ date: date
+ user_id: int = Field(foreign_key="users.id", index=True)
+
+ # 关系
+ user: Optional[User] = Relationship()
+
diff --git a/backend/app/models/upload.py b/backend/app/models/upload.py
new file mode 100644
index 0000000..3c80266
--- /dev/null
+++ b/backend/app/models/upload.py
@@ -0,0 +1,25 @@
+"""
+文件上传模型
+"""
+from datetime import datetime
+from typing import Optional
+from sqlmodel import SQLModel, Field, Relationship
+from .user import User
+
+
+class Upload(SQLModel, table=True):
+ """文件上传记录表"""
+ __tablename__ = "uploads"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ filename: str = Field(max_length=200)
+ stored_path: str = Field(max_length=300)
+ file_size: int # 字节数
+ mime_type: str = Field(max_length=100)
+ uploaded_at: datetime = Field(default_factory=datetime.now)
+ expires_at: Optional[datetime] = Field(default=None)
+ user_id: int = Field(foreign_key="users.id", index=True)
+
+ # 关系
+ user: Optional[User] = Relationship()
+
diff --git a/backend/app/models/user.py b/backend/app/models/user.py
new file mode 100644
index 0000000..a096bb0
--- /dev/null
+++ b/backend/app/models/user.py
@@ -0,0 +1,18 @@
+"""
+用户模型
+"""
+from datetime import datetime
+from typing import Optional
+from sqlmodel import SQLModel, Field
+
+
+class User(SQLModel, table=True):
+ """用户表"""
+ __tablename__ = "users"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ username: str = Field(max_length=50, unique=True, index=True)
+ email: Optional[str] = Field(default=None, max_length=100, unique=True, index=True)
+ hashed_password: str
+ created_at: datetime = Field(default_factory=datetime.now)
+
diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py
new file mode 100644
index 0000000..4840629
--- /dev/null
+++ b/backend/app/schemas/__init__.py
@@ -0,0 +1,5 @@
+"""
+Pydantic 模式定义
+用于 API 请求和响应的数据验证
+"""
+
diff --git a/backend/app/schemas/post.py b/backend/app/schemas/post.py
new file mode 100644
index 0000000..e2a322c
--- /dev/null
+++ b/backend/app/schemas/post.py
@@ -0,0 +1,42 @@
+"""
+博客文章相关的 Pydantic 模式
+"""
+from datetime import datetime
+from typing import Optional
+from pydantic import BaseModel
+
+
+class PostBase(BaseModel):
+ """博客文章基础模式"""
+ title: str
+ slug: str
+ content: str
+
+
+class PostCreate(PostBase):
+ """创建博客文章请求模式"""
+ pass
+
+
+class PostUpdate(BaseModel):
+ """更新博客文章请求模式"""
+ title: Optional[str] = None
+ slug: Optional[str] = None
+ content: Optional[str] = None
+
+
+class PostInDB(PostBase):
+ """数据库中的博客文章模式"""
+ id: int
+ created_at: datetime
+ updated_at: Optional[datetime] = None
+ user_id: int
+
+ class Config:
+ from_attributes = True
+
+
+class Post(PostInDB):
+ """博客文章响应模式"""
+ pass
+
diff --git a/backend/app/schemas/todo.py b/backend/app/schemas/todo.py
new file mode 100644
index 0000000..4ad83f5
--- /dev/null
+++ b/backend/app/schemas/todo.py
@@ -0,0 +1,39 @@
+"""
+待办事项相关的 Pydantic 模式
+"""
+from datetime import datetime
+from typing import Optional
+from pydantic import BaseModel
+
+
+class TodoBase(BaseModel):
+ """待办事项基础模式"""
+ title: str
+
+
+class TodoCreate(TodoBase):
+ """创建待办事项请求模式"""
+ pass
+
+
+class TodoUpdate(BaseModel):
+ """更新待办事项请求模式"""
+ title: Optional[str] = None
+ done: Optional[bool] = None
+
+
+class TodoInDB(TodoBase):
+ """数据库中的待办事项模式"""
+ id: int
+ done: bool
+ created_at: datetime
+ user_id: int
+
+ class Config:
+ from_attributes = True
+
+
+class Todo(TodoInDB):
+ """待办事项响应模式"""
+ pass
+
diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py
new file mode 100644
index 0000000..122404a
--- /dev/null
+++ b/backend/app/schemas/user.py
@@ -0,0 +1,50 @@
+"""
+用户相关的 Pydantic 模式
+"""
+from datetime import datetime
+from typing import Optional
+from pydantic import BaseModel, EmailStr
+
+
+class UserBase(BaseModel):
+ """用户基础模式"""
+ username: str
+ email: Optional[EmailStr] = None
+
+
+class UserCreate(UserBase):
+ """创建用户请求模式"""
+ password: str
+
+
+class UserUpdate(BaseModel):
+ """更新用户请求模式"""
+ username: Optional[str] = None
+ email: Optional[EmailStr] = None
+ password: Optional[str] = None
+
+
+class UserInDB(UserBase):
+ """数据库中的用户模式"""
+ id: int
+ created_at: datetime
+
+ class Config:
+ from_attributes = True
+
+
+class User(UserInDB):
+ """用户响应模式(不包含敏感信息)"""
+ pass
+
+
+class Token(BaseModel):
+ """JWT 令牌响应模式"""
+ access_token: str
+ token_type: str = "bearer"
+
+
+class TokenData(BaseModel):
+ """令牌数据模式"""
+ username: Optional[str] = None
+
diff --git a/backend/models.py b/backend/models.py
new file mode 100644
index 0000000..af4dfbc
--- /dev/null
+++ b/backend/models.py
@@ -0,0 +1,144 @@
+"""
+数据库模型定义
+使用 SQLModel 定义所有表结构
+"""
+from datetime import datetime, date
+from typing import Optional
+from sqlmodel import SQLModel, Field, Relationship, Column
+from sqlalchemy import Numeric
+from decimal import Decimal
+
+
+# ==================== 用户表 ====================
+class User(SQLModel, table=True):
+ """用户表"""
+ __tablename__ = "users"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ username: str = Field(max_length=50, unique=True, index=True)
+ email: Optional[str] = Field(default=None, max_length=100, unique=True, index=True)
+ hashed_password: str
+ created_at: datetime = Field(default_factory=datetime.now)
+
+
+# ==================== 待办事项表 ====================
+class Todo(SQLModel, table=True):
+ """待办事项表"""
+ __tablename__ = "todos"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ title: str = Field(max_length=200)
+ done: bool = Field(default=False)
+ created_at: datetime = Field(default_factory=datetime.now)
+ user_id: int = Field(foreign_key="users.id", index=True)
+
+ # 关系(可选,用于ORM查询)
+ user: Optional[User] = Relationship()
+
+
+# ==================== 博客文章表 ====================
+class Post(SQLModel, table=True):
+ """博客文章表"""
+ __tablename__ = "posts"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ title: str = Field(max_length=200)
+ slug: str = Field(max_length=200, index=True)
+ content: str # 支持 Markdown
+ created_at: datetime = Field(default_factory=datetime.now)
+ updated_at: Optional[datetime] = Field(default=None)
+ user_id: int = Field(foreign_key="users.id", index=True)
+
+ # 关系
+ user: Optional[User] = Relationship()
+
+
+# ==================== 记账记录表 ====================
+class Transaction(SQLModel, table=True):
+ """记账记录表"""
+ __tablename__ = "transactions"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ amount: Decimal = Field(sa_column=Column(Numeric(10, 2))) # 正数为收入,负数为支出
+ category: str = Field(max_length=50)
+ description: Optional[str] = Field(default=None, max_length=200)
+ date: date
+ user_id: int = Field(foreign_key="users.id", index=True)
+
+ # 关系
+ user: Optional[User] = Relationship()
+
+
+# ==================== 媒体-标签关联表(多对多)====================
+class MediaTag(SQLModel, table=True):
+ """媒体-标签关联表"""
+ __tablename__ = "media_tags"
+
+ media_id: int = Field(foreign_key="media.id", primary_key=True)
+ tag_id: int = Field(foreign_key="tags.id", primary_key=True)
+
+
+# ==================== 书影音收藏表 ====================
+class Media(SQLModel, table=True):
+ """书影音收藏表"""
+ __tablename__ = "media"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ title: str = Field(max_length=200)
+ media_type: str = Field(max_length=20) # book / movie / music
+ rating: Optional[float] = Field(default=None, ge=0.0, le=5.0)
+ comment: Optional[str] = Field(default=None)
+ external_id: Optional[str] = Field(default=None, max_length=100) # ISBN、IMDb ID等
+ cover_url: Optional[str] = Field(default=None, max_length=300)
+ created_at: datetime = Field(default_factory=datetime.now)
+ user_id: int = Field(foreign_key="users.id", index=True)
+
+ # 关系
+ user: Optional[User] = Relationship()
+ tags: list["Tag"] = Relationship(back_populates="media", link_model=MediaTag)
+
+
+# ==================== 媒体标签表 ====================
+class Tag(SQLModel, table=True):
+ """媒体标签表"""
+ __tablename__ = "tags"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ name: str = Field(max_length=50, unique=True, index=True)
+
+ # 关系
+ media: list[Media] = Relationship(back_populates="tags", link_model=MediaTag)
+
+
+# ==================== 聊天消息表 ====================
+class ChatMessage(SQLModel, table=True):
+ """聊天消息表"""
+ __tablename__ = "chat_messages"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ content: str
+ sent_at: datetime = Field(default_factory=datetime.now)
+ user_id: int = Field(foreign_key="users.id", index=True)
+ room: str = Field(max_length=50, default="main", index=True)
+
+ # 关系
+ user: Optional[User] = Relationship()
+
+
+# ==================== 文件上传记录表 ====================
+class Upload(SQLModel, table=True):
+ """文件上传记录表"""
+ __tablename__ = "uploads"
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ filename: str = Field(max_length=200)
+ stored_path: str = Field(max_length=300)
+ file_size: int # 字节数
+ mime_type: str = Field(max_length=100)
+ uploaded_at: datetime = Field(default_factory=datetime.now)
+ expires_at: Optional[datetime] = Field(default=None)
+ user_id: int = Field(foreign_key="users.id", index=True)
+
+ # 关系
+ user: Optional[User] = Relationship()
+
diff --git a/frontend/src/views/Todos.vue b/frontend/src/views/Todos.vue
new file mode 100644
index 0000000..496be15
--- /dev/null
+++ b/frontend/src/views/Todos.vue
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
diff --git a/main.py b/main.py
index eb389a0..eaf3154 100644
--- a/main.py
+++ b/main.py
@@ -1,16 +1,21 @@
-# 这是一个示例 Python 脚本。
+"""
+项目启动入口
+用于开发环境快速启动应用
+"""
+import sys
+import os
-# 按 Shift+F10 执行或将其替换为您的代码。
-# 按 双击 Shift 在所有地方搜索类、文件、工具窗口、操作和设置。
+# 添加 backend 目录到 Python 路径
+backend_dir = os.path.join(os.path.dirname(__file__), "backend")
+if backend_dir not in sys.path:
+ sys.path.insert(0, backend_dir)
+import uvicorn
-def print_hi(name):
- # 在下面的代码行中使用断点来调试脚本。
- print(f'Hi, {name}') # 按 Ctrl+F8 切换断点。
-
-
-# 按装订区域中的绿色按钮以运行脚本。
-if __name__ == '__main__':
- print_hi('PyCharm')
-
-# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助
+if __name__ == "__main__":
+ uvicorn.run(
+ "app.main:app",
+ host="0.0.0.0",
+ port=8000,
+ reload=True, # 开发环境自动重载
+ )
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..80b9688
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,24 @@
+# FastAPI 核心
+fastapi>=0.104.0
+uvicorn[standard]>=0.24.0
+
+# 数据库
+sqlmodel>=0.0.14
+sqlalchemy>=2.0.0
+
+# 数据验证
+pydantic>=2.0.0
+pydantic-settings>=2.0.0
+email-validator>=2.0.0
+
+# 安全认证
+python-jose[cryptography]>=3.3.0
+passlib[bcrypt]>=1.7.4
+python-multipart>=0.0.6
+
+# HTTP 客户端(用于调用第三方 API)
+httpx>=0.25.0
+
+# 其他工具
+python-dotenv>=1.0.0
+
diff --git a/start_backend.bat b/start_backend.bat
new file mode 100644
index 0000000..97371b0
--- /dev/null
+++ b/start_backend.bat
@@ -0,0 +1,4 @@
+@echo off
+echo 启动后端服务...
+python main.py
+
diff --git a/start_backend.sh b/start_backend.sh
new file mode 100644
index 0000000..05e9e6b
--- /dev/null
+++ b/start_backend.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+echo "启动后端服务..."
+python main.py
+
diff --git a/start_frontend.bat b/start_frontend.bat
new file mode 100644
index 0000000..728a4db
--- /dev/null
+++ b/start_frontend.bat
@@ -0,0 +1,5 @@
+@echo off
+echo 启动前端服务...
+cd frontend
+npm run dev
+
diff --git a/start_frontend.sh b/start_frontend.sh
new file mode 100644
index 0000000..9d8c654
--- /dev/null
+++ b/start_frontend.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+echo "启动前端服务..."
+cd frontend
+npm run dev
+
diff --git a/数据库设计说明.md b/数据库设计说明.md
new file mode 100644
index 0000000..ed40688
--- /dev/null
+++ b/数据库设计说明.md
@@ -0,0 +1,155 @@
+# 🗃️ 项目数据库设计说明
+
+本文档描述了本 FastAPI 练手网站的数据库实体(表)结构与关系设计。
+所有用户相关数据均通过 `user_id` 外键关联,实现**多用户数据隔离**。
+数据库使用 **SQLite(开发阶段)**,ORM 框架推荐 **SQLModel**(兼容 Pydantic + SQLAlchemy)。
+
+> 💡 **注**:天气查询功能调用第三方 API,**不持久化存储**,故无对应表。
+
+---
+
+## 📚 实体列表
+
+| 表名 | 用途说明 |
+|------------------|------------------------------|
+| `users` | 用户账户信息 |
+| `todos` | 待办事项列表 |
+| `posts` | 博客文章 |
+| `transactions` | 个人记账记录 |
+| `media` | 书影音收藏条目 |
+| `tags` | 媒体标签(用于分类/打标) |
+| `media_tags` | `media` 与 `tags` 的多对多关联表 |
+| `chat_messages` | 聊天室消息记录 |
+| `uploads` | 用户上传的文件元数据 |
+
+---
+
+## 🔍 详细表结构
+
+### 1. `users` — 用户表
+
+| 字段名 | 类型 | 约束/说明 |
+|-------------------|----------------|-------------------------------|
+| `id` | INTEGER | 主键 (PK) |
+| `username` | VARCHAR(50) | 唯一,非空 |
+<|`email`|VARCHAR(100)|唯一,可为空|
+| `hashed_password` | TEXT | bcrypt 哈希后的密码 |
+| `created_at` | DATETIME | 默认当前时间 |
+
+---
+
+### 2. `todos` — 待办事项
+
+| 字段名 | 类型 | 约束/说明 |
+|--------------|-------------|--------------------------|
+| `id` | INTEGER | PK |
+| `title` | VARCHAR(200)| 非空 |
+| `done` | BOOLEAN | 默认 `FALSE` |
+| `created_at` | DATETIME | 默认当前时间 |
+| `user_id` | INTEGER | 外键 → `users.id` |
+
+---
+
+### 3. `posts` — 博客文章
+
+| 字段名 | 类型 | 约束/说明 |
+|---------------|-------------|----------------------------------------|
+| `id` | INTEGER | PK |
+| `title` | VARCHAR(200)| 非空 |
+| `slug` | VARCHAR(200)| URL 友好标识,建议 `(user_id, slug)` 唯一 |
+| `content` | TEXT | 支持 Markdown |
+| `created_at` | DATETIME | 默认当前时间 |
+| `updated_at` | DATETIME | 可为空 |
+| `user_id` | INTEGER | 外键 → `users.id` |
+
+---
+
+### 4. `transactions` — 记账记录
+
+| 字段名 | 类型 | 约束/说明 |
+|---------------|----------------|----------------------------------|
+| `id` | INTEGER | PK |
+| `amount` | NUMERIC(10,2) | 正数为收入,负数为支出 |
+| `category` | VARCHAR(50) | 如“餐饮”、“交通”、“工资”等 |
+| `description` | VARCHAR(200) | 可为空 |
+| `date` | DATE | 交易发生日期 |
+| `user_id` | INTEGER | 外键 → `users.id` |
+
+---
+
+### 5. `media` — 书影音收藏
+
+| 字段名 | 类型 | 约束/说明 |
+|----------------|-------------|------------------------------------------------|
+| `id` | INTEGER | PK |
+| `title` | VARCHAR(200)| 非空 |
+| `media_type` | VARCHAR(20) | 枚举值:`book` / `movie` / `music` |
+| `rating` | FLOAT | 0.0 ~ 5.0 |
+| `comment` | TEXT | 用户短评 |
+| `external_id` | VARCHAR(100)| 如 ISBN、IMDb ID(用于对接外部 API) |
+| `cover_url` | VARCHAR(300)| 封面图片 URL(可缓存) |
+| `created_at` | DATETIME | 默认当前时间 |
+| `user_id` | INTEGER | 外键 → `users.id` |
+
+---
+
+### 6. `tags` — 媒体标签
+
+| 字段名 | 类型 | 约束/说明 |
+|----------|-------------|---------------|
+| `id` | INTEGER | PK |
+| `name` | VARCHAR(50) | 唯一,非空 |
+
+---
+
+### 7. `media_tags` — 媒体-标签关联表(多对多)
+
+| 字段名 | 类型 | 约束/说明 |
+|-------------|----------|-----------------------------|
+| `media_id` | INTEGER | 外键 → `media.id` |
+| `tag_id` | INTEGER | 外键 → `tags.id` |
+| (联合主键)| — | `(media_id, tag_id)` 唯一 |
+
+---
+
+### 8. `chat_messages` — 聊天消息
+
+| 字段名 | 类型 | 约束/说明 |
+|-------------|----------|-----------------------------------|
+| `id` | INTEGER | PK |
+| `content` | TEXT | 非空 |
+| `sent_at` | DATETIME | 默认当前时间 |
+| `user_id` | INTEGER | 外键 → `users.id`(发送者) |
+| `room` | VARCHAR(50)| 聊天室名称(如 `"main"`) |
+
+---
+
+### 9. `uploads` — 文件上传记录
+
+| 字段名 | 类型 | 约束/说明 |
+|----------------|-------------|--------------------------------------------|
+| `id` | INTEGER | PK |
+| `filename` | VARCHAR(200)| 原始文件名 |
+| `stored_path` | VARCHAR(300)| 服务器存储路径(如 `/uploads/abc.jpg`) |
+| `file_size` | INTEGER | 字节数 |
+| `mime_type` | VARCHAR(100)| 如 `image/jpeg`, `application/pdf` |
+| `uploaded_at` | DATETIME | 默认当前时间 |
+| `expires_at` | DATETIME | 可为空(表示永不过期) |
+| `user_id` | INTEGER | 外键 → `users.id` |
+
+> ⚠️ **安全提示**:前端不应直接访问 `stored_path`,应通过受控路由(如 `/download/{id}`)提供下载,并验证用户权限。
+
+---
+
+## 🔗 实体关系图(ERD)
+
+```mermaid
+erDiagram
+ users ||--o{ todos : "1:N"
+ users ||--o{ posts : "1:N"
+ users ||--o{ transactions : "1:N"
+ users ||--o{ media : "1:N"
+ users ||--o{ chat_messages : "1:N"
+ users ||--o{ uploads : "1:N"
+
+ media }o--o{ tags : "N:M via media_tags"
\ No newline at end of file