04e467e433
后端: - 微信回调 db.commit 失败仍返回 SUCCESS,避免无限重试 - recharge() 加 order_id 幂等保护,防重复充值 - time_expire 使用北京时间(UTC+8),修复时区 bug - 充值档位后端配置化(points-config.yaml + /recharge-options API) - 代码审查 20 项修复(认证加固、扣费顺序、错误响应、状态同步等) 前端: - 充值弹窗:自动轮询 + 【我已支付】手动兜底 - 二维码倒计时显示,过期后遮罩 + 刷新按钮 - 充值档位从后端动态加载 - 去掉 select/qrcode 弹窗标题,金额红色突出显示 - 全项目命名统一(视频生成/压制成片/配音合成/声音复刻等) - Modal 关闭按钮独立于 title 显示
101 lines
3.1 KiB
Python
101 lines
3.1 KiB
Python
"""
|
|
积分流水 CRUD
|
|
=============
|
|
|
|
只增不改,用于审计和对账。
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.crud.base import CRUDBase
|
|
from app.models.point_transaction import PointTransaction
|
|
|
|
|
|
class PointTransactionCRUD(CRUDBase[PointTransaction]):
|
|
"""积分流水数据访问对象"""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__(PointTransaction)
|
|
|
|
async def get_by_user_id(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
user_id: str,
|
|
skip: int = 0,
|
|
limit: int = 50,
|
|
tx_type: str | None = None,
|
|
category: str | None = None,
|
|
start_time: datetime | None = None,
|
|
end_time: datetime | None = None,
|
|
) -> list[PointTransaction]:
|
|
"""根据用户 ID 获取流水记录(支持筛选和分页,按时间倒序)"""
|
|
stmt = select(PointTransaction).where(PointTransaction.user_id == user_id)
|
|
|
|
if tx_type:
|
|
stmt = stmt.where(PointTransaction.type == tx_type)
|
|
if category:
|
|
stmt = stmt.where(PointTransaction.category == category)
|
|
if start_time:
|
|
stmt = stmt.where(PointTransaction.created_at >= start_time)
|
|
if end_time:
|
|
stmt = stmt.where(PointTransaction.created_at <= end_time)
|
|
|
|
stmt = stmt.order_by(PointTransaction.created_at.desc()).offset(skip).limit(limit)
|
|
result = await db.execute(stmt)
|
|
return list(result.scalars().all())
|
|
|
|
async def count_by_user_id(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
user_id: str,
|
|
tx_type: str | None = None,
|
|
category: str | None = None,
|
|
start_time: datetime | None = None,
|
|
end_time: datetime | None = None,
|
|
) -> int:
|
|
"""根据筛选条件统计流水记录总数"""
|
|
from sqlalchemy import func
|
|
|
|
stmt = select(func.count(PointTransaction.id)).where(PointTransaction.user_id == user_id)
|
|
|
|
if tx_type:
|
|
stmt = stmt.where(PointTransaction.type == tx_type)
|
|
if category:
|
|
stmt = stmt.where(PointTransaction.category == category)
|
|
if start_time:
|
|
stmt = stmt.where(PointTransaction.created_at >= start_time)
|
|
if end_time:
|
|
stmt = stmt.where(PointTransaction.created_at <= end_time)
|
|
|
|
result = await db.execute(stmt)
|
|
return result.scalar() or 0
|
|
|
|
async def get_by_source(
|
|
self,
|
|
db: AsyncSession,
|
|
*,
|
|
user_id: str,
|
|
source_type: str,
|
|
source_id: str,
|
|
) -> list[PointTransaction]:
|
|
"""根据消费来源查询流水(用于查询某次 AI 调用的扣费记录)"""
|
|
result = await db.execute(
|
|
select(PointTransaction)
|
|
.where(
|
|
PointTransaction.user_id == user_id,
|
|
PointTransaction.source_type == source_type,
|
|
PointTransaction.source_id == source_id,
|
|
)
|
|
.order_by(PointTransaction.created_at.desc())
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
# 导出实例
|
|
point_transaction = PointTransactionCRUD()
|