51521fc0dd
- 微信支付从 APIv3 降级为 APIv2(MD5/XML) - 积分系统:充值下单、微信回调、消费冻结/结算/退款 - SMS B2M 短信验证码服务 - 双 Token 认证(Access 30min + Refresh 30days) - SSE 单设备踢人 - 用户设备管理、积分账户模型 - Alembic 迁移脚本
129 lines
3.3 KiB
Python
129 lines
3.3 KiB
Python
"""
|
|
积分批次 CRUD
|
|
=============
|
|
|
|
核心操作:按过期时间升序查询批次、扣减剩余积分、冻结/解冻、过期扫描。
|
|
"""
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.crud.base import CRUDBase
|
|
from app.models.point_batch import PointBatch
|
|
|
|
|
|
class PointBatchCRUD(CRUDBase[PointBatch]):
|
|
"""积分批次数据访问对象"""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__(PointBatch)
|
|
|
|
async def get_by_user_id(
|
|
self, db: AsyncSession, *, user_id: str
|
|
) -> list[PointBatch]:
|
|
"""根据用户 ID 获取所有批次(按过期时间升序,FIFO)"""
|
|
result = await db.execute(
|
|
select(PointBatch)
|
|
.where(PointBatch.user_id == user_id)
|
|
.order_by(PointBatch.expired_at.asc())
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
async def get_available_batches(
|
|
self, db: AsyncSession, *, user_id: str
|
|
) -> list[PointBatch]:
|
|
"""
|
|
获取用户可用的批次(remaining + frozen > 0)。
|
|
|
|
按过期时间升序排列,用于 FIFO 扣减。
|
|
"""
|
|
result = await db.execute(
|
|
select(PointBatch)
|
|
.where(
|
|
PointBatch.user_id == user_id,
|
|
PointBatch.remaining + PointBatch.frozen > 0,
|
|
)
|
|
.order_by(PointBatch.expired_at.asc())
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
async def get_expired_batches(
|
|
self, db: AsyncSession, *, limit: int = 1000
|
|
) -> list[PointBatch]:
|
|
"""
|
|
获取已过期的批次(remaining > 0)。
|
|
|
|
用于定时任务扫描过期积分。
|
|
"""
|
|
from datetime import UTC, datetime
|
|
|
|
result = await db.execute(
|
|
select(PointBatch)
|
|
.where(
|
|
PointBatch.expired_at <= datetime.now(UTC),
|
|
PointBatch.remaining > 0,
|
|
)
|
|
.limit(limit)
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
async def deduct_from_batch(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
batch_id: str,
|
|
points: int,
|
|
) -> PointBatch:
|
|
"""
|
|
从指定批次扣减剩余积分。
|
|
|
|
用于消费结算时真正扣减批次积分。
|
|
"""
|
|
batch = await self.get(db, id=batch_id)
|
|
batch.remaining -= points
|
|
await db.commit()
|
|
await db.refresh(batch)
|
|
return batch
|
|
|
|
async def freeze_in_batch(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
batch_id: str,
|
|
points: int,
|
|
) -> PointBatch:
|
|
"""
|
|
在指定批次冻结积分。
|
|
|
|
remaining 减少,frozen 增加。
|
|
"""
|
|
batch = await self.get(db, id=batch_id)
|
|
batch.remaining -= points
|
|
batch.frozen += points
|
|
await db.commit()
|
|
await db.refresh(batch)
|
|
return batch
|
|
|
|
async def unfreeze_in_batch(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
batch_id: str,
|
|
points: int,
|
|
) -> PointBatch:
|
|
"""
|
|
在指定批次解冻积分(返还)。
|
|
|
|
frozen 减少,remaining 增加。
|
|
"""
|
|
batch = await self.get(db, id=batch_id)
|
|
batch.frozen -= points
|
|
batch.remaining += points
|
|
await db.commit()
|
|
await db.refresh(batch)
|
|
return batch
|
|
|
|
|
|
# 导出实例
|
|
point_batch = PointBatchCRUD()
|