models_db.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. """
  2. SQLAlchemy 数据库模型定义
  3. """
  4. from datetime import datetime
  5. from typing import Optional
  6. from sqlalchemy import (
  7. Column, String, Integer, BigInteger, Text,
  8. Enum, Boolean, JSON, TIMESTAMP, Index, UniqueConstraint
  9. )
  10. from sqlalchemy.orm import declarative_base
  11. from sqlalchemy.sql import func
  12. Base = declarative_base()
  13. class User(Base):
  14. """用户表模型"""
  15. __tablename__ = "users"
  16. id = Column(BigInteger, primary_key=True, autoincrement=True)
  17. user_id = Column(String(64), unique=True, nullable=False, index=True, comment="用户唯一标识(UUID)")
  18. # 基本信息
  19. username = Column(String(100), nullable=False, comment="用户名/昵称")
  20. user_type = Column(Enum("guest", "registered", name="user_type"), default="guest", comment="用户类型")
  21. bot_name = Column(String(100), nullable=True, comment="用户给机器人取的名字")
  22. # 注册信息(可选)
  23. phone = Column(String(20), unique=True, nullable=True, index=True, comment="手机号")
  24. wechat_openid = Column(String(100), unique=True, nullable=True, index=True, comment="微信OpenID")
  25. google_email = Column(String(200), unique=True, nullable=True, index=True, comment="Google邮箱")
  26. # 认证信息
  27. is_verified = Column(Boolean, default=False, comment="是否已认证")
  28. registered_at = Column(TIMESTAMP, nullable=True, comment="注册时间")
  29. # 统计信息
  30. total_messages = Column(Integer, default=0, comment="总消息数")
  31. total_rounds = Column(Integer, default=0, comment="总对话轮次")
  32. total_speech_duration_ms = Column(BigInteger, default=0, comment="累积说话时长(毫秒)")
  33. last_active_at = Column(TIMESTAMP, nullable=True, comment="最后活跃时间")
  34. created_at = Column(TIMESTAMP, server_default=func.current_timestamp(), comment="创建时间")
  35. updated_at = Column(TIMESTAMP, server_default=func.current_timestamp(), onupdate=func.current_timestamp(), comment="更新时间")
  36. def to_dict(self) -> dict:
  37. """转换为字典"""
  38. return {
  39. "user_id": self.user_id,
  40. "username": self.username,
  41. "user_type": self.user_type,
  42. "bot_name": self.bot_name,
  43. "phone": self.phone,
  44. "wechat_openid": self.wechat_openid,
  45. "google_email": self.google_email,
  46. "is_verified": self.is_verified,
  47. "registered_at": self.registered_at.isoformat() if self.registered_at else None,
  48. "total_messages": self.total_messages,
  49. "total_rounds": self.total_rounds,
  50. "total_speech_duration_ms": self.total_speech_duration_ms,
  51. "last_active_at": self.last_active_at.isoformat() if self.last_active_at else None,
  52. "created_at": self.created_at.isoformat() if self.created_at else None,
  53. }
  54. class Message(Base):
  55. """消息表模型"""
  56. __tablename__ = "messages"
  57. id = Column(BigInteger, primary_key=True, autoincrement=True)
  58. message_id = Column(String(64), unique=True, nullable=False, comment="消息ID")
  59. user_id = Column(String(64), nullable=False, index=True, comment="用户ID")
  60. # 消息内容
  61. role = Column(Enum("user", "assistant", "system", name="role"), nullable=False, comment="角色")
  62. content = Column(Text, nullable=False, comment="消息内容")
  63. # 轮次信息
  64. round_number = Column(Integer, nullable=False, comment="对话轮次(从1开始)")
  65. # 扩展信息
  66. emotion = Column(String(50), nullable=True, comment="情感标签")
  67. message_metadata = Column(JSON, nullable=True, comment="扩展信息(音频路径、识别耗时等)")
  68. token_count = Column(Integer, nullable=True, comment="Token数量")
  69. created_at = Column(TIMESTAMP, server_default=func.current_timestamp(), index=True, comment="创建时间")
  70. # 复合索引
  71. __table_args__ = (
  72. Index("idx_user_round", "user_id", "round_number"),
  73. )
  74. def to_dict(self) -> dict:
  75. """转换为字典"""
  76. return {
  77. "message_id": self.message_id,
  78. "user_id": self.user_id,
  79. "role": self.role,
  80. "content": self.content,
  81. "round_number": self.round_number,
  82. "emotion": self.emotion,
  83. "metadata": self.message_metadata, # 对外保持 metadata 字段名
  84. "token_count": self.token_count,
  85. "created_at": self.created_at.isoformat() if self.created_at else None,
  86. }
  87. class ConversationSummary(Base):
  88. """对话总结表模型"""
  89. __tablename__ = "conversation_summaries"
  90. id = Column(BigInteger, primary_key=True, autoincrement=True)
  91. summary_id = Column(String(64), unique=True, nullable=False, comment="总结ID")
  92. user_id = Column(String(64), nullable=False, index=True, comment="用户ID")
  93. # 总结范围
  94. start_round = Column(Integer, nullable=False, comment="起始轮次")
  95. end_round = Column(Integer, nullable=False, comment="结束轮次")
  96. # 总结内容
  97. summary_text = Column(Text, nullable=False, comment="总结内容")
  98. summary_type = Column(Enum("auto", "manual", name="summary_type"), default="auto", comment="总结类型")
  99. # 统计信息
  100. message_count = Column(Integer, nullable=True, comment="总结的消息数量")
  101. token_count = Column(Integer, nullable=True, comment="总结文本Token数")
  102. created_at = Column(TIMESTAMP, server_default=func.current_timestamp(), comment="创建时间")
  103. # 复合索引和唯一约束
  104. __table_args__ = (
  105. Index("idx_user_end_round", "user_id", "end_round"),
  106. UniqueConstraint("user_id", "start_round", "end_round", name="uk_user_round_range"),
  107. )
  108. def to_dict(self) -> dict:
  109. """转换为字典"""
  110. return {
  111. "summary_id": self.summary_id,
  112. "user_id": self.user_id,
  113. "start_round": self.start_round,
  114. "end_round": self.end_round,
  115. "summary_text": self.summary_text,
  116. "summary_type": self.summary_type,
  117. "message_count": self.message_count,
  118. "token_count": self.token_count,
  119. "created_at": self.created_at.isoformat() if self.created_at else None,
  120. }