feat: 初始化FastAPI项目基础框架

添加项目基础结构,包括:
- 核心模块(src/main.py)
- 路由模块(users/items)
- 数据库配置和模型
- 日志工具
- 测试用例
- 项目文档和依赖配置
This commit is contained in:
2025-12-15 11:34:24 +08:00
commit 3a5cc50d02
20 changed files with 1157 additions and 0 deletions

11
.env.example Normal file
View File

@@ -0,0 +1,11 @@
# Database configuration
DATABASE_URL=sqlite:///./test.db
# API configuration
API_HOST=localhost
API_PORT=8000
# Security
SECRET_KEY=your-secret-key-here
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30

234
.gitignore vendored Normal file
View File

@@ -0,0 +1,234 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak
venv.bak
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Project specific
test.db
# Additions for better Python project management
# Environment variables file
.env.local
.env.*.local
# IDE and editor files
.vscode/settings.json
.vscode/tasks.json
.vscode/launch.json
.idea/
*.sublime-project
*.sublime-workspace
# Operating System files
.DS_Store*
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Icon?
Thumbs.db
# Logs
*.log
logs/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Dependency directories
node_modules/
# Distribution / packaging
.py[cod]
*.so
.Python
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
*.manifest
*.spec
# Coverage reports
htmlcov/
.coverage
.coverage.*
.cache
# Jupyter Notebook
.ipynb_checkpoints
# Database
*.sqlite3
*.db
*.dump
# Temporary files
*.tmp
*.temp
~*
*~
# Compiled files
*.com
*.class
*.dll
*.exe
*.o
*.so
# Packages
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip

171
README.md Normal file
View File

