Files
shaguabijia-app-server/docs/database/device_liveness.md
T
marco 43f28d358b docs: 补全 API 与数据库文档
- 新增 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>
2026-06-24 04:07:54 +08:00

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/registerPOST /api/v1/device/heartbeatGET /api/v1/device/livenessPOST /api/v1/device/liveness/ack(app/api/v1/device.py);后台 worker heartbeat_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 状态机:unknownalive(收到 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_iduser.id(多对一,硬外键)。
  • comparison_record.device_id 等表的 device_id 同源(都来自客户端 per-install id),但本表无 id 级 join。

索引与约束

  • PK id;index user_iddevice_idlast_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 决定(不在表里)。