feat(device): 无障碍保护存活心跳检测 + 掉线终端告警

- 新增 device 表 + /api/v1/device/{register,heartbeat} + 迁移 device_table
- heartbeat_monitor_worker 周期扫描心跳超时(App 被杀/无障碍停)→ 服务器终端打印告警
  (推送本期未接,先用 logger 终端打印代替真实通知;integrations/jpush.py 已备,后续直接替换)
- config / .env.example 增 JPUSH_* / HEARTBEAT_*
- 见 spec(仓库外 e:\codes\spec\accessibility-liveness-push.md)

注:本提交同时快照了并行 session 未提交的 coupon_state 改动 + 相关 migration
(与本功能同迁移链耦合,无法单独拆分,经确认一并提交)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
陈世睿
2026-06-15 22:16:39 +08:00
parent 47812f7fcc
commit 8adad30ff2
18 changed files with 863 additions and 34 deletions
+26
View File
@@ -61,6 +61,32 @@ class Settings(BaseSettings):
JG_VERIFY_ENDPOINT: str = "https://api.verification.jpush.cn/v1/web/loginTokenVerify"
JG_REQUEST_TIMEOUT_SEC: int = 15
# ===== 极光推送 JPush(无障碍保护掉线告警)=====
# 客户端 push appkey 已知 = 966b451a8d9cfe12d173ea9d(android build.gradle manifestPlaceholder)。
# 若推送与一键登录/短信是同一个极光应用(大概率),JPUSH_* 留空即自动回退到 JG_*。
# 否则在 .env 单独配 JPUSH_APP_KEY / JPUSH_MASTER_SECRET(对应那个 push appkey)。
JPUSH_APP_KEY: str = ""
JPUSH_MASTER_SECRET: str = ""
JPUSH_PUSH_ENDPOINT: str = "https://api.jpush.cn/v3/push"
# 无障碍保护存活监控后台任务
HEARTBEAT_MONITOR_ENABLED: bool = True # 总开关
HEARTBEAT_TIMEOUT_MINUTES: int = 10 # 多久没心跳算掉线(≈3 个客户端心跳周期)
HEARTBEAT_SCAN_INTERVAL_SEC: int = 60 # 扫描周期
@property
def jpush_app_key(self) -> str:
return self.JPUSH_APP_KEY or self.JG_APP_KEY
@property
def jpush_master_secret(self) -> str:
return self.JPUSH_MASTER_SECRET or self.JG_MASTER_SECRET
@property
def jpush_configured(self) -> bool:
"""推送凭证齐全(缺则 monitor 只扫不发,不报错)。"""
return bool(self.jpush_app_key and self.jpush_master_secret)
# ===== 短信 =====
SMS_MOCK: bool = True
SMS_CODE_TTL_SEC: int = 300