""" 虚拟用户管理服务 """ 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)