🚨 修一个严重 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)再部署并复测。
This commit is contained in:
xmz7891
2026-10-07 20:33:27 +08:00
parent f7b9f2a058
commit d907fdcf6e
10 changed files with 781 additions and 6 deletions
+102
View File
@@ -0,0 +1,102 @@
# -*- 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())