Files
shaguabijia-app-server/scripts/show_device_regids.py
T

137 lines
5.1 KiB
Python

# -*- coding: utf-8 -*-
"""导出 device_liveness 表里所有 device_id 及其对应的推送 regId,按 push_vendor 分组(本地开发工具)。
用法:
双击 scripts/show_device_regids.bat,或命令行:
python scripts/show_device_regids.py [filter]
[filter] 可选:大小写不敏感的子串,匹配 device_id / push_vendor / push_token /
registration_id 任一列;不传则列出全部。
show_device_regids.py xiaomi # 只看小米
show_device_regids.py device_Pixel # 按 device_id 片段找
说明:
- 直接以**只读**方式读 SQLite(server 在跑也不会抢写锁),所以开不开服务器都能用。
- push_token = 各厂商的 regId/pushToken(新链路,当前在用);registration_id = 旧极光
regId(历史兼容)。两列都打出来,方便跟 logcat 现役值逐字比对。
- 输出刻意保持纯 ASCII 排版并竖排展示**完整值**:双击弹出的 cmd 走 GBK 码页,竖排纯
ASCII 不会乱码;完整值不截断,才能直接复制去比对。
- 若 DATABASE_URL 不是 sqlite(生产 postgres),这里只提示改用 psql。
"""
from __future__ import annotations
import os
import sqlite3
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
def _resolve_sqlite_path() -> Path | None:
"""按 env > .env > 默认 的顺序拿 DATABASE_URL,解析出 sqlite 文件路径。"""
url = os.environ.get("DATABASE_URL", "").strip()
if not url:
env = REPO_ROOT / ".env"
if env.exists():
for line in env.read_text(encoding="utf-8", errors="ignore").splitlines():
s = line.strip()
if s.startswith("DATABASE_URL="):
url = s.split("=", 1)[1].strip().strip('"').strip("'")
break
if not url:
url = "sqlite:///./data/app.db"
if not url.startswith("sqlite"):
print(f"[!] DATABASE_URL not sqlite: {url}")
print(" This tool only reads a local SQLite dev DB. For prod use psql.")
return None
raw = url.split("///", 1)[1] if "///" in url else "./data/app.db"
p = Path(raw)
if not p.is_absolute():
p = (REPO_ROOT / raw).resolve()
return p
def main() -> int:
needle = sys.argv[1].lower() if len(sys.argv) > 1 else None
db = _resolve_sqlite_path()
if db is None:
return 2
if not db.exists():
print(f"[X] DB file not found: {db}")
return 2
# 只读打开,避免和正在运行的 server 抢写锁。URI 路径用正斜杠(as_posix)规避
# Windows 反斜杠/盘符在 file: URI 里的歧义;万一 URI 形式打不开再回退普通连接。
try:
con = sqlite3.connect(f"file:{db.as_posix()}?mode=ro", uri=True)
except sqlite3.OperationalError:
con = sqlite3.connect(str(db))
con.row_factory = sqlite3.Row
rows = con.execute(
"""
SELECT id, user_id, device_id, push_vendor, push_token, registration_id,
platform, app_version, liveness_state, last_heartbeat_at, updated_at
FROM device_liveness
ORDER BY updated_at DESC
"""
).fetchall()
con.close()
if needle:
def hit(r: sqlite3.Row) -> bool:
for k in ("device_id", "push_vendor", "push_token", "registration_id"):
v = r[k]
if v and needle in str(v).lower():
return True
return False
rows = [r for r in rows if hit(r)]
with_token = sum(1 for r in rows if (r["push_token"] or "").strip())
with_reg = sum(1 for r in rows if (r["registration_id"] or "").strip())
# 按 push_vendor 分组;None/空归入 "(none)"。组顺序:设备数多的在前,(none) 垫底;
# 组内沿用 updated_at DESC(rows 查询时已如此排序,dict 保序即可)。
groups: dict[str, list] = {}
for r in rows:
groups.setdefault(r["push_vendor"] or "(none)", []).append(r)
ordered = sorted(
groups.items(), key=lambda kv: (kv[0] == "(none)", -len(kv[1]), kv[0])
)
tally = " ".join(f"{v}={len(items)}" for v, items in ordered) or "-"
print(f"DB : {db}")
line = f"device_liveness : {len(rows)} row(s)"
if needle:
line += f' filter="{sys.argv[1]}"'
print(line)
print(f"has push_token(vendor regId) : {with_token}"
f" has registration_id(jiguang) : {with_reg}")
print(f"vendors : {tally}")
print("=" * 72)
if not rows:
print("(no rows)")
return 0
for vendor, items in ordered:
print()
print(f"===== vendor={vendor} : {len(items)} device(s) =====")
for r in items:
print(f" [#{r['id']}] user_id={r['user_id']} platform={r['platform']}"
f" state={r['liveness_state']} app={r['app_version'] or '-'}")
print(f" device_id : {r['device_id']}")
print(f" push_token(regId): {r['push_token'] or '(empty)'}")
print(f" registration_id : {r['registration_id'] or '(empty)'}")
print(f" last_heartbeat : {r['last_heartbeat_at'] or '-'}"
f" updated : {r['updated_at']}")
print(" " + "-" * 68)
return 0
if __name__ == "__main__":
raise SystemExit(main())