180 lines
7.2 KiB
Python
180 lines
7.2 KiB
Python
from typing import Optional
|
||
from backend.app.admin.crud.points_crud import points_dao, points_log_dao
|
||
from backend.app.admin.model.points import Points
|
||
from backend.database.db import async_db_session
|
||
|
||
|
||
class PointsService:
|
||
@staticmethod
|
||
async def get_user_points(user_id: int) -> Optional[Points]:
|
||
"""
|
||
获取用户积分账户信息(会检查并清空过期积分)
|
||
"""
|
||
async with async_db_session.begin() as db:
|
||
# 获取当前积分余额(清空前)
|
||
points_account_before = await points_dao.get_by_user_id(db, user_id)
|
||
balance_before = points_account_before.balance if points_account_before else 0
|
||
|
||
# 检查并清空过期积分
|
||
expired_cleared = await points_dao.check_and_clear_expired_points(db, user_id)
|
||
|
||
# 如果清空了过期积分,记录日志
|
||
if expired_cleared and balance_before > 0:
|
||
await points_log_dao.add_log(db, {
|
||
"user_id": user_id,
|
||
"action": "expire_clear",
|
||
"amount": balance_before, # 记录清空前的积分数量
|
||
"balance_after": 0,
|
||
"details": {"message": "过期积分已清空", "cleared_amount": balance_before}
|
||
})
|
||
|
||
return await points_dao.get_by_user_id(db, user_id)
|
||
|
||
@staticmethod
|
||
async def get_user_balance(user_id: int) -> int:
|
||
"""
|
||
获取用户积分余额(会检查并清空过期积分)
|
||
"""
|
||
async with async_db_session.begin() as db:
|
||
# 获取当前积分余额(清空前)
|
||
points_account_before = await points_dao.get_by_user_id(db, user_id)
|
||
balance_before = points_account_before.balance if points_account_before else 0
|
||
|
||
# 检查并清空过期积分
|
||
expired_cleared = await points_dao.check_and_clear_expired_points(db, user_id)
|
||
|
||
# 如果清空了过期积分,记录日志
|
||
if expired_cleared and balance_before > 0:
|
||
await points_log_dao.add_log(db, {
|
||
"user_id": user_id,
|
||
"action": "expire_clear",
|
||
"balance_before": balance_before,
|
||
"balance_after": 0,
|
||
"details": {"message": "过期积分已清空", "cleared_amount": balance_before}
|
||
})
|
||
|
||
return await points_dao.get_balance(db, user_id)
|
||
|
||
@staticmethod
|
||
async def add_points(user_id: int, amount: int, extend_expiration: bool = False, related_id: Optional[int] = None, details: Optional[dict] = None) -> bool:
|
||
"""
|
||
为用户增加积分
|
||
|
||
Args:
|
||
user_id: 用户ID
|
||
amount: 增加的积分数量
|
||
extend_expiration: 是否自动延期过期时间
|
||
related_id: 关联ID(可选)
|
||
details: 附加信息(可选)
|
||
|
||
Returns:
|
||
bool: 是否成功
|
||
"""
|
||
if amount <= 0:
|
||
raise ValueError("积分数量必须大于0")
|
||
|
||
async with async_db_session.begin() as db:
|
||
# 获取当前余额以记录日志
|
||
points_account = await points_dao.get_by_user_id(db, user_id)
|
||
if not points_account:
|
||
points_account = await points_dao.create_user_points(db, user_id)
|
||
|
||
current_balance = points_account.balance
|
||
|
||
# 原子性增加积分(可能延期过期时间)
|
||
result = await points_dao.add_points_atomic(db, user_id, amount, extend_expiration)
|
||
if not result:
|
||
return False
|
||
|
||
# 准备日志详情
|
||
log_details = details or {}
|
||
if extend_expiration:
|
||
log_details["expiration_extended"] = True
|
||
log_details["extension_days"] = 30
|
||
|
||
# 记录积分变动日志
|
||
new_balance = current_balance + amount
|
||
await points_log_dao.add_log(db, {
|
||
"user_id": user_id,
|
||
"action": "earn",
|
||
"amount": amount,
|
||
"balance_after": new_balance,
|
||
"related_id": related_id,
|
||
"details": log_details
|
||
})
|
||
|
||
return True
|
||
|
||
@staticmethod
|
||
async def deduct_points(user_id: int, amount: int, related_id: Optional[int] = None, details: Optional[dict] = None) -> bool:
|
||
"""
|
||
扣减用户积分(会检查并清空过期积分)
|
||
|
||
Args:
|
||
user_id: 用户ID
|
||
amount: 扣减的积分数量
|
||
related_id: 关联ID(可选)
|
||
details: 附加信息(可选)
|
||
|
||
Returns:
|
||
bool: 是否成功
|
||
"""
|
||
if amount <= 0:
|
||
raise ValueError("积分数量必须大于0")
|
||
|
||
async with async_db_session.begin() as db:
|
||
# 获取当前积分余额(清空前)
|
||
points_account_before = await points_dao.get_by_user_id(db, user_id)
|
||
if not points_account_before:
|
||
return False
|
||
|
||
balance_before = points_account_before.balance
|
||
|
||
# 检查并清空过期积分
|
||
expired_cleared = await points_dao.check_and_clear_expired_points(db, user_id)
|
||
|
||
# 如果清空了过期积分,记录日志
|
||
if expired_cleared and balance_before > 0:
|
||
await points_log_dao.add_log(db, {
|
||
"user_id": user_id,
|
||
"action": "expire_clear",
|
||
"amount": balance_before, # 记录清空前的积分数量
|
||
"balance_after": 0,
|
||
"details": {"message": "过期积分已清空", "cleared_amount": balance_before}
|
||
})
|
||
|
||
# 重新获取账户信息(可能已被清空)
|
||
points_account = await points_dao.get_by_user_id(db, user_id)
|
||
if not points_account or points_account.balance < amount:
|
||
return False
|
||
|
||
current_balance = points_account.balance
|
||
|
||
# 原子性扣减积分
|
||
result = await points_dao.deduct_points_atomic(db, user_id, amount)
|
||
if not result:
|
||
return False
|
||
|
||
# 记录积分变动日志
|
||
new_balance = current_balance - amount
|
||
await points_log_dao.add_log(db, {
|
||
"user_id": user_id,
|
||
"action": "spend",
|
||
"amount": amount,
|
||
"balance_after": new_balance,
|
||
"related_id": related_id,
|
||
"details": details
|
||
})
|
||
|
||
return True
|
||
|
||
@staticmethod
|
||
async def initialize_user_points(user_id: int) -> Points:
|
||
"""
|
||
为新用户初始化积分账户
|
||
"""
|
||
async with async_db_session.begin() as db:
|
||
points_account = await points_dao.get_by_user_id(db, user_id)
|
||
if not points_account:
|
||
points_account = await points_dao.create_user_points(db, user_id)
|
||
return points_account |