82 lines
3.8 KiB
Python
82 lines
3.8 KiB
Python
"""功能权限 Service
|
|
|
|
校验用户功能权限并扣减库存,业务异常时回退。
|
|
逻辑与 Java 端 FuncPermissionService 完全一致。
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
from fastapi import HTTPException
|
|
from sqlalchemy import select, func, update, delete
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.models.func_permission import FuncPermission
|
|
from app.models.user_func_permission_stock import UserFuncPermissionStock
|
|
from app.models.user_func_usage_log import UserFuncUsageLog
|
|
|
|
|
|
class FuncPermissionService:
|
|
|
|
def __init__(self, session: AsyncSession):
|
|
self.session = session
|
|
|
|
async def check_and_deduct(self, user_id: int, func_code: str) -> int:
|
|
"""校验权限 + 扣减库存,返回使用记录ID"""
|
|
|
|
# 1. 查功能权限定义
|
|
result = await self.session.execute(select(FuncPermission).where(FuncPermission.func_code == func_code, FuncPermission.status == 1, FuncPermission.is_delete == 0))
|
|
perm = result.scalar_one_or_none()
|
|
|
|
if perm is None:
|
|
raise HTTPException(status_code=403, detail="功能不存在或未启用")
|
|
|
|
# 2. 判断每日免费额度
|
|
daily_free = perm.daily_free_count or 0
|
|
if daily_free > 0:
|
|
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
# noinspection PyTypeChecker
|
|
result = await self.session.execute(select(func.count()).select_from(UserFuncUsageLog).where(UserFuncUsageLog.user_id == user_id, UserFuncUsageLog.func_code == func_code, UserFuncUsageLog.create_time >= today_start,))
|
|
today_used = result.scalar() or 0
|
|
|
|
if today_used < daily_free:
|
|
return await self._insert_usage_log(user_id, func_code)
|
|
|
|
# 3. 查付费库存
|
|
result = await self.session.execute(select(UserFuncPermissionStock).where(UserFuncPermissionStock.user_id == user_id, UserFuncPermissionStock.func_code == func_code))
|
|
stock = result.scalar_one_or_none()
|
|
if stock is None:
|
|
raise HTTPException(status_code=403, detail="无该功能权限")
|
|
|
|
# 4. 时间维度校验
|
|
if stock.time_limit == 1 and stock.expire_time is not None:
|
|
if stock.expire_time < datetime.now():
|
|
raise HTTPException(status_code=403, detail="功能权限已过期")
|
|
|
|
# 5. 次数维度校验
|
|
if stock.count_limit == 0:
|
|
return await self._insert_usage_log(user_id, func_code)
|
|
|
|
# 限次,SQL 原子扣减
|
|
result = await self.session.execute(update(UserFuncPermissionStock).where(UserFuncPermissionStock.user_id == user_id, UserFuncPermissionStock.func_code == func_code, UserFuncPermissionStock.remain_count > 0).values(remain_count=UserFuncPermissionStock.remain_count - 1))
|
|
|
|
if result.rowcount == 0:
|
|
raise HTTPException(status_code=403, detail="功能使用次数已用完")
|
|
|
|
return await self._insert_usage_log(user_id, func_code)
|
|
|
|
async def rollback_usage(self, log_id: int, user_id: int, func_code: str) -> None:
|
|
"""回退使用记录 + 库存"""
|
|
# 删除使用记录
|
|
await self.session.execute(delete(UserFuncUsageLog).where(UserFuncUsageLog.id == log_id))
|
|
|
|
# 尝试回退库存(count_limit=1 才会匹配)
|
|
await self.session.execute(update(UserFuncPermissionStock).where(UserFuncPermissionStock.user_id == user_id, UserFuncPermissionStock.func_code == func_code, UserFuncPermissionStock.count_limit == 1).values(remain_count=UserFuncPermissionStock.remain_count + 1))
|
|
|
|
async def _insert_usage_log(self, user_id: int, func_code: str) -> int:
|
|
"""插入使用记录,返回记录ID"""
|
|
usage_log = UserFuncUsageLog(user_id=user_id, func_code=func_code)
|
|
self.session.add(usage_log)
|
|
await self.session.flush()
|
|
return usage_log.id
|