FinBuddy_Web 计费事务安全与支付流程
· FinBuddy_Web
今日进展
- 计费事务原子性修复:feature_billing_service.py 的 Step 5-7(余额扣除 + 日用量更新 + 流水写入)改用 begin_nested() savepoint 包裹,确保扣除和写入在同一事务中完成
- session_id 隐私脱敏:日志中只记录 session_id 前 8 字符,避免泄露完整会话标识
- frozen_reason 获取方式修正:从 hasattr 改为 getattr,更 Pythonic
- 版本比较日志增强:package_service.py 的 is_newer_version 和 meets_min_version 在 parse 失败时增加 debug 日志
- 支付前端流程:新增 payment.ts 服务、更新 Packages.vue 套餐购买页面、更新类型定义
- Auth 模块更新:认证 schemas 和 service 修改
- LLM 代理服务更新:proxy_service.py 修改
- 环境变量更新:dev/staging/prod 三套 .env 同步更新
关键代码/伪代码
计费事务原子性 — savepoint 方案
# 之前:余额扣除和流水写入不在同一事务
# 并发场景下可能出现:扣了余额但没写流水
# 严重时导致用户余额凭空减少
# 改造前(有 bug)
deducted_account = await self.account_repo.deduct_balance(user_id, actual_fc)
# 如果这里异常,余额扣了但流水没写
tx = Transaction(user_id=user_id, ...)
self.session.add(tx)
await self.session.commit()
# 改造后(savepoint 保证原子性)
# 使用 begin_nested() 而非 rollback()
# 回滚 savepoint 不影响外层 session 状态
async with self.session.begin_nested():
deducted_account = await self.account_repo.deduct_balance(user_id, actual_fc)
if deducted_account is None:
raise InsufficientBalanceException(...)
# 刷新 ORM 对象,确保读到最新数据库值
await self.session.refresh(deducted_account)
# 更新日用量/月用量
deducted_account.daily_used += actual_fc
deducted_account.monthly_used += actual_fc
# 创建交易流水
tx = Transaction(
user_id=user_id,
tx_type="consume",
feature=feature_code,
amount=-actual_fc,
balance_after=deducted_account.balance,
...
)
self.session.add(tx)
# savepoint 结束时自动 commit
# 如果任何一步失败,整个 savepoint 回滚
session_id 隐私脱敏
# 之前:完整 session_id 写入日志
logger.info("bill_feature_start",
user_id=user_id,
feature_code=feature_code) # session_id 可能在其他地方泄露
# 改造后:只记录前 8 字符
safe_session_id = session_id[:8] if session_id else None
logger.info("bill_feature_start",
user_id=user_id,
feature_code=feature_code,
session_id_prefix=safe_session_id) # 足够追踪,不泄露完整标识
遇到的问题
- 计费事务不一致:线上发现偶尔有"扣了余额没写流水"的情况,查了半天发现是并发请求时 deduct_balance 和流水写入不在同一事务。savepoint 方案解决了这个问题,而且不会破坏外层事务状态
- begin_nested() vs rollback():一开始想用 rollback 处理失败场景,但 rollback 会把整个 session 置为无效状态。savepoint 回滚只影响嵌套部分,外层 session 不受影响
- 支付前端联调:payment.ts 和 Packages.vue 需要和后端支付接口对齐,目前还在联调中
明日计划
- 支付流程端到端测试:从选套餐到支付到 FC 到账的完整链路
- 计费事务压力测试:模拟并发扣费场景,验证 savepoint 方案的可靠性
- Auth 模块改动验证:确保登录/注册/Token 刷新流程正常