@@ -0,0 +1,171 @@
# FastAPI项目模板 及 快速开发范式
## 1\. 核心原则
* **简洁至上**:避免过度设计。优先选择简单直接的方案。
* **一致性**:团队成员遵循相同的规范,降低沟通和维护成本。
* **自动化**:利用工具自动完成格式化、检查等重复性工作。
-----
## 2\. 虚拟环境与依赖管理
**目标**:隔离项目依赖,保证环境一致性。
**规范**
* **工具**:使用 Python 内置的 `venv` 模块。
* **创建虚拟环境**:在项目根目录执行 `python -m venv venv``.gitignore` 文件应包含 `venv/` 目录。
* **激活环境**
* macOS/Linux: `source venv/bin/activate`
* Windows: `.\venv\Scripts\activate`
* **依赖管理**
* 使用 `pip``requirements.txt` 文件。
* **安装依赖**`pip install -r requirements.txt`
* **更新依赖文件**:完成开发或添加新包后,运行 `pip freeze > requirements.txt` 更新依赖列表。或手动修改依赖列表。
* **(可选) 开发依赖**:可以创建一个 `requirements-dev.txt` 文件,存放仅在开发时使用的工具(如 `pytest`, `black`),并将其 `include` 到主 `requirements.txt` 中或分开安装。
-----
## 3\. 项目结构
**目标**:清晰、模块化,便于快速定位代码。
推荐一个可扩展的结构:
```
your_project_name/
├── src/                      # 主要应用代码
  ├── __init__.py
  ├── main.py               # FastAPI 应用实例、全局配置、根路由
  ├── routers/              # 业务逻辑路由
   ├── __init__.py
   ├── users.py          # 用户相关的路由
   └── items.py          # 物品相关的路由
  ├── services/             # 业务逻辑层 (当业务复杂时,将复杂逻辑拆分到此处)
   ├── __init__.py
   ├── users.py
   └── items.py
  ├── models/               # 数据模型 (如 Pydantic 模型)
   ├── __init__.py
   └── user.py           # 用户数据模型
  ├── db/                   # 数据库相关配置与连接
   ├── __init__.py
   └── database.py
  └── utils/                # 常用工具函数
      ├── __init__.py
      └── tools.py
├── tests/                    # 测试代码
  ├── __init__.py
  └── test_users.py         # 用户模块的测试
├── .gitignore                # Git 忽略文件
├── README.md                 # 项目说明文档
├── requirements.txt          # 项目依赖列表
├── Dockerfile                # Docker 配置文件
├── .env.example              # 环境变量文件模板
└── .env                      # 环境变量文件 (不提交到Git)
```
**说明**:
* `main.py`: 只做最核心的初始化和包含各个 `router`
* `.env`: 存放环境变量,如数据库连接信息、密钥等。
* `.env.example`: 示例环境变量文件,用于创建 `.env` 文件。
* `Dockerfile`: Dockerfile 文件,用于构建镜像。
* `requirements.txt`: 项目依赖文件,用于安装依赖。
* `README.md`: 项目说明文档。
* `routers`: 路由模块,存放各个接口的实现。
* `models`: 模型模块,存放数据库模型定义。
* `utils`: 工具模块,存放工具函数。
-----
#### 4\. 命名规范
**目标**:遵循 PEP 8保持代码风格统一。
* **文件和目录**:使用小写蛇形命名法 (snake\_case),例如 `user_router.py`
* **变量和函数**:使用小写蛇形命名法,例如 `get_user_by_id`, `db_session`
* **类和 Pydantic 模型**:使用大驼峰命名法 (PascalCase),例如 `UserCreate`, `ItemSchema`
* **常量**:使用全大写蛇形命名法,例如 `DATABASE_URL`, `API_PREFIX`
* **FastAPI 路由函数**:建议以 `动作_资源` 的方式命名,例如 `read_users`, `create_item`
-----
#### 5\. 编码习惯与最佳实践
* **代码格式化**
* **强制使用 `black`**。`black` 是一个无妥协的代码格式化工具,能消除所有关于格式的争论。
* **使用 `isort`** 对 `import` 语句进行排序。
* 在 CI/CD 或 Git pre-commit hook 中加入自动格式化检查。
* **代码质量检查 (Linter)**
* 使用 `flake8` 或更现代的 `ruff` 进行代码风格和潜在错误的检查。
* **FastAPI 实践**
* **善用依赖注入 (`Depends`)**:将数据库会话、用户认证等逻辑通过 `Depends` 注入到路由函数中,实现逻辑复用和解耦。
* **当router逻辑繁重时考虑分离功能**如果路由逻辑过于复杂考虑将复杂逻辑提取到单独的函数中放置在service层并调用。
* **异步优先**:对于 I/O 密集型操作(如数据库查询、外部 API 请求),始终使用 `async def``await`
* **统一日志输出**:使用 uvcrion 的 `from uvicorn.server import logger` 来捕获特定异常,并返回统一格式的错误响应。例子:`logger.error(f"Error: {e}")`
* **配置管理**:使用 os.getenv("ENV_VAR_NAME", "default_value") 从配置文件和环境变量中加载配置。
* **类型提示 (Type Hinting)**
* **强制要求**:为所有函数参数和返回值添加类型提示。这是 FastAPI 的核心特性之一,能提供强大的编辑器支持和自动文档生成。
-----
#### 6\. 版本控制 (Git)
* **分支模型**
* `main` (或 `master`) 分支:用于部署的稳定版本。**只接受**来自 `develop` 的合并。
* `develop` 分支:用于集成各个功能开发的分支。
* `faet/xxx` 分支:从 `develop` 创建,用于开发新功能。开发完成后合并回 `develop`
* `fix/xxx` 分支:用于修复 `main` 分支上的紧急 bug。
* `dev/xxx` 分支:从 `develop` 创建,用于开发新功能。开发完成后合并回 `develop`
* **Commit Message**
* **约定格式**:推荐使用 "Conventional Commits" 规范。
* `feat: add new user registration endpoint` (新功能)
* `fix: correct password hashing algorithm` (修复 Bug)
* `docs: update README with setup instructions` (文档)
* `style: format code with black` (格式化)
* `refactor: restructure database session handling` (重构)
* `test: add tests for user creation` (测试)
* 这样做的好处是提交历史清晰,便于追溯和自动生成更新日志。
-----
#### 7\. 测试
* **框架**:使用 `pytest`
* **工具**:结合 FastAPI 的 `TestClient``HTTPX` 来发送模拟请求。
* **要求**:核心业务逻辑和重要的 API 端点必须有单元测试或集成测试覆盖。
* **数据库**:测试时应使用一个独立的测试数据库,并在每次测试前后清理数据。
-----
### 总结
这份范式旨在为小型 FastAPI 团队提供一个简单、易于上手的起点。最重要的不是规则本身,而是**团队达成共识并持续遵守**。随着项目和团队的发展,可以随时对这些规范进行调整和补充。
**工具链建议**:
* 虚拟环境: `venv`
* 依赖管理: `pip` + `requirements.txt`
* 代码格式化: `black`, `isort`
* 代码检查: `ruff` (推荐) 或 `flake8`
* 测试: `pytest`
* 版本控制: `Git`

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
python-dotenv==1.0.0
pydantic==2.5.0
pydantic-settings==2.1.0

