This commit is contained in:
root
2026-10-16 13:51:00 +08:00
parent 6bf949949c
commit 8fc899d0a9
3830 changed files with 11228 additions and 188377 deletions
@@ -0,0 +1,81 @@
"""积分钱包服务:统一余额变更入口(行锁 + 流水 + 乐观锁,对齐《02-技术文档》3.4)"""
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core import errors
from app.core.response import BizException
from app.models.billing import PointTransaction
from app.models.user import UserWallet
# 流水业务类型
BIZ_RECHARGE = 1 # 充值
BIZ_WATCH = 2 # 观看消耗
BIZ_COMMENT = 3 # 评论奖励
BIZ_MEMBERSHIP = 4 # 开通会员
BIZ_GIFT = 5 # 活动赠送
BIZ_ADJUST = 6 # 后台调整
BIZ_CLAWBACK = 7 # 奖励扣回/退款扣回
BIZ_REFERRAL = 8 # 推荐奖励
async def get_or_create_wallet(db: AsyncSession, user_id: int, for_update: bool = False) -> UserWallet:
stmt = select(UserWallet).where(UserWallet.user_id == user_id)
if for_update:
stmt = stmt.with_for_update()
wallet = (await db.execute(stmt)).scalar_one_or_none()
if wallet is None:
wallet = UserWallet(user_id=user_id)
db.add(wallet)
await db.flush()
if for_update:
# 新建后再加行锁,保证后续并发读到同一行
wallet = (await db.execute(stmt)).scalar_one()
return wallet
async def change_points(
db: AsyncSession,
user_id: int,
amount: int,
biz_type: int,
biz_id: str = "",
remark: str = "",
allow_negative: bool = False,
) -> int:
"""变更积分余额,返回变动后余额。
- amount 正增负减;扣减默认不允许透支(退款扣回场景传 allow_negative=True,余额可为负)
- 同事务内:行锁 → 校验/更新余额与累计 → 写流水
"""
wallet = await get_or_create_wallet(db, user_id, for_update=True)
new_balance = wallet.points + amount
if amount < 0 and new_balance < 0 and not allow_negative:
raise BizException(errors.POINTS_NOT_ENOUGH)
wallet.points = new_balance
wallet.version += 1
if amount > 0:
if biz_type == BIZ_RECHARGE:
wallet.total_recharge += amount
else:
wallet.total_earned += amount
else:
wallet.total_consumed += -amount
db.add(
PointTransaction(
user_id=user_id,
change_amount=amount,
balance_after=new_balance,
biz_type=biz_type,
biz_id=biz_id,
remark=remark,
)
)
await db.flush()
return new_balance
async def get_balance(db: AsyncSession, user_id: int) -> int:
wallet = await get_or_create_wallet(db, user_id)
return wallet.points