1.0.0初始化源代码

This commit is contained in:
yuqianqian10204095yu
2026-03-23 15:40:36 +08:00
parent f13ecb3bba
commit cebc0a288f
53 changed files with 5300 additions and 0 deletions

View File

@@ -0,0 +1,361 @@
"""
虚拟用户管理服务
"""
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)