Files
offerpai_python_ai/app/services/func_permission_service.py
2026-05-20 18:00:12 +08:00

83 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
from app.tool.snowflake import next_id
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(id=next_id(), user_id=user_id, func_code=func_code)
self.session.add(usage_log)
await self.session.flush()
return usage_log.id