001_initial.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. """初始化数据库表
  2. Revision ID: 001
  3. Create Date: 2025-12-07
  4. """
  5. from alembic import op
  6. import sqlalchemy as sa
  7. from sqlalchemy.dialects import postgresql
  8. # revision标识符
  9. revision = '001'
  10. down_revision = None
  11. branch_labels = None
  12. depends_on = None
  13. def upgrade() -> None:
  14. """创建所有表"""
  15. # 用户表
  16. op.create_table(
  17. 'users',
  18. sa.Column('id', sa.Integer(), nullable=False),
  19. sa.Column('username', sa.String(length=50), nullable=False),
  20. sa.Column('email', sa.String(length=100), nullable=False),
  21. sa.Column('password_hash', sa.String(length=255), nullable=False),
  22. sa.Column('avatar_url', sa.String(length=500)),
  23. sa.Column('encrypted_api_keys', postgresql.JSON(), server_default='{}'),
  24. sa.Column('timezone', sa.String(length=50), server_default='UTC'),
  25. sa.Column('preferred_language', sa.String(length=10), server_default='en'),
  26. sa.Column('is_active', sa.Boolean(), server_default='true'),
  27. sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now()),
  28. sa.Column('updated_at', sa.DateTime(timezone=True)),
  29. sa.PrimaryKeyConstraint('id')
  30. )
  31. op.create_index('ix_users_username', 'users', ['username'], unique=True)
  32. op.create_index('ix_users_email', 'users', ['email'], unique=True)
  33. # 角色表
  34. op.create_table(
  35. 'characters',
  36. sa.Column('id', sa.Integer(), nullable=False),
  37. sa.Column('user_id', sa.Integer(), nullable=False),
  38. sa.Column('name', sa.String(length=100), nullable=False),
  39. sa.Column('avatar_url', sa.String(length=500)),
  40. sa.Column('personality', sa.Text()),
  41. sa.Column('background_story', sa.Text()),
  42. sa.Column('system_prompt', sa.Text(), nullable=False),
  43. sa.Column('llm_provider', sa.String(length=50), server_default='openai'),
  44. sa.Column('llm_model', sa.String(length=100), server_default='gpt-3.5-turbo'),
  45. sa.Column('config', postgresql.JSON(), server_default='{}'),
  46. sa.Column('language', sa.String(length=10), server_default='en'),
  47. sa.Column('is_active', sa.Boolean(), server_default='true'),
  48. sa.Column('is_preset', sa.Boolean(), server_default='false'),
  49. sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now()),
  50. sa.Column('updated_at', sa.DateTime(timezone=True)),
  51. sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
  52. sa.PrimaryKeyConstraint('id')
  53. )
  54. op.create_index('ix_characters_user_id', 'characters', ['user_id'])
  55. # 对话表
  56. op.create_table(
  57. 'conversations',
  58. sa.Column('id', sa.Integer(), nullable=False),
  59. sa.Column('user_id', sa.Integer(), nullable=False),
  60. sa.Column('character_id', sa.Integer(), nullable=False),
  61. sa.Column('title', sa.String(length=200)),
  62. sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now()),
  63. sa.Column('updated_at', sa.DateTime(timezone=True)),
  64. sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
  65. sa.ForeignKeyConstraint(['character_id'], ['characters.id'], ondelete='CASCADE'),
  66. sa.PrimaryKeyConstraint('id')
  67. )
  68. op.create_index('idx_user_character', 'conversations', ['user_id', 'character_id'], unique=True)
  69. # 消息表
  70. op.create_table(
  71. 'messages',
  72. sa.Column('id', sa.Integer(), nullable=False),
  73. sa.Column('conversation_id', sa.Integer(), nullable=False),
  74. sa.Column('role', sa.String(length=20), nullable=False),
  75. sa.Column('content', sa.Text(), nullable=False),
  76. sa.Column('tokens_used', sa.Integer(), server_default='0'),
  77. sa.Column('is_proactive', sa.Boolean(), server_default='false'),
  78. sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now()),
  79. sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], ondelete='CASCADE'),
  80. sa.PrimaryKeyConstraint('id')
  81. )
  82. op.create_index('ix_messages_conversation_id', 'messages', ['conversation_id'])
  83. op.create_index('ix_messages_created_at', 'messages', ['created_at'])
  84. # 用户活跃状态表
  85. op.create_table(
  86. 'user_activity',
  87. sa.Column('id', sa.Integer(), nullable=False),
  88. sa.Column('conversation_id', sa.Integer(), nullable=False),
  89. sa.Column('last_message_at', sa.DateTime(timezone=True), nullable=False),
  90. sa.Column('last_check_at', sa.DateTime(timezone=True)),
  91. sa.Column('next_proactive_at', sa.DateTime(timezone=True)),
  92. sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], ondelete='CASCADE'),
  93. sa.PrimaryKeyConstraint('id'),
  94. sa.UniqueConstraint('conversation_id')
  95. )
  96. op.create_index('ix_user_activity_next_proactive_at', 'user_activity', ['next_proactive_at'])
  97. # 好感度表
  98. op.create_table(
  99. 'affection_scores',
  100. sa.Column('id', sa.Integer(), nullable=False),
  101. sa.Column('user_id', sa.Integer(), nullable=False),
  102. sa.Column('character_id', sa.Integer(), nullable=False),
  103. sa.Column('current_score', sa.Integer(), server_default='0', nullable=False),
  104. sa.Column('level', sa.String(length=50), server_default='普通'),
  105. sa.Column('last_interaction', sa.DateTime(timezone=True)),
  106. sa.Column('total_interactions', sa.Integer(), server_default='0'),
  107. sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now()),
  108. sa.Column('updated_at', sa.DateTime(timezone=True)),
  109. sa.CheckConstraint('current_score >= -100 AND current_score <= 100', name='score_range'),
  110. sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
  111. sa.ForeignKeyConstraint(['character_id'], ['characters.id'], ondelete='CASCADE'),
  112. sa.PrimaryKeyConstraint('id')
  113. )
  114. op.create_index('idx_user_character_affection', 'affection_scores', ['user_id', 'character_id'], unique=True)
  115. # 好感度历史表
  116. op.create_table(
  117. 'affection_logs',
  118. sa.Column('id', sa.Integer(), nullable=False),
  119. sa.Column('affection_score_id', sa.Integer(), nullable=False),
  120. sa.Column('message_id', sa.Integer()),
  121. sa.Column('score_change', sa.Integer(), nullable=False),
  122. sa.Column('reason', sa.Text()),
  123. sa.Column('sentiment_analysis', postgresql.JSON()),
  124. sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now()),
  125. sa.ForeignKeyConstraint(['affection_score_id'], ['affection_scores.id'], ondelete='CASCADE'),
  126. sa.ForeignKeyConstraint(['message_id'], ['messages.id'], ondelete='SET NULL'),
  127. sa.PrimaryKeyConstraint('id')
  128. )
  129. op.create_index('ix_affection_logs_affection_score_id', 'affection_logs', ['affection_score_id'])
  130. op.create_index('ix_affection_logs_created_at', 'affection_logs', ['created_at'])
  131. # 主动消息任务表
  132. op.create_table(
  133. 'proactive_message_tasks',
  134. sa.Column('id', sa.Integer(), nullable=False),
  135. sa.Column('user_id', sa.Integer(), nullable=False),
  136. sa.Column('character_id', sa.Integer(), nullable=False),
  137. sa.Column('scheduled_at', sa.DateTime(timezone=True), nullable=False),
  138. sa.Column('status', sa.String(length=20), server_default='pending'),
  139. sa.Column('trigger_reason', sa.String(length=100)),
  140. sa.Column('message_content', sa.Text()),
  141. sa.Column('sent_at', sa.DateTime(timezone=True)),
  142. sa.Column('error_message', sa.Text()),
  143. sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now()),
  144. sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
  145. sa.ForeignKeyConstraint(['character_id'], ['characters.id'], ondelete='CASCADE'),
  146. sa.PrimaryKeyConstraint('id')
  147. )
  148. op.create_index('ix_proactive_tasks_scheduled_at', 'proactive_message_tasks', ['scheduled_at'])
  149. op.create_index('ix_proactive_tasks_status', 'proactive_message_tasks', ['status'])
  150. def downgrade() -> None:
  151. """删除所有表"""
  152. op.drop_table('proactive_message_tasks')
  153. op.drop_table('affection_logs')
  154. op.drop_table('affection_scores')
  155. op.drop_table('user_activity')
  156. op.drop_table('messages')
  157. op.drop_table('conversations')
  158. op.drop_table('characters')
  159. op.drop_table('users')