mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 14:50:45 +00:00
手表端 - 功能页从 10 个杂项精简到 7 个(屏幕/鼠标/媒体/文件/电脑/语音/电源) - 删掉:游戏封锁、待办、掷骰子、抽奖、壁纸、显示器开关、定时关机、 剪贴板历史、性能/磁盘/电量、文件夹浏览、教室限制等 - 改成显式列按钮,不再把电脑端所有功能组无脑铺到表盘 - 远程鼠标控制重做:滑动区 + 左键/滚轮/右键三按钮 滚轮点按=中键、上下滑=滚动 - 新增视频/音乐播放器(手表本地播放):进度拖动、左右滑快进退10秒、 点画面播放暂停、上一/下一、播完自动下一首 电脑端 - 新增 kill_all(结束所有进程),白名单保护系统关键进程和自身 - 新增 media_list / media_pull(播放器数据源) 安全边界:只允许媒体目录,用 relative_to 判断,防同前缀兄弟目录绕过 中转站 - known_devices 从 ID 集合改成「设备ID -> 配对码」映射 - 修复:管理员改码后关机超过 6 小时,改码会被超时清理冲掉 - 老格式 state 文件平滑升级
193 lines
7.0 KiB
Python
193 lines
7.0 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""中继站:管理员改码 + 关机超 TTL 后不能丢(2.3.0)。
|
||
|
||
要钉死的行为:
|
||
|
||
① 管理员在后台把配对码从 111111 改成 222222
|
||
② 那台电脑关机(或断网)超过 DEVICE_TTL(真实 6 小时)
|
||
③ snapshot() 把设备记录和"孤儿"码绑定都清掉
|
||
④ 电脑开机,报**自己本地的旧码** 111111
|
||
⑤ 中转应该回它 222222,并让它写回本地设置
|
||
—— 改动之前这里会收下 111111,管理员的改码白做
|
||
|
||
另外顺带验证:
|
||
|
||
* 老格式 relay_state.json(knownDevices 是纯 ID 列表)能平滑升级
|
||
* 「删除设备」之后历史被忘干净(开关语义不能被绕)
|
||
* 「抹码」之后历史里记的码也清空(否则抹码白做)
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
import tempfile
|
||
import time
|
||
import urllib.error
|
||
import urllib.request
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
PORT = 8151
|
||
BASE = f"http://127.0.0.1:{PORT}"
|
||
_UA = {"User-Agent": "LanRemote/2.3.0"}
|
||
ADMIN_KEY = "recode-ttl-key"
|
||
TTL = 2.0
|
||
|
||
passed = 0
|
||
failed = 0
|
||
|
||
|
||
def check(name: str, ok: bool, detail: str = "") -> bool:
|
||
global passed, failed
|
||
if ok:
|
||
passed += 1
|
||
print(" [PASS] " + name)
|
||
else:
|
||
failed += 1
|
||
print(" [FAIL] " + name + ((" —— " + str(detail)) if detail else ""))
|
||
return ok
|
||
|
||
|
||
def main() -> int:
|
||
work = Path(tempfile.mkdtemp(prefix="recode_ttl_"))
|
||
data = work / "d"
|
||
data.mkdir(parents=True, exist_ok=True)
|
||
env = dict(os.environ)
|
||
env.update({
|
||
"RELAY_PORT": str(PORT), "RELAY_BIND": "127.0.0.1",
|
||
"RELAY_DATA": str(data), "RELAY_DEVICE_TTL": str(TTL),
|
||
"RELAY_ADMIN_KEY": ADMIN_KEY, "RELAY_POLL": "2",
|
||
"PYTHONIOENCODING": "utf-8", "PYTHONUNBUFFERED": "1",
|
||
})
|
||
relay = None
|
||
|
||
def start() -> subprocess.Popen:
|
||
p = subprocess.Popen(
|
||
[sys.executable, "-u", str(ROOT / "relay" / "relay.py")], env=env,
|
||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||
for _ in range(80):
|
||
time.sleep(0.4)
|
||
try:
|
||
health()
|
||
return p
|
||
except Exception: # noqa: BLE001
|
||
continue
|
||
return p
|
||
|
||
def health() -> dict:
|
||
r = urllib.request.Request(BASE + "/v1/health", headers=_UA)
|
||
with urllib.request.urlopen(r, timeout=5) as resp:
|
||
return json.loads(resp.read().decode("utf-8"))
|
||
|
||
def post(path: str, payload: dict, admin: bool = False) -> dict:
|
||
body = json.dumps(payload).encode()
|
||
headers = {**_UA, "Content-Type": "application/json"}
|
||
if admin:
|
||
headers["X-Admin"] = ADMIN_KEY
|
||
req = urllib.request.Request(BASE + path, data=body, method="POST",
|
||
headers=headers)
|
||
try:
|
||
with urllib.request.urlopen(req, timeout=20) as resp:
|
||
return json.loads(resp.read().decode("utf-8"))
|
||
except urllib.error.HTTPError as exc:
|
||
try:
|
||
return json.loads(exc.read().decode("utf-8"))
|
||
except Exception: # noqa: BLE001
|
||
return {}
|
||
|
||
def reg(dev: str, code: str, name: str = "教室电脑") -> dict:
|
||
return post("/v1/agent/register",
|
||
{"deviceId": dev, "code": code, "name": name,
|
||
"version": "2.3.0"})
|
||
|
||
try:
|
||
relay = start()
|
||
dev = "classroom-PC-AAAA1111"
|
||
old_code = "111111"
|
||
new_code = "222222"
|
||
|
||
print()
|
||
print("═══ 1. 登记 + 管理员改码 ═══")
|
||
r = reg(dev, old_code)
|
||
check("① 首次登记成功", r.get("ok") is True, r)
|
||
check("② 登记返回的码是它上报的", r.get("code") == old_code, r)
|
||
|
||
res = post("/v1/admin/set_code",
|
||
{"deviceId": dev, "code": new_code}, admin=True)
|
||
check("③ 管理员改码成功", res.get("ok") is True, res)
|
||
|
||
r2 = reg(dev, old_code)
|
||
check("④ 在线时改码生效(回的是新码)",
|
||
r2.get("ok") is True and r2.get("code") == new_code, r2)
|
||
|
||
print()
|
||
print(f"═══ 2. 电脑关机,等 {TTL + 2:.0f} 秒超过 TTL ═══")
|
||
time.sleep(TTL + 2)
|
||
h = health()
|
||
check("⑤ 设备记录被清掉(设计中如此)",
|
||
dev not in [d["deviceId"] for d in h["devices"]])
|
||
print(f" 清理后 codeCount={h.get('codeCount')}")
|
||
|
||
print()
|
||
print("═══ 3. 电脑开机,报本地旧码 —— 应该被纠正成新码 ═══")
|
||
r3 = reg(dev, old_code)
|
||
check("⑥ 开机登记成功", r3.get("ok") is True, r3)
|
||
check("⑦ **写回的是新码 222222,不是它报的旧码**",
|
||
r3.get("code") == new_code,
|
||
f"拿到 {r3.get('code')!r},期望 {new_code!r}")
|
||
check("⑧ 有 note 说明改码原因", bool(r3.get("note")), r3.get("note"))
|
||
|
||
h2 = health()
|
||
codes_now = [d.get("code") for d in h2["devices"]]
|
||
check("⑨ /v1/health 里这台设备显示的也是新码",
|
||
new_code in codes_now, codes_now)
|
||
|
||
print()
|
||
print("═══ 4. 重启中转 —— 新码要能持久化 ═══")
|
||
relay.terminate()
|
||
relay.wait(timeout=15)
|
||
relay = start()
|
||
r4 = reg(dev, old_code)
|
||
check("⑩ 重启后仍然纠正成新码",
|
||
r4.get("ok") is True and r4.get("code") == new_code, r4)
|
||
|
||
print()
|
||
print("═══ 5. 「删除设备」要忘干净(否���开关被绕过)═══")
|
||
post("/v1/admin/delete", {"deviceId": dev}, admin=True)
|
||
post("/v1/admin/allow_new", {"allow": False}, admin=True)
|
||
r5 = reg(dev, new_code)
|
||
check("⑪ 删除后设备被当成新设备,且开关关着 -> 拒绝",
|
||
r5.get("ok") is False and r5.get("blocked") is True, r5)
|
||
post("/v1/admin/allow_new", {"allow": True}, admin=True)
|
||
|
||
print()
|
||
print("═══ 6. 「抹码」之后历史里的码也要清空 ═══")
|
||
dev2 = "classroom-PC-BBBB2222"
|
||
reg(dev2, "333333")
|
||
post("/v1/admin/forget", {"code": "333333"}, admin=True)
|
||
r6 = reg(dev2, "444444")
|
||
check("⑫ 抹码后可以换新码(历史没把旧码塞回来)",
|
||
r6.get("ok") is True and r6.get("code") == "444444", r6)
|
||
r7 = reg(dev2, "555555")
|
||
check("⑬ 但换过之后又锁定单码制(回 444444)",
|
||
r7.get("ok") is True and r7.get("code") == "444444", r7)
|
||
|
||
print()
|
||
print("=" * 62)
|
||
print(f" 通过 {passed} 项,失败 {failed} 项")
|
||
print("=" * 62)
|
||
return 0 if failed == 0 else 1
|
||
finally:
|
||
if relay is not None and relay.poll() is None:
|
||
relay.terminate()
|
||
try:
|
||
relay.wait(timeout=10)
|
||
except Exception: # noqa: BLE001
|
||
relay.kill()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|