2026-01-07 11:56:33 +08:00
|
|
|
|
"""
|
|
|
|
|
|
数据库模型定义
|
|
|
|
|
|
"""
|
2026-01-21 23:21:36 +01:00
|
|
|
|
from datetime import datetime, timezone
|
2026-01-07 11:56:33 +08:00
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-21 23:21:36 +01:00
|
|
|
|
def utc_now():
|
|
|
|
|
|
"""返回当前 UTC 时间(带时区信息)"""
|
|
|
|
|
|
return datetime.now(timezone.utc)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-07 11:56:33 +08:00
|
|
|
|
class User(Base):
|
|
|
|
|
|
"""用户表"""
|
|
|
|
|
|
__tablename__ = "users"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(String, primary_key=True)
|
2026-01-18 15:57:47 +08:00
|
|
|
|
phone = Column(String, unique=True, nullable=False, index=True) # 手机号(唯一,必填)
|
|
|
|
|
|
password_hash = Column(String, nullable=False) # 密码哈希
|
|
|
|
|
|
email = Column(String, unique=True, nullable=True) # 邮箱(可选)
|
|
|
|
|
|
openid = Column(String, unique=True, nullable=True) # 微信 OpenID(可选)
|
2026-01-07 11:56:33 +08:00
|
|
|
|
nickname = Column(String, nullable=False)
|
|
|
|
|
|
avatar_url = Column(String, nullable=True)
|
|
|
|
|
|
subscription_type = Column(String, default="free") # free, premium
|
2026-02-10 14:23:29 +08:00
|
|
|
|
subscription_expires_at = Column(DateTime(timezone=True), nullable=True) # 订阅到期时间
|
2026-01-21 23:21:36 +01:00
|
|
|
|
created_at = Column(DateTime(timezone=True), default=utc_now)
|
2026-01-07 11:56:33 +08:00
|
|
|
|
|
2026-03-01 10:12:23 +01:00
|
|
|
|
birth_year = Column(Integer, nullable=True)
|
|
|
|
|
|
birth_place = Column(String, nullable=True)
|
|
|
|
|
|
grew_up_place = Column(String, nullable=True)
|
|
|
|
|
|
occupation = Column(String, nullable=True)
|
|
|
|
|
|
|
2026-01-07 11:56:33 +08:00
|
|
|
|
# Relationships
|
|
|
|
|
|
conversations = relationship("Conversation", back_populates="user")
|
|
|
|
|
|
chapters = relationship("Chapter", back_populates="user")
|
|
|
|
|
|
books = relationship("Book", back_populates="user")
|
2026-02-10 14:23:29 +08:00
|
|
|
|
orders = relationship("Order", back_populates="user", cascade="all, delete-orphan")
|
2026-01-21 22:31:03 +01:00
|
|
|
|
memoir_state = relationship("MemoirState", back_populates="user", uselist=False, cascade="all, delete-orphan")
|
2026-01-18 15:57:47 +08:00
|
|
|
|
refresh_tokens = relationship("RefreshToken", back_populates="user", cascade="all, delete-orphan")
|
2026-01-07 11:56:33 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Conversation(Base):
|
|
|
|
|
|
"""对话会话表"""
|
|
|
|
|
|
__tablename__ = "conversations"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(String, primary_key=True)
|
|
|
|
|
|
user_id = Column(String, ForeignKey("users.id"), nullable=False)
|
2026-01-21 23:21:36 +01:00
|
|
|
|
started_at = Column(DateTime(timezone=True), default=utc_now)
|
|
|
|
|
|
ended_at = Column(DateTime(timezone=True), nullable=True)
|
2026-01-07 11:56:33 +08:00
|
|
|
|
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)
|
2026-01-21 23:21:36 +01:00
|
|
|
|
created_at = Column(DateTime(timezone=True), default=utc_now)
|
2026-01-07 11:56:33 +08:00
|
|
|
|
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 列表
|
2026-01-21 23:21:36 +01:00
|
|
|
|
updated_at = Column(DateTime(timezone=True), default=utc_now, onupdate=utc_now)
|
2026-01-07 11:56:33 +08:00
|
|
|
|
category = Column(String, nullable=True) # 章节分类
|
2026-01-21 22:31:03 +01:00
|
|
|
|
is_new = Column(Boolean, default=True) # 是否为新内容(未读)
|
2026-02-14 10:57:51 +01:00
|
|
|
|
is_active = Column(Boolean, default=True) # 是否启用(清除回忆后置为 False)
|
2026-01-21 22:31:03 +01:00
|
|
|
|
source_segments = Column(JSON, nullable=True) # 来源 segment IDs 列表
|
2026-01-07 11:56:33 +08:00
|
|
|
|
|
|
|
|
|
|
# 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)
|
2026-01-21 23:21:36 +01:00
|
|
|
|
updated_at = Column(DateTime(timezone=True), default=utc_now, onupdate=utc_now)
|
2026-01-21 22:31:03 +01:00
|
|
|
|
has_update = Column(Boolean, default=False) # 是否有新内容
|
|
|
|
|
|
last_update_chapter_id = Column(String, nullable=True) # 最近更新的章节 ID
|
2026-01-07 11:56:33 +08:00
|
|
|
|
|
|
|
|
|
|
# Relationships
|
|
|
|
|
|
user = relationship("User", back_populates="books")
|
|
|
|
|
|
|
2026-01-18 15:57:47 +08:00
|
|
|
|
|
2026-01-21 22:31:03 +01:00
|
|
|
|
class MemoirState(Base):
|
|
|
|
|
|
"""回忆录状态表 - 对话 Agent 与后台 Agent 共享"""
|
|
|
|
|
|
__tablename__ = "memoir_states"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(String, primary_key=True)
|
|
|
|
|
|
user_id = Column(String, ForeignKey("users.id"), unique=True, nullable=False)
|
|
|
|
|
|
stage_order = Column(JSON, default=list) # 阶段顺序
|
|
|
|
|
|
current_stage = Column(String, default="childhood") # 当前阶段
|
|
|
|
|
|
covered_stages = Column(JSON, default=list) # 已完成阶段列表
|
|
|
|
|
|
slots = Column(JSON, nullable=False) # 各阶段 slot 信息
|
2026-01-21 23:21:36 +01:00
|
|
|
|
updated_at = Column(DateTime(timezone=True), default=utc_now, onupdate=utc_now)
|
2026-01-21 22:31:03 +01:00
|
|
|
|
|
|
|
|
|
|
# Relationships
|
|
|
|
|
|
user = relationship("User", back_populates="memoir_state")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-18 15:57:47 +08:00
|
|
|
|
class RefreshToken(Base):
|
|
|
|
|
|
"""刷新令牌表"""
|
|
|
|
|
|
__tablename__ = "refresh_tokens"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(String, primary_key=True)
|
|
|
|
|
|
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
|
|
|
|
|
|
token = Column(String, unique=True, nullable=False, index=True) # 刷新令牌(唯一)
|
2026-01-21 23:21:36 +01:00
|
|
|
|
expires_at = Column(DateTime(timezone=True), nullable=False) # 过期时间(30天后)
|
|
|
|
|
|
created_at = Column(DateTime(timezone=True), default=utc_now)
|
2026-01-18 15:57:47 +08:00
|
|
|
|
is_revoked = Column(Boolean, default=False) # 是否已撤销
|
2026-01-27 11:35:57 +08:00
|
|
|
|
device_info = Column(String, nullable=True) # 设备信息(用于全设备登出)
|
2026-01-18 15:57:47 +08:00
|
|
|
|
|
|
|
|
|
|
# Relationships
|
|
|
|
|
|
user = relationship("User", back_populates="refresh_tokens")
|
|
|
|
|
|
|
2026-01-27 11:35:57 +08:00
|
|
|
|
|
|
|
|
|
|
class SmsVerificationCode(Base):
|
|
|
|
|
|
"""短信验证码表"""
|
|
|
|
|
|
__tablename__ = "sms_verification_codes"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(String, primary_key=True)
|
|
|
|
|
|
phone = Column(String, nullable=False, index=True) # 手机号
|
|
|
|
|
|
code = Column(String, nullable=False) # 6位验证码
|
|
|
|
|
|
purpose = Column(String, nullable=False) # register/login/reset_password/change_phone
|
|
|
|
|
|
is_used = Column(Boolean, default=False) # 是否已使用
|
|
|
|
|
|
is_expired = Column(Boolean, default=False) # 是否已过期
|
|
|
|
|
|
expires_at = Column(DateTime(timezone=True), nullable=False) # 过期时间(5分钟后)
|
|
|
|
|
|
created_at = Column(DateTime(timezone=True), default=utc_now)
|
|
|
|
|
|
verified_at = Column(DateTime(timezone=True), nullable=True) # 验证时间
|
|
|
|
|
|
ip_address = Column(String, nullable=True) # 请求IP地址
|
|
|
|
|
|
|
2026-02-10 14:23:29 +08:00
|
|
|
|
|
|
|
|
|
|
class Order(Base):
|
|
|
|
|
|
"""支付订单表"""
|
|
|
|
|
|
__tablename__ = "orders"
|
|
|
|
|
|
|
|
|
|
|
|
id = Column(String, primary_key=True) # 内部订单号
|
|
|
|
|
|
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
|
|
|
|
|
|
plan_id = Column(String, nullable=False) # 套餐 ID(free / premium)
|
|
|
|
|
|
plan_name = Column(String, nullable=False) # 套餐名称
|
|
|
|
|
|
amount = Column(Integer, nullable=False) # 金额(单位:分)
|
|
|
|
|
|
currency = Column(String, default="CNY")
|
|
|
|
|
|
payment_method = Column(String, nullable=False) # wechat / alipay
|
|
|
|
|
|
status = Column(String, default="pending") # pending / paid / failed / cancelled / refunded
|
|
|
|
|
|
trade_no = Column(String, nullable=True, index=True) # 第三方交易号(微信/支付宝)
|
|
|
|
|
|
paid_at = Column(DateTime(timezone=True), nullable=True) # 支付完成时间
|
|
|
|
|
|
created_at = Column(DateTime(timezone=True), default=utc_now)
|
|
|
|
|
|
expired_at = Column(DateTime(timezone=True), nullable=True) # 订单超时时间
|
|
|
|
|
|
|
|
|
|
|
|
# Relationships
|
|
|
|
|
|
user = relationship("User", back_populates="orders")
|
|
|
|
|
|
|