diff --git a/api/database/__init__.py b/api/database/__init__.py new file mode 100644 index 0000000..3067e8a --- /dev/null +++ b/api/database/__init__.py @@ -0,0 +1,17 @@ +""" +数据库模块 +""" +from .database import get_db, get_async_db, init_db +from .models import User, Conversation, Segment, Chapter, Book + +__all__ = [ + "get_db", + "get_async_db", + "init_db", + "User", + "Conversation", + "Segment", + "Chapter", + "Book", +] + diff --git a/api/database/__pycache__/__init__.cpython-312.pyc b/api/database/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..be996f1 Binary files /dev/null and b/api/database/__pycache__/__init__.cpython-312.pyc differ diff --git a/api/database/__pycache__/database.cpython-312.pyc b/api/database/__pycache__/database.cpython-312.pyc new file mode 100644 index 0000000..0763710 Binary files /dev/null and b/api/database/__pycache__/database.cpython-312.pyc differ diff --git a/api/database/__pycache__/models.cpython-312.pyc b/api/database/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..72e0cd4 Binary files /dev/null and b/api/database/__pycache__/models.cpython-312.pyc differ diff --git a/api/database/database.py b/api/database/database.py new file mode 100644 index 0000000..0ea70ec --- /dev/null +++ b/api/database/database.py @@ -0,0 +1,51 @@ +""" +数据库连接和初始化 +""" +import os +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, Session +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker + +from .models import Base + +# 数据库文件路径 +DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./life_echo.db") +ASYNC_DATABASE_URL = DATABASE_URL.replace("sqlite://", "sqlite+aiosqlite://") + +# 创建同步引擎(用于迁移等) +engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) + +# 创建异步引擎(用于实际应用) +async_engine = create_async_engine(ASYNC_DATABASE_URL, echo=False) + +# 创建会话工厂 +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +AsyncSessionLocal = async_sessionmaker(async_engine, class_=AsyncSession, expire_on_commit=False) + + +def init_db(): + """初始化数据库,创建所有表""" + Base.metadata.create_all(bind=engine) + + +def get_db(): + """获取同步数据库会话(用于迁移等)""" + db = SessionLocal() + try: + yield db + finally: + db.close() + + +async def get_async_db(): + """获取异步数据库会话(用于实际应用)""" + async with AsyncSessionLocal() as session: + try: + yield session + await session.commit() + except Exception: + await session.rollback() + raise + finally: + await session.close() + diff --git a/api/database/models.py b/api/database/models.py new file mode 100644 index 0000000..57570e5 --- /dev/null +++ b/api/database/models.py @@ -0,0 +1,98 @@ +""" +数据库模型定义 +""" +from datetime import datetime +from typing import Optional, List +from sqlalchemy import Column, String, Integer, DateTime, Boolean, Text, ForeignKey, JSON +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship + +Base = declarative_base() + + +class User(Base): + """用户表""" + __tablename__ = "users" + + id = Column(String, primary_key=True) + openid = Column(String, unique=True, nullable=True) # 微信 OpenID + nickname = Column(String, nullable=False) + avatar_url = Column(String, nullable=True) + subscription_type = Column(String, default="free") # free, premium + created_at = Column(DateTime, default=datetime.utcnow) + + # Relationships + conversations = relationship("Conversation", back_populates="user") + chapters = relationship("Chapter", back_populates="user") + books = relationship("Book", back_populates="user") + + +class Conversation(Base): + """对话会话表""" + __tablename__ = "conversations" + + id = Column(String, primary_key=True) + user_id = Column(String, ForeignKey("users.id"), nullable=False) + started_at = Column(DateTime, default=datetime.utcnow) + ended_at = Column(DateTime, nullable=True) + duration_seconds = Column(Integer, default=0) + summary = Column(Text, nullable=True) + status = Column(String, default="active") # active, ended, processing + current_topic = Column(String, nullable=True) + conversation_stage = Column(String, nullable=True) # childhood, education, career, family, beliefs, summary + + # Relationships + user = relationship("User", back_populates="conversations") + segments = relationship("Segment", back_populates="conversation", cascade="all, delete-orphan") + + +class Segment(Base): + """对话段落表""" + __tablename__ = "segments" + + id = Column(String, primary_key=True) + conversation_id = Column(String, ForeignKey("conversations.id"), nullable=False) + audio_url = Column(String, nullable=True) + transcript_text = Column(Text, nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) + processed = Column(Boolean, default=False) + topic_category = Column(String, nullable=True) + agent_response = Column(Text, nullable=True) + + # Relationships + conversation = relationship("Conversation", back_populates="segments") + + +class Chapter(Base): + """章节表""" + __tablename__ = "chapters" + + id = Column(String, primary_key=True) + user_id = Column(String, ForeignKey("users.id"), nullable=False) + title = Column(String, nullable=False) + content = Column(Text, nullable=False) + order_index = Column(Integer, nullable=False) + status = Column(String, default="draft") # draft, completed + images = Column(JSON, nullable=True) # 图片 URL 列表 + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + category = Column(String, nullable=True) # 章节分类 + + # Relationships + user = relationship("User", back_populates="chapters") + + +class Book(Base): + """回忆录表""" + __tablename__ = "books" + + id = Column(String, primary_key=True) + user_id = Column(String, ForeignKey("users.id"), nullable=False) + title = Column(String, nullable=False) + total_pages = Column(Integer, default=0) + total_words = Column(Integer, default=0) + cover_image_url = Column(String, nullable=True) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # Relationships + user = relationship("User", back_populates="books") +