0
src/__init__.py Normal file
View File

0
src/db/__init__.py Normal file
View File

22
src/db/database.py Normal file
View File

@@ -0,0 +1,22 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
from dotenv import load_dotenv
load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./test.db")
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

58
src/db/models.py Normal file
View File

@@ -0,0 +1,58 @@
"""
数据库模型模块
该模块定义了 SQLAlchemy ORM 模型,用于与数据库进行交互。
当前包含日志表的模型定义。
"""
from sqlalchemy import Column, String, Text, DateTime, BIGINT
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
# 创建基类,所有模型都需要继承此类
Base = declarative_base()
class Log(Base):
"""
系统日志数据库模型
该模型定义了日志表的结构,用于存储系统的各种日志信息,
包括普通日志和异常日志。
"""
__tablename__ = "logs" # 数据库表名
# 日志ID主键自动递增
id = Column(BIGINT, primary_key=True, autoincrement=True, comment="日志ID主键")
# 日志时间戳默认为当前UTC时间
timestamp = Column(DateTime, default=datetime.utcnow, nullable=False, comment="日志时间戳")
# 日志级别 (INFO, WARNING, ERROR, DEBUG, CRITICAL)
level = Column(String(20), nullable=False, comment="日志级别 (INFO, WARNING, ERROR, DEBUG, CRITICAL)")
# 产生日志的模块名
module = Column(String(100), nullable=False, comment="产生日志的模块")
# 产生日志的函数名(可选)
function = Column(String(100), nullable=True, comment="产生日志的函数")
# 日志消息内容
message = Column(Text, nullable=False, comment="日志消息")
# 错误堆栈信息(可选,主要用于异常日志)
traceback = Column(Text, nullable=True, comment="错误堆栈信息")
# 请求URL可选用于记录HTTP请求相关信息
request_url = Column(String(500), nullable=True, comment="请求URL")
# 请求方法(可选,如 GET, POST 等)
request_method = Column(String(10), nullable=True, comment="请求方法 (GET, POST等)")
# 用户代理信息(可选)
user_agent = Column(String(500), nullable=True, comment="用户代理")
# IP地址可选
ip_address = Column(String(45), nullable=True, comment="IP地址")
# 关联的用户ID可选
user_id = Column(BIGINT, nullable=True, comment="关联的用户ID")

59
src/main.py Normal file
View File

