Files
huihuiSquare/backend/app/services/virtual_user_service.py
yuqianqian10204095yu cebc0a288f 1.0.0初始化源代码
2026-03-23 15:40:36 +08:00

362 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
虚拟用户管理服务
"""
import logging
from typing import Optional, List, Dict, Any
from datetime import datetime, timedelta
from sqlalchemy.orm import Session
from sqlalchemy import and_, func, Date
from app.models.virtual_user import VirtualUser, VirtualUserPersona, ActivityLevel, UserStatus
from app.models.interaction import InteractionRecord, InteractionType
from app.services.ai_service import ai_service
from app.core.config import settings
logger = logging.getLogger(__name__)
class VirtualUserService:
"""虚拟用户服务类"""
# 预设写作风格库
WRITING_STYLES = [
"幽默风趣",
"严肃理性",
"文艺清新",
"吐槽犀利",
"感性温暖",
"客观中立",
"激情澎湃",
"冷静分析",
"活泼可爱",
"深沉内敛"
]
# 昵称前缀和后缀
NICKNAME_PREFIXES = ["清风", "星辰", "云端", "晨曦", "暮色", "流年", "初心", "远方"]
NICKNAME_SUFFIXES = ["行者", "旅人", "追梦", "时光", "记忆", "印象", "故事", "传奇"]
def __init__(self, db: Session):
self.db = db
def get_user_by_id(self, user_id: int) -> Optional[VirtualUser]:
"""根据 ID 获取用户"""
return self.db.query(VirtualUser).filter(VirtualUser.id == user_id).first()
def get_user_by_username(self, username: str) -> Optional[VirtualUser]:
"""根据用户名获取用户"""
return self.db.query(VirtualUser).filter(VirtualUser.username == username).first()
def get_users(
self,
page: int = 1,
page_size: int = 20,
status: Optional[UserStatus] = None,
search: Optional[str] = None
) -> Dict[str, Any]:
"""
获取用户列表
:param page: 页码
:param page_size: 每页数量
:param status: 状态筛选
:param search: 搜索关键词
:return: 用户列表和总数
"""
query = self.db.query(VirtualUser)
if status:
query = query.filter(VirtualUser.status == status)
if search:
query = query.filter(
or_(
VirtualUser.nickname.like(f"%{search}%"),
VirtualUser.username.like(f"%{search}%")
)
)
total = query.count()
users = query.order_by(VirtualUser.created_at.desc()).offset(
(page - 1) * page_size
).limit(page_size).all()
return {"total": total, "items": users}
def create_user(
self,
username: str,
password: str,
nickname: str,
writing_style: Optional[str] = None,
activity_level: ActivityLevel = ActivityLevel.MEDIUM,
avatar_url: Optional[str] = None,
persona_description: Optional[str] = None
) -> Optional[VirtualUser]:
"""
创建虚拟用户
:param username: 用户名
:param password: 密码
:param nickname: 昵称
:param writing_style: 写作风格
:param activity_level: 活跃度
:param avatar_url: 头像 URL
:param persona_description: 人格描述
:return: 创建的用户
"""
# 检查用户名是否已存在
existing = self.get_user_by_username(username)
if existing:
logger.error(f"Username already exists: {username}")
return None
user = VirtualUser(
username=username,
password=password, # TODO: 加密存储
nickname=nickname,
writing_style=writing_style or self._random_writing_style(),
activity_level=activity_level,
avatar_url=avatar_url or self._generate_avatar_url(),
persona_description=persona_description
)
self.db.add(user)
self.db.commit()
self.db.refresh(user)
logger.info(f"Virtual user created: {username}")
return user
def generate_users(
self,
count: int,
writing_styles: Optional[List[str]] = None,
activity_levels: Optional[List[ActivityLevel]] = None,
generate_persona: bool = True
) -> List[VirtualUser]:
"""
批量生成虚拟用户
:param count: 生成数量
:param writing_styles: 写作风格列表
:param activity_levels: 活跃度级别列表
:param generate_persona: 是否生成 AI 人格描述
:return: 生成的用户列表
"""
import random
styles = writing_styles or self.WRITING_STYLES
levels = activity_levels or [ActivityLevel.LOW, ActivityLevel.MEDIUM, ActivityLevel.HIGH]
created_users = []
for i in range(count):
# 生成唯一用户名
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
username = f"user_{timestamp}_{i}"
# 随机生成昵称
prefix = random.choice(self.NICKNAME_PREFIXES)
suffix = random.choice(self.NICKNAME_SUFFIXES)
nickname = f"{prefix}{suffix}{random.randint(100, 999)}"
# 随机密码
password = f"pwd_{random.randint(100000, 999999)}"
# 随机写作风格
writing_style = random.choice(styles)
# 随机活跃度
activity_level = random.choice(levels)
# 生成头像
avatar_url = self._generate_avatar_url()
# AI 生成人格描述
persona_description = None
if generate_persona:
persona_description = self._generate_persona_description(
writing_style,
activity_level
)
user = self.create_user(
username=username,
password=password,
nickname=nickname,
writing_style=writing_style,
activity_level=activity_level,
avatar_url=avatar_url,
persona_description=persona_description
)
if user:
created_users.append(user)
logger.info(f"Generated {len(created_users)} virtual users")
return created_users
def _generate_persona_description(
self,
writing_style: str,
activity_level: ActivityLevel
) -> str:
"""AI 生成人格描述"""
import asyncio
prompt = f"""请为一位虚拟用户生成人格描述,要求:
- 写作风格:{writing_style}
- 活跃度:{activity_level.value}
请用 50-100 字描述这个人的性格特点、兴趣爱好、说话方式等。直接输出描述内容。"""
try:
# 同步调用异步方法
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
result = loop.run_until_complete(ai_service._call_ai_api(prompt))
loop.close()
if result and result.get("content"):
return result["content"]
except Exception as e:
logger.error(f"Generate persona description error: {e}")
return f"这是一位{writing_style}的虚拟用户,活跃度{activity_level.value}"
def _random_writing_style(self) -> str:
"""随机选择写作风格"""
import random
return random.choice(self.WRITING_STYLES)
def _generate_avatar_url(self) -> str:
"""生成随机头像 URL使用第三方头像 API"""
import random
# 使用 DiceBear 头像 API
seed = f"avatar_{datetime.now().timestamp()}_{random.randint(1000, 9999)}"
return f"https://api.dicebear.com/7.x/avataaars/svg?seed={seed}"
def update_user(
self,
user_id: int,
**kwargs
) -> Optional[VirtualUser]:
"""更新用户信息"""
user = self.get_user_by_id(user_id)
if not user:
return None
for key, value in kwargs.items():
if hasattr(user, key) and value is not None:
setattr(user, key, value)
self.db.commit()
self.db.refresh(user)
return user
def delete_user(self, user_id: int) -> bool:
"""删除用户"""
user = self.get_user_by_id(user_id)
if not user:
return False
self.db.delete(user)
self.db.commit()
logger.info(f"Virtual user deleted: {user_id}")
return True
def import_users_from_excel(
self,
users_data: List[Dict[str, Any]],
generate_persona: bool = True
) -> Dict[str, Any]:
"""
从 Excel 导入虚拟用户
:param users_data: 用户数据列表
:param generate_persona: 是否生成 AI 人格描述
:return: 导入结果
"""
success_count = 0
failed_count = 0
created_users = []
for user_data in users_data:
try:
username = user_data.get("username")
password = user_data.get("password")
nickname = user_data.get("nickname", "")
if not username or not password:
logger.warning(f"Missing username or password: {user_data}")
failed_count += 1
continue
# 如果昵称为空,生成一个
if not nickname:
nickname = f"用户{username}"
writing_style = user_data.get("writing_style")
activity_level_str = user_data.get("activity_level", "medium")
# 转换活跃度枚举
try:
activity_level = ActivityLevel(activity_level_str.lower())
except ValueError:
activity_level = ActivityLevel.MEDIUM
user = self.create_user(
username=username,
password=password,
nickname=nickname,
writing_style=writing_style,
activity_level=activity_level
)
if user:
success_count += 1
created_users.append(user)
else:
failed_count += 1
except Exception as e:
logger.error(f"Import user error: {e}")
failed_count += 1
return {
"success_count": success_count,
"failed_count": failed_count,
"created_users": created_users
}
def get_user_stats(self, user_id: int) -> Dict[str, Any]:
"""获取用户统计信息"""
user = self.get_user_by_id(user_id)
if not user:
return {}
today = datetime.now().date()
# 统计今日互动
today_interactions = self.db.query(InteractionRecord).filter(
and_(
InteractionRecord.virtual_user_id == user_id,
func.date(InteractionRecord.execution_time) == today
)
).all()
today_comments = sum(1 for i in today_interactions if i.interaction_type == InteractionType.COMMENT)
today_replies = sum(1 for i in today_interactions if i.interaction_type == InteractionType.REPLY)
return {
"user_id": user_id,
"nickname": user.nickname,
"total_interactions": user.total_interactions,
"today_comments": today_comments,
"today_replies": today_replies,
"last_interaction_time": user.last_interaction_time
}
# 工厂函数
def get_virtual_user_service(db: Session) -> VirtualUserService:
"""获取虚拟用户服务实例"""
return VirtualUserService(db)