first commit
This commit is contained in:
2
MeetSpot/app/db/__init__.py
Normal file
2
MeetSpot/app/db/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""数据库相关模块初始化。"""
|
||||
|
||||
50
MeetSpot/app/db/crud.py
Normal file
50
MeetSpot/app/db/crud.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""常用数据库操作封装。"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.user import User
|
||||
|
||||
|
||||
def _default_nickname(phone: str) -> str:
|
||||
suffix = phone[-4:] if len(phone) >= 4 else phone
|
||||
return f"用户{suffix}"
|
||||
|
||||
|
||||
async def get_user_by_phone(db: AsyncSession, phone: str) -> Optional[User]:
|
||||
"""根据手机号查询用户。"""
|
||||
stmt = select(User).where(User.phone == phone)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def get_user_by_id(db: AsyncSession, user_id: str) -> Optional[User]:
|
||||
"""根据ID查询用户。"""
|
||||
stmt = select(User).where(User.id == user_id)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def create_user(
|
||||
db: AsyncSession, phone: str, nickname: Optional[str] = None, avatar_url: str = ""
|
||||
) -> User:
|
||||
"""创建新用户。"""
|
||||
user = User(
|
||||
phone=phone,
|
||||
nickname=nickname or _default_nickname(phone),
|
||||
avatar_url=avatar_url or "",
|
||||
)
|
||||
db.add(user)
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
async def touch_last_login(db: AsyncSession, user: User) -> None:
|
||||
"""更新用户最近登录时间。"""
|
||||
user.last_login = datetime.utcnow()
|
||||
await db.commit()
|
||||
|
||||
48
MeetSpot/app/db/database.py
Normal file
48
MeetSpot/app/db/database.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""数据库引擎与会话管理。
|
||||
|
||||
使用SQLite作为MVP默认存储,保留通过环境变量`DATABASE_URL`切换到PostgreSQL的能力。
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import AsyncGenerator
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
|
||||
# 项目根目录,默认将SQLite数据库放在data目录下
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
DATA_DIR = PROJECT_ROOT / "data"
|
||||
DATA_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# 允许通过环境变量覆盖数据库连接串
|
||||
DEFAULT_SQLITE_PATH = DATA_DIR / "meetspot.db"
|
||||
DATABASE_URL = os.getenv(
|
||||
"DATABASE_URL", f"sqlite+aiosqlite:///{DEFAULT_SQLITE_PATH.as_posix()}"
|
||||
)
|
||||
|
||||
# 创建异步引擎与会话工厂
|
||||
engine = create_async_engine(DATABASE_URL, echo=False, future=True)
|
||||
AsyncSessionLocal = async_sessionmaker(
|
||||
bind=engine, class_=AsyncSession, expire_on_commit=False, autoflush=False
|
||||
)
|
||||
|
||||
# 统一的ORM基类
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
||||
"""FastAPI 依赖:提供数据库会话并确保正确关闭。"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
yield session
|
||||
|
||||
|
||||
async def init_db() -> None:
|
||||
"""在启动时创建数据库表。"""
|
||||
# 延迟导入以避免循环依赖
|
||||
from app import models # noqa: F401 确保所有模型已注册
|
||||
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
Reference in New Issue
Block a user