@@ -0,0 +1,59 @@
"""
FastAPI 应用主入口文件
该文件负责初始化 FastAPI 应用实例,配置中间件,
注册路由以及定义根路径和健康检查端点。
"""
import sys
import os
# 将项目根目录添加到 Python 路径中,确保可以正确导入项目模块
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from src.routers import users, items
# 初始化 FastAPI 应用实例
app = FastAPI(
title="规范FastApi 开发基础框架",
description="规范的FastApi 开发基础框架",
version="1.0.0"
)
# 配置 CORS 中间件,允许所有来源、凭证、方法和头部
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
allow_origin_regex=None,
)
# 注册路由模块
# 用户相关路由,前缀为 /users标签为 users
app.include_router(users.router, prefix="/users", tags=["users"])
# 物品相关路由,前缀为 /items标签为 items
app.include_router(items.router, prefix="/items", tags=["items"])
# 根路径端点,返回欢迎信息
@app.get("/")
async def root():
return {"message": "Welcome to 规范FastApi 开发基础框架"}
# 健康检查端点,用于检查应用是否正常运行
@app.get("/health")
async def health_check():
return {"status": "healthy"}
# 当直接运行此文件时启动应用服务器
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"src.main:app",
host="0.0.0.0",
port=8000,
reload=True
)

0
src/models/__init__.py Normal file
View File

36
src/models/item.py Normal file
View File

@@ -0,0 +1,36 @@
"""
物品数据模型模块
该模块定义了物品相关的数据模型,使用 Pydantic 进行数据验证和序列化。
包括基础物品模型、创建物品模型和完整物品模型。
"""
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class ItemBase(BaseModel):
"""
物品基础模型,定义了物品的基本信息字段
"""
name: str # 物品名称,必需字段
description: Optional[str] = None # 物品描述,可选字段
price: float # 物品价格,必需字段
class ItemCreate(ItemBase):
"""
物品创建模型,继承自 ItemBase
当前与 ItemBase 相同,但保留独立的类以便未来扩展
"""
pass
class Item(ItemBase):
"""
完整物品模型,继承自 ItemBase增加了数据库相关字段
"""
id: int # 物品唯一标识符
created_at: datetime = None # 物品创建时间,可选字段
class Config:
# 允许从 ORM 模型转换为 Pydantic 模型
from_attributes = True

35
src/models/user.py Normal file
View File

@@ -0,0 +1,35 @@
"""
用户数据模型模块
该模块定义了用户相关的数据模型,使用 Pydantic 进行数据验证和序列化。
包括基础用户模型、创建用户模型和完整用户模型。
"""
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class UserBase(BaseModel):
"""
用户基础模型,定义了用户的基本信息字段
"""
email: str # 用户邮箱,必需字段
first_name: str # 用户名字,必需字段
last_name: str # 用户姓氏,必需字段
class UserCreate(UserBase):
"""
用户创建模型,继承自 UserBase增加了密码字段
"""
password: str # 用户密码,必需字段
class User(UserBase):
"""
完整用户模型,继承自 UserBase增加了数据库相关字段
"""
id: int # 用户唯一标识符
created_at: datetime = None # 用户创建时间,可选字段
class Config:
# 允许从 ORM 模型转换为 Pydantic 模型
from_attributes = True

0
src/routers/__init__.py Normal file
View File

123
src/routers/items.py Normal file
View File

