- 新增数据库迁移脚本 (practice_rooms, practice_room_messages) - 新增后端 API: 房间创建/加入/消息同步/报告生成 - 新增前端页面: 入口页/对练房间/报告页 - 新增 AI 双人评估服务和提示词
This commit is contained in:
122
backend/app/models/practice_room.py
Normal file
122
backend/app/models/practice_room.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
双人对练房间模型
|
||||
|
||||
功能:
|
||||
- 房间管理(创建、加入、状态)
|
||||
- 参与者管理
|
||||
- 实时消息同步
|
||||
"""
|
||||
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey, JSON
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.models.base import Base
|
||||
|
||||
|
||||
class PracticeRoom(Base):
|
||||
"""双人对练房间模型"""
|
||||
__tablename__ = "practice_rooms"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, comment="房间ID")
|
||||
room_code = Column(String(10), unique=True, nullable=False, index=True, comment="6位邀请码")
|
||||
room_name = Column(String(200), comment="房间名称")
|
||||
|
||||
# 场景信息
|
||||
scene_id = Column(Integer, ForeignKey("practice_scenes.id", ondelete="SET NULL"), comment="关联场景ID")
|
||||
scene_name = Column(String(200), comment="场景名称")
|
||||
scene_type = Column(String(50), comment="场景类型")
|
||||
scene_background = Column(Text, comment="场景背景")
|
||||
|
||||
# 角色设置
|
||||
role_a_name = Column(String(50), default="角色A", comment="角色A名称(如销售顾问)")
|
||||
role_b_name = Column(String(50), default="角色B", comment="角色B名称(如顾客)")
|
||||
role_a_description = Column(Text, comment="角色A描述")
|
||||
role_b_description = Column(Text, comment="角色B描述")
|
||||
|
||||
# 参与者信息
|
||||
host_user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, comment="房主用户ID")
|
||||
guest_user_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), comment="加入者用户ID")
|
||||
host_role = Column(String(10), default="A", comment="房主选择的角色(A/B)")
|
||||
max_participants = Column(Integer, default=2, comment="最大参与人数")
|
||||
|
||||
# 状态和时间
|
||||
status = Column(String(20), default="waiting", index=True, comment="状态: waiting/ready/practicing/completed/canceled")
|
||||
created_at = Column(DateTime, server_default=func.now(), comment="创建时间")
|
||||
started_at = Column(DateTime, comment="开始时间")
|
||||
ended_at = Column(DateTime, comment="结束时间")
|
||||
duration_seconds = Column(Integer, default=0, comment="对练时长(秒)")
|
||||
|
||||
# 对话统计
|
||||
total_turns = Column(Integer, default=0, comment="总对话轮次")
|
||||
role_a_turns = Column(Integer, default=0, comment="角色A发言次数")
|
||||
role_b_turns = Column(Integer, default=0, comment="角色B发言次数")
|
||||
|
||||
# 软删除
|
||||
is_deleted = Column(Boolean, default=False, comment="是否删除")
|
||||
deleted_at = Column(DateTime, comment="删除时间")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<PracticeRoom(code='{self.room_code}', status='{self.status}')>"
|
||||
|
||||
@property
|
||||
def is_full(self) -> bool:
|
||||
"""房间是否已满"""
|
||||
return self.guest_user_id is not None
|
||||
|
||||
@property
|
||||
def participant_count(self) -> int:
|
||||
"""当前参与人数"""
|
||||
count = 1 # 房主
|
||||
if self.guest_user_id:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def get_user_role(self, user_id: int) -> str:
|
||||
"""获取用户在房间中的角色"""
|
||||
if user_id == self.host_user_id:
|
||||
return self.host_role
|
||||
elif user_id == self.guest_user_id:
|
||||
return "B" if self.host_role == "A" else "A"
|
||||
return None
|
||||
|
||||
def get_role_name(self, role: str) -> str:
|
||||
"""获取角色名称"""
|
||||
if role == "A":
|
||||
return self.role_a_name
|
||||
elif role == "B":
|
||||
return self.role_b_name
|
||||
return None
|
||||
|
||||
def get_user_role_name(self, user_id: int) -> str:
|
||||
"""获取用户的角色名称"""
|
||||
role = self.get_user_role(user_id)
|
||||
return self.get_role_name(role) if role else None
|
||||
|
||||
|
||||
class PracticeRoomMessage(Base):
|
||||
"""房间实时消息模型"""
|
||||
__tablename__ = "practice_room_messages"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True, comment="消息ID")
|
||||
room_id = Column(Integer, ForeignKey("practice_rooms.id", ondelete="CASCADE"), nullable=False, index=True, comment="房间ID")
|
||||
user_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), comment="发送者用户ID")
|
||||
message_type = Column(String(20), nullable=False, comment="消息类型: chat/system/join/leave/start/end")
|
||||
content = Column(Text, comment="消息内容")
|
||||
role_name = Column(String(50), comment="角色名称")
|
||||
sequence = Column(Integer, nullable=False, comment="消息序号")
|
||||
created_at = Column(DateTime(3), server_default=func.now(3), comment="创建时间")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<PracticeRoomMessage(room_id={self.room_id}, type='{self.message_type}', seq={self.sequence})>"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""转换为字典(用于SSE推送)"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"room_id": self.room_id,
|
||||
"user_id": self.user_id,
|
||||
"message_type": self.message_type,
|
||||
"content": self.content,
|
||||
"role_name": self.role_name,
|
||||
"sequence": self.sequence,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
Reference in New Issue
Block a user