43f28d358b
- 新增 device-liveness(无障碍存活监控)、internal(内部回写端点)、cps-redirect(CPS 短链落地)三族单文件文档 - API 总览补录此前缺失端点:coupon prompt 频控、report/invite、wxpay 回调、platform 客户端配置、feedback/user/ad 零散读端点 - database 新增 cps_wx_user / device_liveness / launch_confirm_sample 三表文档,更新 OVERVIEW/README/comparison_record/数据库迁移/后端技术实现 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.8 KiB
4.8 KiB
device_liveness — 无障碍存活监控(心跳 + 掉线召回)
模型
app/models/device.py(DeviceLiveness) · 仓库app/repositories/device.py(register_or_update/touch_heartbeat/list_overdue/mark_notified/get_device/ack_kill_alert) · 接口 用户POST /api/v1/device/register、POST /api/v1/device/heartbeat、GET /api/v1/device/liveness、POST /api/v1/device/liveness/ack(app/api/v1/device.py);后台 workerheartbeat_monitor_worker· ← 索引 · 总览
每行 = 一个用户的一台设备(per-install,(user_id, device_id) 唯一)。客户端无障碍服务存活时周期上报心跳刷新 last_heartbeat_at;App 前台/登录拿到极光 push token 时上报 registration_id。后端 heartbeat_monitor_worker 扫「曾保护过、现已心跳超时」的设备,推送(或本期仅终端打印)提醒用户重开无障碍。表名不叫 device:它存的不是设备信息(品牌/型号),而是无障碍存活状态。#65 新增。
设计见 spec
spec/accessibility-liveness-push.md(推送版)+spec/accessibility-liveness-pull-prompt.md(后置 pull 提醒版)。
用在哪 / 增删改查
- C / U(upsert):
register_or_update(POST /device/register,App 拿到 push token 时调)按(user_id, device_id)upsert,只在传入非空时更新registration_id/platform/app_version;touch_heartbeat(POST /device/heartbeat,无障碍服务存活时周期调,心跳也能自注册)在accessibility_enabled=true时刷last_heartbeat_at、置ever_protected=true、状态机重置回alive、清notified_at(掉线恢复→下次再断才再推一条)。 - U(worker):
mark_notified(heartbeat_monitor_worker检出掉线后)置liveness_state='notified'+notified_at+kill_alert_pending=True;ack_kill_alert(POST /device/liveness/ack,客户端弹过引导后)清kill_alert_pending(幂等)。 - R:
list_overdue(worker 扫描:ever_protected=true+liveness_state='alive'+last_heartbeat_at早于now - timeout)→ 掉线设备列表;get_device(GET /device/liveness,客户端进 App 拉本机是否被判掉线过)。 - D:无。
字段
| 列 | 类型 | 约束 / 默认 | 说明(取值 / join) |
|---|---|---|---|
id |
Integer | PK, autoincrement | |
user_id |
Integer | FK→user.id, index, NOT NULL | 归属用户 |
device_id |
String(128) | index, NOT NULL | 客户端 DeviceId.get() 生成的 per-install id(如 device_Pixel_ab12cd34) |
registration_id |
String(64) | nullable | 极光推送 registration id;JCollectionAuth 同意后才下发,拿到才填 |
platform |
String(16) | NOT NULL, default android |
|
app_version |
String(32) | nullable | 上报时 App 版本 |
ever_protected |
Boolean | NOT NULL, default false | 收到过 service 心跳即 true(=该设备开过无障碍,功能对它有意义)。list_overdue 的过滤前提 |
last_heartbeat_at |
DateTime(tz) | index, nullable | 最近一次 service 心跳时间(存活证明);超时即视为保护掉线 |
last_report_protection_on |
Boolean | NOT NULL, default false | 最近一次上报的无障碍开关状态(观测用) |
liveness_state |
String(16) | NOT NULL, default unknown |
状态机:unknown → alive(收到 service 心跳)→ silent/notified(扫描发现超时并已推送);心跳恢复 handler 重置回 alive |
notified_at |
DateTime(tz) | nullable | 最近一次推送告警时间 |
kill_alert_pending |
Boolean | NOT NULL, default false | 掉线告警「待客户端提醒」标记(后置 pull 版)。与 liveness_state 解耦:worker 检出掉线即置 True;touch_heartbeat(心跳恢复)不动它;只由客户端 ack 清 |
created_at |
DateTime(tz) | server_default now() | |
updated_at |
DateTime(tz) | server_default now(), onupdate now() |
关系 / Join Key
user_id→user.id(多对一,硬外键)。- 与
comparison_record.device_id等表的device_id同源(都来自客户端 per-install id),但本表无 id 级 join。
索引与约束
- PK
id;indexuser_id、device_id、last_heartbeat_at;UNIQUE(user_id,device_id) =uq_device_liveness_user_device(一用户一设备一行,upsert 幂等键)。
注意
kill_alert_pending为什么和liveness_state解耦:服务随 App 重启会先发心跳把state重置回alive,若复用state判「待提醒」,客户端进 App 这一刻可能恰好已被重置 → 漏看这次掉线。故另设一个只由 worker 置、只由客户端 ack 清的标记,规避竞态。list_overdue本期不要求有registration_id:本期只做终端打印检测、未真推送,没接极光 token 的设备也要检出。- 心跳超时阈值由 worker 的
timeout_minutes决定(不在表里)。