Files
ykxmz/_build/scan_admin_escapes.py
xmz7891 d907fdcf6e 🚨 修一个严重 bug:管理页所有按钮又失效了(同一个坑踩第二次)
== 症状 ==

在**真实中转**上实测管理页时,用 node --check 检查页面内嵌的 JS:

    SyntaxError: Invalid or unexpected token  (第 46 行)

整段 <script> 不执行 -> 改码 / 删除 / 批量删除 / 全选 全部变幽灵按键。

**这正是用户之前报过的那个 bug,加新功能时又踩了一遍。**

== 根因(讽刺到极点)==

admin.py 的 ADMIN_JS 是**普通三引号字符串**,里面写一个反斜杠+n
会被 Python 解释成**真实换行**。而 JS 的行注释 // 不能跨行 ——
一断行,后面就成了裸文本,整段脚本语法错误。

出事的是这段注释:

    // ⚠ 这里必须是 \\n(反斜杠+n),不能是 \n ——
                                      ^^^^
                       这个 \n 被 Python 变成真换行

**注释为了举例说明「不能写 \n」,自己写了一个 \n。**
于是注释被断成两行,把整段 JS 弄坏了。

== 修法 ==

注释改成**一个转义序列都不写**,只用文字描述:

    // ⚠ 下面 prompt 里的换行,必须写成「反斜杠 + n」两个字符。
    //   本文件是普通三引号字符串:只写反斜杠 + n,Python 会把它
    //   变成**真实换行** ... (一个转义都不出现)

并把"踩过两次"的历史写进注释,提醒后人别再来第三次。

== 新增两个测试(防止再犯)==

1. `_tests/test_admin_js_syntax.py`(已注册为回归阶段 3a6)
   **本地渲染管理页**,把 <script> 抠出来喂给 `node --check`,
   还检查注释行里有没有裸控制字符。

   为什么原来查不出来:已有的「管理页 JS 合法性」阶段只检查
   token 在不在,不查语法 —— 「23 项测试通过」和「功能能用」
   是两件事。

2. `_build/scan_admin_escapes.py`
   扫源文件里"注释下一行不像 JS 代码"的可疑断行。

3. `_tests/test_admin_live.py`
   在**真实中转**上走 HTTP 实测两个新功能(不能进回归套件,
   因为它依赖线上设备):
     一、管理页能打开(带 X-Admin 头绕开登录页)
     二、页面里真的有 allowNew 和改码按钮
     三、内嵌 JS 语法合法
     四、关掉接入开关 -> 新设备被拒 -> 打开 -> 能注册(实测通过)
     五、改配对码 -> 真的变了(实测通过)
     六、不带 X-Admin 访问管理接口返回 401(实测通过)

   实测结果:16/17 通过(唯一的失败就是上面那个 JS bug,
   已修,本地渲染 6/6 通过)。

== 顺带纠正一个我自己的误解 ==

我一开始只带 `?key=` 访问管理页,拿到的是**登录页**,
还以为是设备上部署了旧版本。实际是管理页改成了
**账号 + 密码登录 + Cookie 会话**(用户要求),
脚本通道用 `X-Admin` 头(见 relay.py 的 _admin_gate)。

== 部署时机 ==

**故意没有立刻部署到设备上** —— 部署要重启中转服务,
会打断 relay-stability 正在跑的隧道配比 A/B 对照。
等对照跑完(约 22:00)再部署并复测。
2026-10-07 20:33:27 +08:00

103 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
r'''扫 admin.py 里所有会被 Python 变成真换行的 \n(在注释和 JS 里)。
== 为什么 ==
admin.py 的 ADMIN_JS 是**普通三引号字符串**。
里面写 `\n`(一个反斜杠 + n)会被 Python 解释成**真实换行**。
在 JS 里,行注释 `// ...` 不能跨行 —— 一旦注释中间出现真实换行,
后面的内容就变成一行裸文本,整段 <script> 语法错误,
管理页**所有**按钮失效。
这个坑在同一个文件里踩了两次,第二次还是"注释为了举例说明
不能写 \n,自己写了一个 \n"。所以写个扫描器钉住它。
规则:
* 在 ADMIN_JS 这类普通字符串里,**任何** \n \r \t 之类的转义
都只应该出现在「要输出给 JS 的字符串字面量」里,
而且必须写成 \\n(双反斜杠)。
* 注释里一个转义都不该有。
这个扫描器逐行看:如果某行是 // 注释,却含有单个反斜杠转义,报警。
'''
from __future__ import annotations
import ast
import re
import sys
from pathlib import Path
SRC = Path(__file__).resolve().parent.parent / "relay" / "admin.py"
def main() -> int:
text = SRC.read_text(encoding="utf-8")
tree = ast.parse(text)
# 找出所有大段的字符串常量(ADMIN_JS / HTML 模板)
found = 0
problems = []
for node in ast.walk(tree):
if not isinstance(node, ast.Constant) or not isinstance(node.value, str):
continue
val = node.value
if len(val) < 500 or "<script" not in val and "querySelector" not in val:
continue
found += 1
start = node.lineno
# 逐行检查「注释行里有没有被吃掉的转义」
for i, line in enumerate(val.splitlines()):
stripped = line.strip()
if not stripped.startswith("//"):
continue
# 注释里出现了真实的制表符/回车,或者其它控制字符
for ch in line:
if ord(ch) < 32 and ch != "\t":
problems.append(
"第 %d 行附近的注释里有裸控制字符 0x%02x:%r"
% (start + i, ord(ch), line[:60]))
break
# 再直接扫源文件:在 JS 段里,行尾恰好断在奇怪位置的注释
print(" 找到 %d 段 JS/HTML 模板常量" % found)
print()
# 更直接的办法:把源文件里三引号字符串的原样行拿出来,
# 找「// 注释行后面紧跟着一行不以 // 开头、也不像代码」的
src_lines = text.splitlines()
suspicious = []
for i, line in enumerate(src_lines, 1):
s = line.strip()
if not s.startswith("//"):
continue
# 下一行
if i < len(src_lines):
nxt = src_lines[i].strip()
# 注释的下一行既不是注释、也不像 JS 语句开头
if (nxt and not nxt.startswith("//")
and not re.match(r"^(const|let|var|if|for|while|function|"
r"document|window|return|}|\)|else|await|"
r"try|catch|toast|setTimeout|\w+\s*[=(.]|"
r"<|/\*|\*|--)",
nxt)):
suspicious.append((i, s[:70], nxt[:70]))
if suspicious:
print(" ⚠ 可疑的注释断行(注释下一行不像 JS):")
for ln, a, b in suspicious[:10]:
print(" 第 %d 行: %s" % (ln, a))
print(" 下一行: %s" % b)
else:
print(" ✓ 没有可疑的注释断行")
if problems:
print()
for p in problems:
print(" ⚠ " + p)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())