feat: 添加MySQL数据库支持和完整的数据库管理功能
- 添加MySQL数据库连接配置 - 创建SQLAlchemy数据库模型(用户表和产品表) - 添加Alembic数据库迁移工具 - 创建数据库管理脚本(database_manager.py) - 添加SQL文件目录结构(migrations, seeds, schemas) - 更新应用配置以支持数据库连接 - 添加完整的数据库设置文档 - 配置开发环境数据库连接(localhost:3306, root/123456, fast_demo)
This commit is contained in:
parent
04cb2cbbf7
commit
1f2bb12178
|
|
@ -0,0 +1,86 @@
|
|||
# CODEBUDDY.md
|
||||
|
||||
此文件为 CodeBuddy Code 在此代码库中工作时提供指导。
|
||||
|
||||
## 常用命令
|
||||
|
||||
### 应用启动
|
||||
```bash
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 使用便捷启动脚本(推荐)
|
||||
python start.py --env dev # 开发环境
|
||||
python start.py --env prod # 生产环境
|
||||
python start.py --port 9000 # 指定端口
|
||||
|
||||
# 直接运行主文件
|
||||
python main.py
|
||||
```
|
||||
|
||||
### 环境配置
|
||||
```bash
|
||||
# 环境变量优先级(从高到低):
|
||||
# 1. 环境变量
|
||||
# 2. .env.{environment} 文件
|
||||
# 3. .env 基础配置文件
|
||||
# 4. 代码中的默认值
|
||||
|
||||
# 配置验证
|
||||
python -c "from app.core.config import settings; print(f'Environment: {settings.ENVIRONMENT}'); print(f'Debug: {settings.DEBUG}')"
|
||||
```
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 应用工厂模式
|
||||
项目采用应用工厂模式,核心文件:
|
||||
- **应用创建**:`app/core/application.py` - 使用 `create_application()` 工厂函数
|
||||
- **配置管理**:`app/core/config.py` - 基于 pydantic-settings 的多环境配置
|
||||
- **路由聚合**:`app/api/v1/router.py` - 集中管理 API 路由注册
|
||||
|
||||
### 分层架构
|
||||
1. **API层** (`app/api/v1/endpoints/`) - FastAPI 路由端点,处理 HTTP 请求/响应
|
||||
2. **服务层** (`app/services/`) - 业务逻辑封装,使用单例服务实例
|
||||
3. **模型层** (`app/models/`) - 核心领域模型,Pydantic BaseModel
|
||||
4. **Schema层** (`app/schemas/`) - 请求/响应验证和序列化
|
||||
|
||||
### 路由结构
|
||||
- 所有 API 以 `/api/v1/` 为前缀
|
||||
- 路由通过 `app/api/v1/router.py` 统一注册
|
||||
- 每个资源对应一个独立的路由器文件
|
||||
|
||||
### 配置管理
|
||||
- 支持多环境:development、testing、production
|
||||
- 自动加载对应配置文件:`.env.dev.{env}`
|
||||
- 类型安全验证和转换
|
||||
- 便利属性:`settings.is_development`、`settings.is_production`
|
||||
|
||||
### 服务模式
|
||||
- 使用静态服务类 (`UserService`、`ProductService`)
|
||||
- 当前使用内存数据库进行演示
|
||||
- 服务层负责所有 CRUD 操作和业务规则
|
||||
|
||||
### 关键特性
|
||||
- CORS 中间件配置
|
||||
- 应用生命周期事件处理器
|
||||
- 模块化路由设计
|
||||
- 版本化的 API 结构
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 添加新资源
|
||||
1. 模型层:在 `app/models/` 创建数据模型
|
||||
2. Schema层:在 `app/schemas/` 创建 Base/Create/Update/Response 模式
|
||||
3. 服务层:在 `app/services/` 创建服务类
|
||||
4. API层:在 `app/api/v1/endpoints/` 创建路由端点
|
||||
5. 路由注册:在 `app/api/v1/router.py` 注册新路由
|
||||
|
||||
### 配置扩展
|
||||
- 新配置项添加到 `app/core/config.py` 中的 Settings 类
|
||||
- 遵循现有配置验证模式
|
||||
- 支持环境变量覆盖
|
||||
|
||||
### 数据存储
|
||||
- 当前使用内存数据存储用于演示
|
||||
- 实际实现将在服务层集成数据库
|
||||
- 服务接口设计支持无缝替换存储后端
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
# 数据库设置指南
|
||||
|
||||
本项目已配置支持MySQL数据库,包含完整的数据库模型、迁移脚本和管理工具。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
sql/
|
||||
├── migrations/ # 数据库迁移文件
|
||||
│ └── 001_create_tables.sql
|
||||
├── seeds/ # 种子数据文件
|
||||
│ └── sample_data.sql
|
||||
└── schemas/ # 数据库架构文件
|
||||
└── init_database.sql
|
||||
|
||||
alembic/ # Alembic迁移工具
|
||||
├── versions/ # 自动生成的迁移版本
|
||||
├── env.py # Alembic环境配置
|
||||
└── script.py.mako # 迁移模板
|
||||
|
||||
app/database/ # 数据库相关代码
|
||||
├── __init__.py
|
||||
├── connection.py # 数据库连接管理
|
||||
├── base.py # 基础模型类
|
||||
└── models.py # SQLAlchemy模型
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. 配置数据库
|
||||
|
||||
创建 `.env.dev` 文件(参考 `.env.example`):
|
||||
|
||||
```env
|
||||
# MySQL 数据库配置
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=your_password
|
||||
DB_NAME=fastapi_demo
|
||||
DB_CHARSET=utf8mb4
|
||||
```
|
||||
|
||||
### 3. 初始化数据库
|
||||
|
||||
使用数据库管理脚本:
|
||||
|
||||
```bash
|
||||
# 初始化数据库(创建数据库和表,插入示例数据)
|
||||
python database_manager.py init
|
||||
|
||||
# 或者分步执行
|
||||
python database_manager.py create-db # 创建数据库
|
||||
python database_manager.py create-tables # 创建表
|
||||
python database_manager.py seed # 插入示例数据
|
||||
```
|
||||
|
||||
### 4. 启动应用
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
## 数据库管理
|
||||
|
||||
### 使用数据库管理脚本
|
||||
|
||||
```bash
|
||||
python database_manager.py init # 初始化数据库
|
||||
python database_manager.py reset # 重置数据库
|
||||
python database_manager.py create-db # 仅创建数据库
|
||||
python database_manager.py create-tables # 仅创建表
|
||||
python database_manager.py seed # 仅插入示例数据
|
||||
python database_manager.py help # 显示帮助
|
||||
```
|
||||
|
||||
### 使用Alembic进行迁移
|
||||
|
||||
```bash
|
||||
# 初始化Alembic(首次使用)
|
||||
alembic init alembic
|
||||
|
||||
# 创建新的迁移
|
||||
alembic revision --autogenerate -m "描述信息"
|
||||
|
||||
# 执行迁移
|
||||
alembic upgrade head
|
||||
|
||||
# 回滚迁移
|
||||
alembic downgrade -1
|
||||
|
||||
# 查看迁移历史
|
||||
alembic history
|
||||
|
||||
# 查看当前版本
|
||||
alembic current
|
||||
```
|
||||
|
||||
## 数据库模型
|
||||
|
||||
### 用户表 (users)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int(11) | 主键,自增 |
|
||||
| username | varchar(50) | 用户名,唯一 |
|
||||
| email | varchar(100) | 邮箱,唯一 |
|
||||
| full_name | varchar(100) | 全名,可空 |
|
||||
| is_active | tinyint(1) | 是否激活 |
|
||||
| created_at | datetime | 创建时间 |
|
||||
| updated_at | datetime | 更新时间 |
|
||||
|
||||
### 产品表 (products)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int(11) | 主键,自增 |
|
||||
| name | varchar(200) | 产品名称 |
|
||||
| description | text | 产品描述,可空 |
|
||||
| price | decimal(10,2) | 价格 |
|
||||
| stock | int(11) | 库存数量 |
|
||||
| is_available | tinyint(1) | 是否可用 |
|
||||
| created_at | datetime | 创建时间 |
|
||||
| updated_at | datetime | 更新时间 |
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 数据库连接配置
|
||||
|
||||
在 `app/core/config.py` 中配置:
|
||||
|
||||
```python
|
||||
# MySQL 数据库配置
|
||||
DB_HOST: str = "localhost" # 数据库主机
|
||||
DB_PORT: int = 3306 # 数据库端口
|
||||
DB_USER: str = "root" # 数据库用户名
|
||||
DB_PASSWORD: str = "" # 数据库密码
|
||||
DB_NAME: str = "fastapi_demo" # 数据库名称
|
||||
DB_CHARSET: str = "utf8mb4" # 字符集
|
||||
|
||||
# 数据库连接池配置
|
||||
DB_POOL_SIZE: int = 5 # 连接池大小
|
||||
DB_MAX_OVERFLOW: int = 10 # 最大溢出连接数
|
||||
DB_POOL_TIMEOUT: int = 30 # 连接超时时间(秒)
|
||||
DB_POOL_RECYCLE: int = 3600 # 连接回收时间(秒)
|
||||
```
|
||||
|
||||
### 环境变量
|
||||
|
||||
支持通过环境变量覆盖配置:
|
||||
|
||||
```bash
|
||||
export DB_HOST=localhost
|
||||
export DB_PORT=3306
|
||||
export DB_USER=root
|
||||
export DB_PASSWORD=your_password
|
||||
export DB_NAME=fastapi_demo
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **连接失败**
|
||||
- 检查MySQL服务是否启动
|
||||
- 验证数据库配置信息
|
||||
- 确认用户权限
|
||||
|
||||
2. **字符编码问题**
|
||||
- 确保数据库使用utf8mb4字符集
|
||||
- 检查连接字符串中的charset参数
|
||||
|
||||
3. **迁移失败**
|
||||
- 检查Alembic配置
|
||||
- 确认数据库连接正常
|
||||
- 查看迁移文件语法
|
||||
|
||||
### 日志调试
|
||||
|
||||
启用SQL日志:
|
||||
|
||||
```env
|
||||
DB_ECHO=true
|
||||
LOG_LEVEL=debug
|
||||
```
|
||||
|
||||
## 开发建议
|
||||
|
||||
1. **使用迁移管理数据库结构变更**
|
||||
2. **在开发环境中使用示例数据**
|
||||
3. **定期备份生产数据库**
|
||||
4. **使用连接池优化性能**
|
||||
5. **监控数据库连接状态**
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
# 开发环境配置总结
|
||||
|
||||
## 🎯 数据库配置
|
||||
|
||||
### 连接信息
|
||||
- **主机**: localhost
|
||||
- **端口**: 3306
|
||||
- **用户名**: root
|
||||
- **密码**: 123456
|
||||
- **数据库名**: fast_demo
|
||||
- **字符集**: utf8mb4
|
||||
|
||||
### 连接URL
|
||||
```
|
||||
mysql+pymysql://root:123456@localhost:3306/fast_demo?charset=utf8mb4
|
||||
```
|
||||
|
||||
## 📁 配置文件
|
||||
|
||||
### 1. 应用配置 (`app/core/config.py`)
|
||||
已更新默认数据库配置:
|
||||
```python
|
||||
DB_HOST: str = "localhost"
|
||||
DB_PORT: int = 3306
|
||||
DB_USER: str = "root"
|
||||
DB_PASSWORD: str = "123456"
|
||||
DB_NAME: str = "fast_demo"
|
||||
DB_CHARSET: str = "utf8mb4"
|
||||
```
|
||||
|
||||
### 2. Alembic配置 (`alembic.ini`)
|
||||
已更新数据库URL:
|
||||
```ini
|
||||
sqlalchemy.url = mysql+pymysql://root:123456@localhost:3306/fast_demo?charset=utf8mb4
|
||||
```
|
||||
|
||||
## 🗄️ 数据库表
|
||||
|
||||
### 用户表 (users)
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int(11) | 主键,自增 |
|
||||
| username | varchar(50) | 用户名,唯一 |
|
||||
| email | varchar(100) | 邮箱,唯一 |
|
||||
| full_name | varchar(100) | 全名,可空 |
|
||||
| is_active | tinyint(1) | 是否激活 |
|
||||
| created_at | datetime | 创建时间 |
|
||||
| updated_at | datetime | 更新时间 |
|
||||
|
||||
### 产品表 (products)
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int(11) | 主键,自增 |
|
||||
| name | varchar(200) | 产品名称 |
|
||||
| description | text | 产品描述,可空 |
|
||||
| price | decimal(10,2) | 价格 |
|
||||
| stock | int(11) | 库存数量 |
|
||||
| is_available | tinyint(1) | 是否可用 |
|
||||
| created_at | datetime | 创建时间 |
|
||||
| updated_at | datetime | 更新时间 |
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 启动应用
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
### 2. 访问API文档
|
||||
- Swagger UI: http://127.0.0.1:8000/docs
|
||||
- ReDoc: http://127.0.0.1:8000/redoc
|
||||
|
||||
### 3. 数据库管理
|
||||
```bash
|
||||
# 初始化数据库
|
||||
python database_manager.py init
|
||||
|
||||
# 重置数据库
|
||||
python database_manager.py reset
|
||||
|
||||
# 查看帮助
|
||||
python database_manager.py help
|
||||
```
|
||||
|
||||
## 🔧 环境变量
|
||||
|
||||
可以通过环境变量覆盖配置:
|
||||
|
||||
```bash
|
||||
export DB_HOST=localhost
|
||||
export DB_PORT=3306
|
||||
export DB_USER=root
|
||||
export DB_PASSWORD=123456
|
||||
export DB_NAME=fast_demo
|
||||
```
|
||||
|
||||
## ✅ 验证配置
|
||||
|
||||
数据库连接已成功验证:
|
||||
- ✅ 数据库 'fast_demo' 已存在
|
||||
- ✅ 数据库连接成功
|
||||
- ✅ 表 'users' 和 'products' 已创建
|
||||
- ✅ 示例数据已插入
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
1. 确保MySQL服务正在运行
|
||||
2. 确保用户 'root' 有访问数据库的权限
|
||||
3. 开发环境配置已优化,包含热重载等功能
|
||||
4. 生产环境请修改默认密码和配置
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = alembic
|
||||
|
||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||
# Uncomment the line below if you want the files to be prepended with date and time
|
||||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory.
|
||||
prepend_sys_path = .
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python-dateutil library that can be
|
||||
# installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to dateutil.tz.gettz()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the
|
||||
# "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version number format
|
||||
version_num_format = %04d
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses
|
||||
# os.pathsep. If this key is omitted entirely, it falls back to the legacy
|
||||
# behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
version_path_separator = os
|
||||
|
||||
# set to 'true' to search source files recursively
|
||||
# in each "version_locations" directory
|
||||
# new in Alembic version 1.10
|
||||
# recursive_version_locations = false
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = mysql+pymysql://root:123456@localhost:3306/fast_demo?charset=utf8mb4
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
||||
# hooks = ruff
|
||||
# ruff.type = exec
|
||||
# ruff.executable = %(here)s/.venv/bin/ruff
|
||||
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
"""
|
||||
Alembic 环境配置
|
||||
"""
|
||||
from logging.config import fileConfig
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
from alembic import context
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 添加项目根目录到 Python 路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app.database.models import Base
|
||||
from app.core.config import settings
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
target_metadata = Base.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def get_url():
|
||||
"""获取数据库连接URL"""
|
||||
return settings.DATABASE_URL
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = get_url()
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
configuration = config.get_section(config.config_ini_section)
|
||||
configuration["sqlalchemy.url"] = get_url()
|
||||
|
||||
connectable = engine_from_config(
|
||||
configuration,
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection, target_metadata=target_metadata
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
|
|
@ -96,6 +96,12 @@ def register_events(app: FastAPI) -> None:
|
|||
# 可以在这里添加启动时的初始化操作
|
||||
# 如:数据库连接、缓存初始化等
|
||||
|
||||
# 创建数据库表(如果不存在)
|
||||
from app.database.connection import engine
|
||||
from app.database.models import Base
|
||||
Base.metadata.create_all(bind=engine)
|
||||
print("[INFO] Database tables created/verified successfully")
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
"""应用关闭时执行"""
|
||||
|
|
|
|||
|
|
@ -30,10 +30,25 @@ class Settings(BaseSettings):
|
|||
REDOC_URL: Optional[str] = "/redoc"
|
||||
|
||||
# === 数据库配置 ===
|
||||
DATABASE_URL: Optional[str] = None
|
||||
# MySQL 数据库配置
|
||||
DB_HOST: str = "localhost"
|
||||
DB_PORT: int = 3306
|
||||
DB_USER: str = "root"
|
||||
DB_PASSWORD: str = "123456"
|
||||
DB_NAME: str = "fast_demo"
|
||||
DB_CHARSET: str = "utf8mb4"
|
||||
|
||||
# 数据库连接池配置
|
||||
DB_ECHO: bool = False
|
||||
DB_POOL_SIZE: int = 5
|
||||
DB_MAX_OVERFLOW: int = 10
|
||||
DB_POOL_TIMEOUT: int = 30
|
||||
DB_POOL_RECYCLE: int = 3600
|
||||
|
||||
@property
|
||||
def DATABASE_URL(self) -> str:
|
||||
"""构建数据库连接URL"""
|
||||
return f"mysql+pymysql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}?charset={self.DB_CHARSET}"
|
||||
|
||||
# === Redis 配置 ===
|
||||
REDIS_HOST: str = "localhost"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
数据库模块
|
||||
"""
|
||||
from .connection import engine, SessionLocal, get_db
|
||||
from .base import Base
|
||||
|
||||
__all__ = ["engine", "SessionLocal", "get_db", "Base"]
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
"""
|
||||
数据库基础模型类
|
||||
"""
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
数据库连接管理
|
||||
"""
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
# 创建数据库引擎
|
||||
engine = create_engine(
|
||||
settings.DATABASE_URL,
|
||||
echo=settings.DB_ECHO,
|
||||
pool_size=settings.DB_POOL_SIZE,
|
||||
max_overflow=settings.DB_MAX_OVERFLOW,
|
||||
pool_timeout=settings.DB_POOL_TIMEOUT,
|
||||
pool_recycle=settings.DB_POOL_RECYCLE,
|
||||
pool_pre_ping=True, # 连接前测试连接是否有效
|
||||
)
|
||||
|
||||
# 创建会话工厂
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# 创建基础模型类
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def get_db():
|
||||
"""
|
||||
获取数据库会话的依赖注入函数
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
"""
|
||||
SQLAlchemy 数据库模型
|
||||
"""
|
||||
from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, Text
|
||||
from sqlalchemy.sql import func
|
||||
from .base import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""用户表"""
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
username = Column(String(50), unique=True, index=True, nullable=False, comment="用户名")
|
||||
email = Column(String(100), unique=True, index=True, nullable=False, comment="邮箱")
|
||||
full_name = Column(String(100), nullable=True, comment="全名")
|
||||
is_active = Column(Boolean, default=True, nullable=False, comment="是否激活")
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now(), comment="创建时间")
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), comment="更新时间")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<User(id={self.id}, username='{self.username}', email='{self.email}')>"
|
||||
|
||||
|
||||
class Product(Base):
|
||||
"""产品表"""
|
||||
__tablename__ = "products"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
||||
name = Column(String(200), nullable=False, comment="产品名称")
|
||||
description = Column(Text, nullable=True, comment="产品描述")
|
||||
price = Column(Float, nullable=False, comment="价格")
|
||||
stock = Column(Integer, default=0, nullable=False, comment="库存数量")
|
||||
is_available = Column(Boolean, default=True, nullable=False, comment="是否可用")
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now(), comment="创建时间")
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), comment="更新时间")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Product(id={self.id}, name='{self.name}', price={self.price})>"
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
"""
|
||||
数据库管理脚本
|
||||
用于初始化数据库、运行迁移、插入测试数据等
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
from sqlalchemy import create_engine, text
|
||||
from app.core.config import settings
|
||||
from app.database.models import Base
|
||||
|
||||
|
||||
def create_database():
|
||||
"""创建数据库(如果不存在)"""
|
||||
# 连接到MySQL服务器(不指定数据库)
|
||||
server_url = f"mysql+pymysql://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:{settings.DB_PORT}/"
|
||||
engine = create_engine(server_url)
|
||||
|
||||
with engine.connect() as conn:
|
||||
# 创建数据库
|
||||
conn.execute(text(f"CREATE DATABASE IF NOT EXISTS `{settings.DB_NAME}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"))
|
||||
print(f"✅ 数据库 '{settings.DB_NAME}' 创建成功")
|
||||
|
||||
|
||||
def create_tables():
|
||||
"""创建数据库表"""
|
||||
engine = create_engine(settings.DATABASE_URL)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
print("✅ 数据库表创建成功")
|
||||
|
||||
|
||||
def run_sql_file(file_path):
|
||||
"""运行SQL文件"""
|
||||
if not os.path.exists(file_path):
|
||||
print(f"❌ SQL文件不存在: {file_path}")
|
||||
return
|
||||
|
||||
engine = create_engine(settings.DATABASE_URL)
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
sql_content = f.read()
|
||||
|
||||
with engine.connect() as conn:
|
||||
# 分割SQL语句并执行
|
||||
statements = [stmt.strip() for stmt in sql_content.split(';') if stmt.strip()]
|
||||
for statement in statements:
|
||||
if statement:
|
||||
conn.execute(text(statement))
|
||||
conn.commit()
|
||||
|
||||
print(f"✅ SQL文件执行成功: {file_path}")
|
||||
|
||||
|
||||
def init_database():
|
||||
"""初始化数据库"""
|
||||
print("🚀 开始初始化数据库...")
|
||||
|
||||
# 1. 创建数据库
|
||||
create_database()
|
||||
|
||||
# 2. 创建表
|
||||
create_tables()
|
||||
|
||||
# 3. 插入示例数据
|
||||
sample_data_file = "sql/seeds/sample_data.sql"
|
||||
if os.path.exists(sample_data_file):
|
||||
run_sql_file(sample_data_file)
|
||||
|
||||
print("🎉 数据库初始化完成!")
|
||||
|
||||
|
||||
def reset_database():
|
||||
"""重置数据库"""
|
||||
print("⚠️ 重置数据库...")
|
||||
|
||||
# 删除并重新创建数据库
|
||||
server_url = f"mysql+pymysql://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:{settings.DB_PORT}/"
|
||||
engine = create_engine(server_url)
|
||||
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text(f"DROP DATABASE IF EXISTS `{settings.DB_NAME}`"))
|
||||
print(f"🗑️ 数据库 '{settings.DB_NAME}' 已删除")
|
||||
|
||||
# 重新初始化
|
||||
init_database()
|
||||
|
||||
|
||||
def show_help():
|
||||
"""显示帮助信息"""
|
||||
print("""
|
||||
数据库管理脚本使用说明:
|
||||
|
||||
python database_manager.py init - 初始化数据库(创建数据库和表)
|
||||
python database_manager.py reset - 重置数据库(删除并重新创建)
|
||||
python database_manager.py create-db - 仅创建数据库
|
||||
python database_manager.py create-tables - 仅创建表
|
||||
python database_manager.py seed - 仅插入示例数据
|
||||
python database_manager.py help - 显示此帮助信息
|
||||
|
||||
环境变量配置:
|
||||
- DB_HOST: 数据库主机 (默认: localhost)
|
||||
- DB_PORT: 数据库端口 (默认: 3306)
|
||||
- DB_USER: 数据库用户名 (默认: root)
|
||||
- DB_PASSWORD: 数据库密码 (默认: 空)
|
||||
- DB_NAME: 数据库名称 (默认: fastapi_demo)
|
||||
""")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
if len(sys.argv) < 2:
|
||||
show_help()
|
||||
return
|
||||
|
||||
command = sys.argv[1].lower()
|
||||
|
||||
try:
|
||||
if command == "init":
|
||||
init_database()
|
||||
elif command == "reset":
|
||||
reset_database()
|
||||
elif command == "create-db":
|
||||
create_database()
|
||||
elif command == "create-tables":
|
||||
create_tables()
|
||||
elif command == "seed":
|
||||
run_sql_file("sql/seeds/sample_data.sql")
|
||||
elif command == "help":
|
||||
show_help()
|
||||
else:
|
||||
print(f"❌ 未知命令: {command}")
|
||||
show_help()
|
||||
except Exception as e:
|
||||
print(f"❌ 执行失败: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -4,3 +4,9 @@ pydantic==2.8.0
|
|||
pydantic-settings==2.1.0
|
||||
python-multipart==0.0.6
|
||||
email-validator==2.1.0
|
||||
|
||||
# 数据库相关依赖
|
||||
sqlalchemy>=2.0.30
|
||||
alembic>=1.13.1
|
||||
pymysql>=1.1.0
|
||||
cryptography>=41.0.0
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
-- 迁移文件:创建基础表结构
|
||||
-- 创建时间:2024-01-01
|
||||
-- 描述:创建用户表和产品表
|
||||
|
||||
-- 创建用户表
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL COMMENT '用户名',
|
||||
`email` varchar(100) NOT NULL COMMENT '邮箱',
|
||||
`full_name` varchar(100) DEFAULT NULL COMMENT '全名',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否激活',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`),
|
||||
UNIQUE KEY `email` (`email`),
|
||||
KEY `idx_username` (`username`),
|
||||
KEY `idx_email` (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
|
||||
|
||||
-- 创建产品表
|
||||
CREATE TABLE IF NOT EXISTS `products` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(200) NOT NULL COMMENT '产品名称',
|
||||
`description` text COMMENT '产品描述',
|
||||
`price` decimal(10,2) NOT NULL COMMENT '价格',
|
||||
`stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量',
|
||||
`is_available` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否可用',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_name` (`name`),
|
||||
KEY `idx_price` (`price`),
|
||||
KEY `idx_is_available` (`is_available`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品表';
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
-- 创建数据库
|
||||
CREATE DATABASE IF NOT EXISTS `fastapi_demo`
|
||||
DEFAULT CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- 使用数据库
|
||||
USE `fastapi_demo`;
|
||||
|
||||
-- 创建用户表
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL COMMENT '用户名',
|
||||
`email` varchar(100) NOT NULL COMMENT '邮箱',
|
||||
`full_name` varchar(100) DEFAULT NULL COMMENT '全名',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否激活',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`),
|
||||
UNIQUE KEY `email` (`email`),
|
||||
KEY `idx_username` (`username`),
|
||||
KEY `idx_email` (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
|
||||
|
||||
-- 创建产品表
|
||||
CREATE TABLE IF NOT EXISTS `products` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(200) NOT NULL COMMENT '产品名称',
|
||||
`description` text COMMENT '产品描述',
|
||||
`price` decimal(10,2) NOT NULL COMMENT '价格',
|
||||
`stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量',
|
||||
`is_available` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否可用',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_name` (`name`),
|
||||
KEY `idx_price` (`price`),
|
||||
KEY `idx_is_available` (`is_available`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品表';
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
-- 插入示例用户数据
|
||||
INSERT INTO `users` (`username`, `email`, `full_name`, `is_active`) VALUES
|
||||
('admin', 'admin@example.com', '管理员', 1),
|
||||
('john_doe', 'john@example.com', 'John Doe', 1),
|
||||
('jane_smith', 'jane@example.com', 'Jane Smith', 1),
|
||||
('bob_wilson', 'bob@example.com', 'Bob Wilson', 0);
|
||||
|
||||
-- 插入示例产品数据
|
||||
INSERT INTO `products` (`name`, `description`, `price`, `stock`, `is_available`) VALUES
|
||||
('iPhone 15 Pro', '苹果最新旗舰手机,配备A17 Pro芯片', 9999.00, 50, 1),
|
||||
('MacBook Pro 16"', '专业级笔记本电脑,M3 Max芯片', 19999.00, 20, 1),
|
||||
('AirPods Pro', '主动降噪无线耳机', 1999.00, 100, 1),
|
||||
('iPad Air', '轻薄便携的平板电脑', 4399.00, 30, 1),
|
||||
('Apple Watch Series 9', '智能手表,健康监测', 2999.00, 80, 1),
|
||||
('Magic Keyboard', '无线键盘,支持多设备连接', 999.00, 0, 0);
|
||||
Loading…
Reference in New Issue