问题背景
使用fastapi-scaff脚手架创建项目后,发现三个常见问题:
- 时区配置缺失 默认的时区对于linux mac相对友好 对于Windows会有问题 (其实新增环境变量TZ也可以解决 不过本文会创建新的工具类 担心有其他问题)
TZ环境变量 ✅ 默认设置 ❌ 通常为空 Windows不自动设置
zoneinfo数据 ✅ /usr/share/zoneinfo ❌ 需要tzdata包 Python标准库路径不同
Python zoneinfo模块 ✅ 自动使用系统数据 ❌ 需要tzdata包 Windows下fallback机制
因为:默认脚手架的user模型类创建的时候 引用的是 toollib.utils.now2timestamp,而不是用zoneinfo
主要是三个地方
bash
$ echo $TZ
空(但系统有时区配置)
$ ls /usr/share/zoneinfo/Asia/Shanghai
✅ 文件存在
$ python3 -c "import zoneinfo; print(zoneinfo.ZoneInfo('Asia/Shanghai'))"
✅ 正常输出
- ORM alembic 如果使用Base类不一致 统一使用 DeclBase
- 完善 alembic 配置文件 env.py
完整解决方案
第一步:创建项目
bash
# 1. 安装脚手架
pip install fastapi-scaff==0.5.7
# 2. 创建项目
fastapi-scaff new my_fastapi
# 3. 进入项目目录
cd my_fastapi
fastapi-scaff 常见参数 -h可以直接看
bash
-e, --edition `new`时可指定项目结构版本(默认标准版) 这个用的多一些
-d, --db `new`时可指定项目数据库(默认sqlite)
-v, --vn `add`时可指定版本(默认v1)
-s, --subdir `add`时可指定子目录(默认空)
-t, --target `add`时可指定目标(默认asm)
--celery `new`|`add`时可指定是否集成celery(默认不集成)
examples:
`new`: fastapi-scaff new <myproj>
`add`: fastapi-scaff add <myapi>
第二步:添加缺失依赖
bash
# 添加时区和迁移工具依赖
echo "tzdata>=2021.5" >> requirements.txt
echo "alembic>=1.11.0" >> requirements.txt
# 安装所有依赖
pip install -r requirements.txt
第三步:创建时区工具文件
bash
# 创建统一时区工具
cat > app/utils/mytime.py << 'EOF'
from datetime import datetime
from zoneinfo import ZoneInfo
SHANGHAI_TZ = ZoneInfo("Asia/Shanghai")
def now() -> datetime:
"""返回上海时区的当前时间"""
return datetime.now(SHANGHAI_TZ)
def now_timestamp() -> int:
"""返回当前时间戳(秒)"""
return int(now().timestamp())
def now_ms() -> int:
"""返回当前时间戳(毫秒)"""
return int(now().timestamp() * 1000)
EOF
第四步:修改用户模型
bash
# 备份原文件
cp app/models/user.py app/models/user.py.backup
# 使用sed修改文件
sed -i '/from toollib.utils import now2timestamp/d' app/models/user.py
sed -i '/from app.initializer import g/a\from app.utils import mytime' app/models/user.py
sed -i 's/default=now2timestamp/default=mytime.now_timestamp/g' app/models/user.py
修改后的关键部分:
python
from sqlalchemy import Column, BigInteger, Integer, String
from app.initializer import g
from app.models import DeclBase
from app.utils import mytime # 新增导入
class User(DeclBase):
__tablename__ = "user"
# ... 其他字段保持不变
created_at = Column(BigInteger, default=mytime.now_timestamp, comment="创建时间")
updated_at = Column(BigInteger, default=mytime.now_timestamp, onupdate=mytime.now_timestamp, comment="更新时间")
第五步:初始化Alembic迁移
bash
# 初始化Alembic
alembic init alembic
# 配置env.py使用项目Base类
cat >> alembic/env.py << 'EOF'
# 添加项目路径
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
# 导入Base类
from app.models import DeclBase
target_metadata = DeclBase.metadata
# 设置数据库URL(根据配置文件)
config.set_main_option("sqlalchemy.url", "sqlite:///app_dev.sqlite")
EOF
使用sqlite的完整env.py配置文件:
python
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import os
import sys
config = context.config
# Set database URL directly
config.set_main_option("sqlalchemy.url", "sqlite:///app_dev.sqlite")
# Add project root to Python path
project_root = os.path.dirname(os.path.dirname(__file__))
sys.path.append(project_root)
# Import Base class
try:
from app.models import DeclBase
target_metadata = DeclBase.metadata
except ImportError:
try:
from app.initializer._db import DeclBase
target_metadata = DeclBase.metadata
except ImportError:
from sqlalchemy.ext.declarative import declarative_base
DeclBase = declarative_base()
target_metadata = DeclBase.metadata
if config.config_file_name is not None:
fileConfig(config.config_file_name)
def run_migrations_offline():
url = config.get_main_option("sqlalchemy.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():
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
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()
第六步:执行数据库迁移
bash
# 生成迁移文件
alembic revision --autogenerate -m "init"
# 应用迁移
alembic upgrade head
# 验证表结构
sqlite3 app_dev.sqlite ".tables"
API测试流程
1. 启动服务
bash
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
2. 创建用户
bash
curl -X POST "http://127.0.0.1:8000/api/v1/user" \
-H "Content-Type: application/json" \
-d '{
"phone": "13634759152",
"password": "passwd",
"name": "admin",
"age": 0,
"gender": 1
}'
3. 登录获取Token
bash
curl -X POST "http://127.0.0.1:8000/api/v1/user/login" \
-H "Content-Type: application/json" \
-d '{
"phone": "13634759152",
"password": "passwd"
}'
4. 使用Token查询用户
bash
# 使用上一步返回的token
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjIwMDA5MDc1NjI2MjY1ODg2NzIiLCJwaG9uZSI6IjEzNjM0NzU5MTUyIiwibmFtZSI6ImFkbWluIiwiYWdlIjowLCJnZW5kZXIiOjEsImV4cCI6MTc2ODQ4MTIyOX0.yS_pyuHfR0FuctSsb86zebYVlk8CFK2-ErsQMBvRqaA"
curl -X GET "http://127.0.0.1:8000/api/v1/user/2000907562626588672" \
-H "accept: application/json" \
-H "Authorization: Bearer $TOKEN"
5. 查询用户列表
bash
curl -X GET "http://127.0.0.1:8000/api/v1/user?page=1&size=10" \
-H "accept: application/json" \
-H "Authorization: Bearer $TOKEN"
验证命令
bash
# 验证时区配置
python -c "from app.utils.mytime import now_timestamp; print('当前时间戳:', now_timestamp())"
# 验证数据库迁移
alembic current
# 验证表结构
sqlite3 app_dev.sqlite "SELECT name FROM sqlite_master WHERE type='table';"
问题排查清单
| 问题 | 检查命令 | 解决方案 |
|---|---|---|
| 时区错误 | python -c "from zoneinfo import ZoneInfo; print(ZoneInfo('Asia/Shanghai'))" |
pip install tzdata |
| Alembic找不到Base | grep -r "DeclBase" app/ |
修改env.py导入路径 |
| 迁移文件不生成 | sqlite3 app_dev.sqlite ".tables" |
检查env.py中的target_metadata |
| Token认证失败 | 检查用户表的jwt_key字段 | 确保jwt_key不为空 |
总结
fastapi-scaff脚手架创建的项目需要手动补充三个核心配置:
- 时区统一 :创建
app/utils/mytime.py,替换所有时间相关函数 - 数据库迁移:初始化Alembic,配置正确的Base类和数据库连接
- 模型一致:确保所有模型继承同一个Base类
完成上述配置后,项目即可正常运行完整的JWT认证流程和数据库迁移功能。
欢迎焦虑 沟通 有错误 指正留言~