@@ -0,0 +1,123 @@
"""
物品路由器模块
该模块定义了物品相关的 RESTful API 端点,
包括创建、读取、更新和删除物品等功能。
注意:当前实现使用内存存储,实际应用中应替换为数据库存储。
"""
import sys
import os
# 将项目根目录添加到 Python 路径中,确保可以正确导入项目模块
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from fastapi import APIRouter, HTTPException
from typing import List
from src.models.item import Item, ItemCreate
# 创建 API 路由器实例
router = APIRouter()
# 模拟数据库存储,实际应用中应使用真实数据库
items_db = []
# 创建物品端点
# 接收 ItemCreate 模型数据,返回创建的 Item 对象
@router.post("/", response_model=Item, status_code=201)
async def create_item(item: ItemCreate):
"""
创建新物品
参数:
- item: ItemCreate 模型,包含物品创建所需信息
返回:
- Item: 创建成功的物品对象
"""
# 创建新物品对象并添加到数据库
new_item = Item(id=len(items_db) + 1, **item.dict())
items_db.append(new_item)
return new_item
# 根据物品ID获取物品信息端点
@router.get("/{item_id}", response_model=Item)
async def read_item(item_id: int):
"""
根据物品ID获取物品信息
参数:
- item_id: 物品ID
返回:
- Item: 找到的物品对象
异常:
- HTTPException: 当物品不存在时返回 404 错误
"""
# 查找指定ID的物品
for item in items_db:
if item.id == item_id:
return item
raise HTTPException(status_code=404, detail="Item not found")
# 获取物品列表端点,支持分页
@router.get("/", response_model=List[Item])
async def read_items(skip: int = 0, limit: int = 100):
"""
获取物品列表,支持分页
参数:
- skip: 跳过的记录数,默认为 0
- limit: 返回的记录数,默认为 100
返回:
- List[Item]: 物品对象列表
"""
return items_db[skip : skip + limit]
# 更新物品信息端点
@router.put("/{item_id}", response_model=Item)
async def update_item(item_id: int, item_update: ItemCreate):
"""
更新物品信息
参数:
- item_id: 要更新的物品ID
- item_update: ItemCreate 模型,包含更新后的物品信息
返回:
- Item: 更新后的物品对象
异常:
- HTTPException: 当物品不存在时返回 404 错误
"""
# 查找并更新指定ID的物品
for index, item in enumerate(items_db):
if item.id == item_id:
updated_item = Item(id=item_id, **item_update.dict())
items_db[index] = updated_item
return updated_item
raise HTTPException(status_code=404, detail="Item not found")
# 删除物品端点
@router.delete("/{item_id}", status_code=204)
async def delete_item(item_id: int):
"""
删除物品
参数:
- item_id: 要删除的物品ID
返回:
- 无内容,成功时返回 204 状态码
异常:
- HTTPException: 当物品不存在时返回 404 错误
"""
# 查找并删除指定ID的物品
for index, item in enumerate(items_db):
if item.id == item_id:
items_db.pop(index)
return
raise HTTPException(status_code=404, detail="Item not found")

131
src/routers/users.py Normal file
View File

@@ -0,0 +1,131 @@
"""
用户路由器模块
该模块定义了用户相关的 RESTful API 端点,
包括创建、读取、更新和删除用户等功能。
注意:当前实现使用内存存储,实际应用中应替换为数据库存储。
"""
import sys
import os
# 将项目根目录添加到 Python 路径中,确保可以正确导入项目模块
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from fastapi import APIRouter, HTTPException
from typing import List
from src.models.user import User, UserCreate
# 创建 API 路由器实例
router = APIRouter()
# 模拟数据库存储,实际应用中应使用真实数据库
users_db = []
# 创建用户端点
# 接收 UserCreate 模型数据,返回创建的 User 对象
@router.post("/", response_model=User, status_code=201)
async def create_user(user: UserCreate):
"""
创建新用户
参数:
- user: UserCreate 模型,包含用户创建所需信息
返回:
- User: 创建成功的用户对象
异常:
- HTTPException: 当邮箱已被注册时返回 400 错误
"""
# 检查邮箱是否已存在
for existing_user in users_db:
if existing_user.email == user.email:
raise HTTPException(status_code=400, detail="Email already registered")
# 创建新用户对象并添加到数据库
new_user = User(id=len(users_db) + 1, **user.dict())
users_db.append(new_user)
return new_user
# 根据用户ID获取用户信息端点
@router.get("/{user_id}", response_model=User)
async def read_user(user_id: int):
"""
根据用户ID获取用户信息
参数:
- user_id: 用户ID
返回:
- User: 找到的用户对象
异常:
- HTTPException: 当用户不存在时返回 404 错误
"""
# 查找指定ID的用户
for user in users_db:
if user.id == user_id:
return user
raise HTTPException(status_code=404, detail="User not found")
# 获取用户列表端点,支持分页
@router.get("/", response_model=List[User])
async def read_users(skip: int = 0, limit: int = 100):
"""
获取用户列表,支持分页
参数:
- skip: 跳过的记录数,默认为 0
- limit: 返回的记录数,默认为 100
返回:
- List[User]: 用户对象列表
"""
return users_db[skip : skip + limit]
# 更新用户信息端点
@router.put("/{user_id}", response_model=User)
async def update_user(user_id: int, user_update: UserCreate):
"""
更新用户信息
参数:
- user_id: 要更新的用户ID
- user_update: UserCreate 模型,包含更新后的用户信息
返回:
- User: 更新后的用户对象
异常:
- HTTPException: 当用户不存在时返回 404 错误
"""
# 查找并更新指定ID的用户
for index, user in enumerate(users_db):
if user.id == user_id:
updated_user = User(id=user_id, **user_update.dict())
users_db[index] = updated_user
return updated_user
raise HTTPException(status_code=404, detail="User not found")
# 删除用户端点
@router.delete("/{user_id}", status_code=204)
async def delete_user(user_id: int):
"""
删除用户
参数:
- user_id: 要删除的用户ID
返回:
- 无内容,成功时返回 204 状态码
异常:
- HTTPException: 当用户不存在时返回 404 错误
"""
# 查找并删除指定ID的用户
for index, user in enumerate(users_db):
if user.id == user_id:
users_db.pop(index)
return
raise HTTPException(status_code=404, detail="User not found")

