baoxiang/backend/app/services/user_service.py

400 lines
13 KiB
Python
Raw Normal View History

2025-12-16 18:06:50 +08:00
"""
用户服务
"""
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, desc
from typing import List, Optional, Tuple
from ..models.user import User, Transaction, UserRole, UserStatus
from ..schemas.user import UserCreate, UserUpdate, ChangePasswordRequest
from ..core.security import get_password_hash, verify_password
from ..utils.redis import redis_client
from datetime import datetime
2025-12-16 19:03:48 +08:00
from ..services.system_service import SystemService
from ..models.system import ConfigType
2025-12-16 18:06:50 +08:00
class UserService:
"""用户服务"""
@staticmethod
def create_user(db: Session, user_data: UserCreate) -> User:
"""
创建用户
"""
# 检查用户名和邮箱是否已存在
existing_user = db.query(User).filter(
(User.username == user_data.username) | (User.email == user_data.email)
).first()
if existing_user:
raise ValueError("用户名或邮箱已存在")
# 检查密码长度(虽然前端已检查,但后端也要验证)
if len(user_data.password) > 72:
raise ValueError("密码长度不能超过72个字符")
# 创建用户
hashed_password = get_password_hash(user_data.password)
db_user = User(
username=user_data.username,
email=user_data.email,
hashed_password=hashed_password,
)
db.add(db_user)
db.commit() # 先提交用户获取ID
db.refresh(db_user) # 刷新对象获取自增ID
# 记录注册奖励交易
2025-12-16 21:39:32 +08:00
# 从数据库动态读取新用户奖励
from ..services.system_service import ConfigManager
new_user_reward = ConfigManager.get_new_user_reward(db)
2025-12-16 18:06:50 +08:00
db.add(Transaction(
user_id=db_user.id,
type="注册奖励",
2025-12-16 21:39:32 +08:00
amount=new_user_reward,
2025-12-16 18:06:50 +08:00
balance_after=db_user.balance,
description="新用户注册奖励"
))
db.commit()
return db_user
@staticmethod
def authenticate_user(db: Session, username: str, password: str) -> User:
"""
验证用户
"""
user = db.query(User).filter(User.username == username).first()
if not user or not verify_password(password, user.hashed_password):
return None
return user
@staticmethod
def get_user_by_id(db: Session, user_id: int) -> User:
"""
根据ID获取用户
"""
return db.query(User).filter(User.id == user_id).first()
@staticmethod
def get_user_by_username(db: Session, username: str) -> User:
"""
根据用户名获取用户
"""
return db.query(User).filter(User.username == username).first()
@staticmethod
def update_user(db: Session, user: User, user_data: UserUpdate) -> User:
"""
更新用户信息
"""
update_data = user_data.dict(exclude_unset=True)
# 如果更新邮箱,检查邮箱是否已存在
if "email" in update_data and update_data["email"] != user.email:
existing = db.query(User).filter(
User.email == update_data["email"],
User.id != user.id
).first()
if existing:
raise ValueError("邮箱已存在")
for field, value in update_data.items():
setattr(user, field, value)
db.commit()
db.refresh(user)
return user
@staticmethod
def get_user_list(
db: Session,
skip: int = 0,
limit: int = 20,
search: Optional[str] = None,
role: Optional[str] = None,
status: Optional[str] = None,
sort_by: str = "created_at",
order: str = "desc"
) -> List[User]:
"""
获取用户列表管理员
"""
query = db.query(User)
# 搜索过滤
if search:
query = query.filter(
or_(
User.username.like(f"%{search}%"),
User.email.like(f"%{search}%"),
User.nickname.like(f"%{search}%")
)
)
# 角色过滤
if role:
query = query.filter(User.role == role)
# 状态过滤
if status:
query = query.filter(User.status == status)
# 排序
sort_field = getattr(User, sort_by, User.created_at)
if order.lower() == "desc":
query = query.order_by(desc(sort_field))
else:
query = query.order_by(sort_field)
return query.offset(skip).limit(limit).all()
@staticmethod
def adjust_balance_with_version(
db: Session,
user: User,
amount: int,
description: str,
admin_user: User,
expected_version: Optional[int] = None
) -> bool:
"""
调整用户余额使用乐观锁
"""
2025-12-16 19:03:48 +08:00
# 记录变更前的余额
old_balance = user.balance
2025-12-16 18:06:50 +08:00
# 如果指定了版本号,进行版本检查
if expected_version is not None and user.version != expected_version:
raise ValueError("用户数据已更新,请刷新后重试")
# 更新余额和版本
user.balance += amount
user.version += 1
# 通知用户余额更新
import asyncio
try:
# 尝试获取当前事件循环
loop = asyncio.get_running_loop()
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
loop.create_task(notify_user_balance_update(user.id, user.balance))
except RuntimeError:
# 如果没有运行中的事件循环,则在新线程中处理
import threading
def run_async():
async def _run():
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
await notify_user_balance_update(user.id, user.balance)
asyncio.run(_run())
thread = threading.Thread(target=run_async, daemon=True)
thread.start()
# 记录交易
transaction = Transaction(
user_id=user.id,
type="管理员调整",
amount=amount,
balance_after=user.balance,
description=f"{description} (操作人: {admin_user.username})"
)
db.add(transaction)
db.add(user)
db.commit()
2025-12-16 19:03:48 +08:00
# 余额变更后,触发余额监控服务
from ..services.balance_monitor_service import BalanceMonitorService
BalanceMonitorService.on_balance_changed(db, user, old_balance, user.balance)
2025-12-16 18:06:50 +08:00
return True
@staticmethod
def create_transaction(
db: Session,
user_id: int,
transaction_type: str,
amount: int,
balance_after: int,
description: str,
related_id: Optional[int] = None
) -> Transaction:
"""
创建交易记录
"""
transaction = Transaction(
user_id=user_id,
type=transaction_type,
amount=amount,
balance_after=balance_after,
description=description,
related_id=related_id
)
db.add(transaction)
db.commit()
db.refresh(transaction)
return transaction
@staticmethod
def get_user_transactions(
db: Session,
user_id: int,
skip: int = 0,
limit: int = 50
) -> List[Transaction]:
"""
获取用户交易记录
"""
return db.query(Transaction).filter(
Transaction.user_id == user_id
).order_by(desc(Transaction.created_at)).offset(skip).limit(limit).all()
@staticmethod
def get_user_transactions_paginated(
db: Session,
user_id: int,
skip: int = 0,
limit: int = 20
) -> List[Transaction]:
"""
分页获取用户交易记录用于管理员界面
"""
return db.query(Transaction).filter(
Transaction.user_id == user_id
).order_by(desc(Transaction.created_at)).offset(skip).limit(limit).all()
@staticmethod
def claim_daily_allowance(db: Session, user: User) -> bool:
"""
领取每日低保
"""
today = datetime.now().date()
allowance_key = f"daily_allowance:{user.id}:{today.isoformat()}"
2025-12-16 19:03:48 +08:00
2025-12-16 18:06:50 +08:00
# 检查今日是否已领取
if redis_client.exists(allowance_key):
return False
2025-12-16 19:03:48 +08:00
# 从数据库获取低保金额
daily_allowance = UserService.get_daily_allowance(db)
2025-12-16 18:06:50 +08:00
# 发放低保
2025-12-16 19:03:48 +08:00
user.balance += daily_allowance
2025-12-16 18:06:50 +08:00
user.version += 1
db.commit()
2025-12-16 19:03:48 +08:00
2025-12-16 18:06:50 +08:00
# 通知用户余额更新
import asyncio
try:
# 尝试获取当前事件循环
loop = asyncio.get_running_loop()
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
loop.create_task(notify_user_balance_update(user.id, user.balance))
except RuntimeError:
# 如果没有运行中的事件循环,则在新线程中处理
import threading
def run_async():
async def _run():
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
await notify_user_balance_update(user.id, user.balance)
asyncio.run(_run())
thread = threading.Thread(target=run_async, daemon=True)
thread.start()
# 记录交易
UserService.create_transaction(
db=db,
user_id=user.id,
transaction_type="低保",
2025-12-16 19:03:48 +08:00
amount=daily_allowance,
2025-12-16 18:06:50 +08:00
balance_after=user.balance,
description="每日低保"
)
# 设置领取标记24小时过期
redis_client.setex(allowance_key, 86400, "claimed")
return True
@staticmethod
def get_rich_ranking(db: Session, limit: int = 10) -> List[User]:
"""
获取富豪榜
"""
return db.query(User).filter(
User.is_active == True
).order_by(desc(User.balance)).limit(limit).all()
@staticmethod
def get_next_allowance_time(db: Session, user: User) -> dict:
"""
获取下次低保领取时间
"""
from datetime import datetime, timedelta
today = datetime.now().date()
allowance_key = f"daily_allowance:{user.id}:{today.isoformat()}"
2025-12-16 19:03:48 +08:00
# 从数据库获取低保金额
daily_allowance = UserService.get_daily_allowance(db)
2025-12-16 18:06:50 +08:00
# 检查今日是否已领取
if redis_client.exists(allowance_key):
# 如果今天已领取,下次领取时间为明天
tomorrow = today + timedelta(days=1)
next_claim_time = datetime.combine(tomorrow, datetime.min.time())
return {
"can_claim": False,
"next_claim_time": next_claim_time.isoformat(),
2025-12-16 19:03:48 +08:00
"daily_allowance": daily_allowance
2025-12-16 18:06:50 +08:00
}
else:
# 如果今天未领取,可以立即领取
return {
"can_claim": True,
"next_claim_time": datetime.now().isoformat(),
2025-12-16 19:03:48 +08:00
"daily_allowance": daily_allowance
2025-12-16 18:06:50 +08:00
}
2025-12-16 19:03:48 +08:00
@staticmethod
def get_daily_allowance(db: Session) -> int:
"""
从数据库获取每日低保金额
"""
2025-12-16 21:39:32 +08:00
# 使用 ConfigManager 从数据库动态读取
from ..services.system_service import ConfigManager
return ConfigManager.get_daily_allowance(db)
2025-12-16 19:03:48 +08:00
@staticmethod
def get_balance_zero_reward(db: Session) -> int:
"""
从数据库获取余额清零自动发放金额
"""
config = SystemService.get_config(db, "BALANCE_ZERO_REWARD_AMOUNT")
if config:
typed_value = SystemService.get_typed_value(config)
return int(typed_value)
# 如果数据库中没有配置返回默认值10000分
return 10000
@staticmethod
def get_allowance_reset_time(db: Session) -> str:
"""
从数据库获取低保每日刷新时间
"""
config = SystemService.get_config(db, "ALLOWANCE_RESET_TIME")
if config:
return config.config_value
# 如果数据库中没有配置,返回默认值
return "00:00"
2025-12-16 21:39:32 +08:00
@staticmethod
def get_admin_user(db: Session) -> Optional[User]:
"""
获取管理员账户
"""
return db.query(User).filter(
User.role == UserRole.ADMIN
).first()