feat(wallet): 0 点定时把用户金币「到分全额」自动兑成现金 (#45)
客户端已删手动兑入口、改「0 点自动兑现金」,服务端补这条批处理: - wallet.exchange_coins_to_cash 加 remark / enforce_min 参数:enforce_min=False(自动兑)只要够 1 分(COIN_PER_CENT)即可,不受手动兑下限约束;remark 可定制。 - 新增 wallet.daily_auto_exchange(db):扫 coin_balance>=100 的用户,到分全额(floor(余额/100)*100) 兑现金,零头留下次;幂等键=当天是否已有 exchange_in 流水(_has_exchange_in_on);逐用户独立事务, 单用户异常 rollback 不中断。 - scripts/daily_auto_exchange.py(--once / --loop):带 .env AUTO_EXCHANGE_ENABLED 开关(false→no-op) + 文件锁防重入;deploy/ 配 systemd service+timer(0 点触发)+ 说明。 - config.AUTO_EXCHANGE_ENABLED(默认 true)+ .env.example。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: OuYingJun1024 <1034284404@qq.com> Reviewed-on: #45 Co-authored-by: ouzhou <ouzhou@wonderable.ai> Co-committed-by: ouzhou <ouzhou@wonderable.ai>
This commit was merged in pull request #45.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# 0 点自动兑金币 定时任务 — 运维手册
|
||||
|
||||
> 对象:维护「每天 0 点把用户金币自动兑成现金」这套定时任务的同事。
|
||||
> 🔒 服务器登录信息见**私密交接清单**,不入库。
|
||||
|
||||
## 它是什么
|
||||
客户端已删手动「兑金币」入口、改为「0 点自动兑现金(可能存在延迟)」,故服务端每天 0 点跑一轮:
|
||||
把每个用户的金币按汇率 **100 金币 = 1 分** 兑成现金。
|
||||
|
||||
- **到分全额**:`coin_amount = floor(余额 / 100) × 100`,不足 1 分(<100 金币)的零头留到下次。
|
||||
- **复用 `exchange_in` 流水类型**(手动兑入口已下线,不会混淆),用户在「现金明细」看到「金币兑换现金」。
|
||||
- **幂等**:同一用户当天已有 `exchange_in` 流水则跳过 → timer 重触发 / 手动重跑当天不会重复兑。
|
||||
- 逻辑见 `app/repositories/wallet.py::daily_auto_exchange`,入口 `scripts/daily_auto_exchange.py`。
|
||||
|
||||
## 文件
|
||||
| 项 | 路径 |
|
||||
|---|---|
|
||||
| 脚本入口 | `scripts/daily_auto_exchange.py` |
|
||||
| 核心逻辑 | `app/repositories/wallet.py::daily_auto_exchange` |
|
||||
| 开关 | `.env` 的 `AUTO_EXCHANGE_ENABLED`(默认 true;false=脚本 no-op) |
|
||||
| systemd 单元 | `deploy/daily-exchange.{service,timer}` |
|
||||
| 运行锁 | `/tmp/daily_auto_exchange.lock`(可用环境变量 `DAILY_EXCHANGE_LOCK` 覆盖) |
|
||||
|
||||
## 部署(Linux 服务器)
|
||||
```bash
|
||||
sudo cp deploy/daily-exchange.{service,timer} /etc/systemd/system/
|
||||
sudo systemctl daemon-reload && sudo systemctl enable --now daily-exchange.timer
|
||||
systemctl list-timers daily-exchange.timer # 确认下次触发时间
|
||||
```
|
||||
|
||||
## 本机 Windows 开发(无 systemd)
|
||||
直接手动跑:
|
||||
```
|
||||
.venv\Scripts\python -m scripts.daily_auto_exchange --once
|
||||
# 本地循环调试(每小时一轮,幂等空跑无害):
|
||||
.venv\Scripts\python -m scripts.daily_auto_exchange --loop --interval 3600
|
||||
```
|
||||
|
||||
## 怎么看健康 / 手动跑一次
|
||||
```bash
|
||||
sudo systemctl start daily-exchange.service # 立即手动跑一轮(不等 0 点)
|
||||
journalctl -u daily-exchange -n 30 --no-pager # 看日志:扫描/兑换/跳过/失败/累计兑入分
|
||||
```
|
||||
日志一行形如:`自动兑完成 用时1.2s 扫描120 兑换88 已兑过跳过30 零头跳过2 失败0 累计兑入15600分`。
|
||||
|
||||
## 怎么关 / 临时停
|
||||
- **临时停(推荐)**:`.env` 置 `AUTO_EXCHANGE_ENABLED=false` → 脚本 no-op,无需动 timer。
|
||||
- **停 timer**:`sudo systemctl disable --now daily-exchange.timer`。
|
||||
|
||||
## 脚本参数
|
||||
- `--once`:跑一轮(timer 用这个,默认)
|
||||
- `--loop --interval N`:常驻循环,每 N 秒一轮(默认 3600,调试用)
|
||||
- 锁位置可用环境变量 `DAILY_EXCHANGE_LOCK=/path/to.lock` 覆盖
|
||||
|
||||
## 注意事项
|
||||
- **触发时间**:`OnCalendar=*-*-* 00:00:00`。客户端文案已注明「可能存在延迟」,要错开整点扎堆可改 `00:05:00`。
|
||||
- **catch-up**:`Persistent=true`,服务器宕机错过当天会在重启后补跑一轮。
|
||||
- **DB 无关**:sqlite / postgres 均可(不像美团 ETL 需要 PG)。
|
||||
- **改脚本 / 改部署**:走 git + PR,由有 root 的人部署。
|
||||
@@ -0,0 +1,33 @@
|
||||
# 0 点自动兑金币 —— 单轮把每个用户金币「到分全额」兑成现金,由 daily-exchange.timer 每天 0 点触发。
|
||||
#
|
||||
# 仅用于 Linux 服务器;本机 Windows 开发无 systemd,直接手动跑脚本即可:
|
||||
# .venv\Scripts\python -m scripts.daily_auto_exchange --once
|
||||
#
|
||||
# 部署(服务器):
|
||||
# sudo cp deploy/daily-exchange.{service,timer} /etc/systemd/system/
|
||||
# sudo systemctl daemon-reload && sudo systemctl enable --now daily-exchange.timer
|
||||
# # 手动跑一次验证: sudo systemctl start daily-exchange.service && journalctl -u daily-exchange -n 30
|
||||
#
|
||||
# 前置:.env 的 AUTO_EXCHANGE_ENABLED=true(默认);置 false 则脚本 no-op,免动 timer。
|
||||
[Unit]
|
||||
Description=Daily auto-exchange coins to cash (one-shot, driven by timer)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
WorkingDirectory=/opt/shaguabijia-app-server
|
||||
Environment="PATH=/opt/shaguabijia-app-server/.venv/bin:/usr/bin:/bin"
|
||||
EnvironmentFile=/opt/shaguabijia-app-server/.env
|
||||
ExecStart=/opt/shaguabijia-app-server/.venv/bin/python -m scripts.daily_auto_exchange --once
|
||||
SyslogIdentifier=daily-exchange
|
||||
# 脚本自带 30min 文件锁;给 20min 硬超时,防卡死轮次长期占锁。
|
||||
TimeoutStartSec=1200
|
||||
|
||||
# 与主服务 shaguabijia-app-server.service 同款加固。
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/opt/shaguabijia-app-server
|
||||
ProtectHome=true
|
||||
@@ -0,0 +1,14 @@
|
||||
# 每天 0 点触发一次「自动兑金币」(Linux 服务器用)。
|
||||
# 见 daily-exchange.service 顶部注释的部署步骤。
|
||||
[Unit]
|
||||
Description=Run daily auto-exchange coins->cash at midnight
|
||||
|
||||
[Timer]
|
||||
# 每天 0 点跑。客户端文案已注明「可能存在延迟」,可按需改 00:05 错开整点扎堆。
|
||||
OnCalendar=*-*-* 00:00:00
|
||||
# 服务器宕机/重启后,补跑错过的那一轮(而不是干等次日)。
|
||||
Persistent=true
|
||||
AccuracySec=1min
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user