0
src/services/__init__.py Normal file
View File

0
src/utils/__init__.py Normal file
View File

179
src/utils/logger.py Normal file
View File

@@ -0,0 +1,179 @@
"""
日志工具模块
该模块提供了完整的日志记录功能,包括:
1. 控制台日志输出
2. 数据库日志存储
3. 异常信息捕获和记录
4. 不同日志级别的记录函数
日志信息会被同时输出到控制台和存储到数据库中,便于问题排查和系统监控。
"""
import sys
import os
import logging
import traceback
from typing import Optional
from datetime import datetime
# 将项目根目录添加到 Python 路径中,确保可以正确导入项目模块
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from src.db.database import get_db
from src.db.models import Log
# 配置基础日志设置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 创建模块级日志记录器
logger = logging.getLogger(__name__)
def log_to_database(
level: str,
message: str,
module: str,
function: Optional[str] = None,
traceback_info: Optional[str] = None,
request_url: Optional[str] = None,
request_method: Optional[str] = None,
user_agent: Optional[str] = None,
ip_address: Optional[str] = None,
user_id: Optional[int] = None
):
"""
将日志信息保存到数据库
参数:
- level: 日志级别 (INFO, WARNING, ERROR, DEBUG)
- message: 日志消息内容
- module: 产生日志的模块名
- function: 产生日志的函数名(可选)
- traceback_info: 异常堆栈信息(可选)
- request_url: 请求URL可选
- request_method: 请求方法(可选)
- user_agent: 用户代理信息(可选)
- ip_address: IP地址可选
- user_id: 用户ID可选
"""
try:
# 获取数据库会话
db_generator = get_db()
db = next(db_generator)
# 创建日志条目对象
log_entry = Log(
level=level,
message=message,
module=module,
function=function,
traceback=traceback_info,
request_url=request_url,
request_method=request_method,
user_agent=user_agent,
ip_address=ip_address,
user_id=user_id
)
# 保存到数据库
db.add(log_entry)
db.commit()
db.refresh(log_entry)
db.close()
except Exception as e:
# 如果数据库记录失败,至少打印到控制台
logger.error(f"Failed to log to database: {str(e)}")
def capture_exception(
exception: Exception,
module: str,
function: Optional[str] = None,
request_url: Optional[str] = None,
request_method: Optional[str] = None,
user_agent: Optional[str] = None,
ip_address: Optional[str] = None,
user_id: Optional[int] = None
):
"""
捕获并记录异常信息
参数:
- exception: 捕获到的异常对象
- module: 产生异常的模块名
- function: 产生异常的函数名(可选)
- request_url: 请求URL可选
- request_method: 请求方法(可选)
- user_agent: 用户代理信息(可选)
- ip_address: IP地址可选
- user_id: 用户ID可选
"""
# 获取异常堆栈信息
tb_str = ''.join(traceback.format_exception(type(exception), exception, exception.__traceback__))
# 记录错误日志到数据库和控制台
log_to_database(
level="ERROR",
message=str(exception),
module=module,
function=function,
traceback_info=tb_str,
request_url=request_url,
request_method=request_method,
user_agent=user_agent,
ip_address=ip_address,
user_id=user_id
)
# 同时打印到控制台
logger.error(f"[{module}] {str(exception)}", exc_info=True)
def info(message: str, module: str, function: Optional[str] = None):
"""
记录INFO级别日志
参数:
- message: 日志消息内容
- module: 产生日志的模块名
- function: 产生日志的函数名(可选)
"""
logger.info(f"[{module}] {message}")
log_to_database("INFO", message, module, function)
def warning(message: str, module: str, function: Optional[str] = None):
"""
记录WARNING级别日志
参数:
- message: 日志消息内容
- module: 产生日志的模块名
- function: 产生日志的函数名(可选)
"""
logger.warning(f"[{module}] {message}")
log_to_database("WARNING", message, module, function)
def error(message: str, module: str, function: Optional[str] = None):
"""
记录ERROR级别日志
参数:
- message: 日志消息内容
- module: 产生日志的模块名
- function: 产生日志的函数名(可选)
"""
logger.error(f"[{module}] {message}")
log_to_database("ERROR", message, module, function)
def debug(message: str, module: str, function: Optional[str] = None):
"""
记录DEBUG级别日志
参数:
- message: 日志消息内容
- module: 产生日志的模块名
- function: 产生日志的函数名(可选)
"""
logger.debug(f"[{module}] {message}")
log_to_database("DEBUG", message, module, function)

