feat: 接入起卦后端流程并完善积分扣减链路

This commit is contained in:
qzl
2026-04-03 19:04:46 +08:00
parent a136e42290
commit d87b2e1e3a
56 changed files with 3310 additions and 809 deletions
+12
View File
@@ -17,6 +17,7 @@ from core.agentscope.schemas.agui_input import extract_latest_user_payload
from core.config.settings import config
from core.logging import get_logger
from schemas.agent.forwarded_props import (
parse_forwarded_props_divination_payload,
parse_forwarded_props_runtime_mode,
RuntimeMode,
)
@@ -93,11 +94,22 @@ class AgentService:
forwarded_props = getattr(run_input, "forwarded_props", None)
try:
runtime_mode = parse_forwarded_props_runtime_mode(forwarded_props)
divination_payload = parse_forwarded_props_divination_payload(
forwarded_props
)
except ValueError as exc:
raise ApiProblemError(
status_code=422,
detail=problem_payload(code="AGENT_PAYLOAD_INVALID", detail=str(exc)),
) from exc
if divination_payload is None:
raise ApiProblemError(
status_code=422,
detail=problem_payload(
code="AGENT_DIVINATION_PAYLOAD_REQUIRED",
detail="forwardedProps.divinationPayload is required",
),
)
if runtime_config is None:
from v1.agent.system_agents_config import (
+12
View File
@@ -0,0 +1,12 @@
from __future__ import annotations
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from core.db import get_db
from v1.points.repository import PointsRepository
from v1.points.service import PointsService
def get_points_service(session: AsyncSession = Depends(get_db)) -> PointsService:
return PointsService(repository=PointsRepository(session))
+11
View File
@@ -56,3 +56,14 @@ class PointsRepository:
)
self._session.add(entry)
await self._session.flush()
async def get_user_points(self, *, user_id: UUID) -> UserPoints:
insert_stmt = (
insert(UserPoints)
.values(user_id=user_id)
.on_conflict_do_nothing(index_elements=[UserPoints.user_id])
)
await self._session.execute(insert_stmt)
stmt = select(UserPoints).where(UserPoints.user_id == user_id)
return (await self._session.execute(stmt)).scalar_one()
+28
View File
@@ -0,0 +1,28 @@
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends
from core.auth.models import CurrentUser
from v1.points.dependencies import get_points_service
from v1.points.schemas import PointsBalanceResponse
from v1.points.service import PointsService
from v1.users.dependencies import get_current_user
router = APIRouter(prefix="/points", tags=["points"])
@router.get("/balance", response_model=PointsBalanceResponse)
async def get_points_balance(
service: Annotated[PointsService, Depends(get_points_service)],
current_user: Annotated[CurrentUser, Depends(get_current_user)],
) -> PointsBalanceResponse:
result = await service.get_points_balance(user_id=current_user.id)
return PointsBalanceResponse(
balance=result.balance,
frozenBalance=result.frozen_balance,
availableBalance=result.available_balance,
runCost=result.run_cost,
canRun=result.can_run,
)
+13
View File
@@ -0,0 +1,13 @@
from __future__ import annotations
from pydantic import BaseModel, ConfigDict, Field
class PointsBalanceResponse(BaseModel):
model_config = ConfigDict(populate_by_name=True, serialize_by_alias=True)
balance: int = Field(ge=0)
frozen_balance: int = Field(alias="frozenBalance", ge=0)
available_balance: int = Field(alias="availableBalance", ge=0)
run_cost: int = Field(alias="runCost", gt=0)
can_run: bool = Field(alias="canRun")
+26
View File
@@ -22,6 +22,15 @@ class RunChargeResult:
event_id: str
@dataclass(frozen=True)
class PointsBalanceResult:
balance: int
frozen_balance: int
available_balance: int
run_cost: int
can_run: bool
class PointsService:
def __init__(self, repository: PointsRepository) -> None:
self._repository = repository
@@ -51,6 +60,23 @@ class PointsService:
)
return available
async def get_points_balance(
self,
*,
user_id: UUID,
) -> PointsBalanceResult:
account = await self._repository.get_user_points(user_id=user_id)
balance = int(account.balance)
frozen_balance = int(account.frozen_balance)
available = max(balance - frozen_balance, 0)
return PointsBalanceResult(
balance=balance,
frozen_balance=frozen_balance,
available_balance=available,
run_cost=RUN_POINTS_COST,
can_run=available >= RUN_POINTS_COST,
)
async def consume_successful_run_points(
self,
*,
+2
View File
@@ -4,8 +4,10 @@ from fastapi import APIRouter
from v1.agent.router import router as agent_router
from v1.auth.router import router as auth_router
from v1.points.router import router as points_router
router = APIRouter(prefix="/api/v1")
router.include_router(auth_router)
router.include_router(agent_router)
router.include_router(points_router)