0
tests/__init__.py Normal file
View File

92
tests/test_users.py Normal file
View File

@@ -0,0 +1,92 @@
import pytest
from fastapi.testclient import TestClient
from src.main import app
client = TestClient(app)
def test_create_user():
response = client.post(
"/users/",
json={
"email": "test@example.com",
"first_name": "Test",
"last_name": "User",
"password": "testpassword"
}
)
assert response.status_code == 201
data = response.json()
assert data["email"] == "test@example.com"
assert "id" in data
def test_read_users():
response = client.get("/users/")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
def test_read_user():
# First create a user
response = client.post(
"/users/",
json={
"email": "test2@example.com",
"first_name": "Test2",
"last_name": "User2",
"password": "testpassword"
}
)
assert response.status_code == 201
user_id = response.json()["id"]
# Then read the user
response = client.get(f"/users/{user_id}")
assert response.status_code == 200
data = response.json()
assert data["id"] == user_id
def test_update_user():
# First create a user
response = client.post(
"/users/",
json={
"email": "test3@example.com",
"first_name": "Test3",
"last_name": "User3",
"password": "testpassword"
}
)
assert response.status_code == 201
user_id = response.json()["id"]
# Then update the user
response = client.put(
f"/users/{user_id}",
json={
"email": "updated@example.com",
"first_name": "Updated",
"last_name": "User",
"password": "updatedpassword"
}
)
assert response.status_code == 200
data = response.json()
assert data["email"] == "updated@example.com"
def test_delete_user():
# First create a user
response = client.post(
"/users/",
json={
"email": "test4@example.com",
"first_name": "Test4",
"last_name": "User4",
"password": "testpassword"
}
)
assert response.status_code == 201
user_id = response.json()["id"]
# Then delete the user
response = client.delete(f"/users/{user_id}")
assert response.status_code == 204