Archived
chore: 清理前端与容器脚本中指向已删台账的过时编号
模板/样式/脚本的注释里散落着「第 N 条」「F59」「P3-43」这类编号,它们指向的 修复验收台账与交接文档已在本轮删除——留着既检索不到、也对不上号,还让后来读 注释的人以为存在一份可查的编号清单。本次只动注释,正文零改动。 覆盖范围(全仓一致): - templates/index.html 163 处、static/site_auth.js 55 处,共 218 处编号整段摘除 (含「第 N 条(Px):」前缀与「(Fxx 短标签)」括号)。 - 容器/配置 16 处:docker-compose.yml、entrypoint.sh(F59 / 第 93 条 / 第 97 条)、 Dockerfile(T7 / 第 97 条)、.dockerignore(T1 / 第 97 条 / 第 55 条 / P2-9)、 .env.example(F59 / 第 100 条 / 「详见交接文档 F11」这条失效指引)、.gitignore(P3-41)。 - Python 4 处:core/config.py(D-1)、services/tasks.py(O-5×2)、services/_shared.py(O-5) ——上一轮 Python 侧清理的漏网。 为什么规则要收窄(两类误伤,实测踩到): 1. 标签首字母不能用泛化的 [A-Z]:X5 是 QQ 浏览器内核、ES5 是 JS 版本,都不是编号。 泛化后会把「微信 XWeb/X5、QQ 内置」改成「微信 XWeb/、QQ 内置」,把 「Chromium <87 的老 X5 不识别 inset」改成「老 不识别」。现按本仓真实用过的 任务族白名单收窄(A B C D F O P R S T U),X5/ES5 原样保留。 2. 括号内「编号 + 长说明」不能整段删:「(F51 只防了切歌,这里补同歌翻页)」 整段删掉会连说明一起丢;现只摘编号、保留说明文字。 为什么同步改黄金契约快照:题头注释属于模板内容,改了注释首页字节数必然变 (466261 → 464333),tests/golden/responses.json 的 index 条目按既定流程刷新 html_len/html_sha256;并在 tests/test_golden.py 补一段说明,写清该条目记的是 「当前模板」而非 Flask 期数值、以及有意改模板后如何重算 (html_len = 文件字节数 - 1,sha256 = 文件去掉末尾换行后的哈希)。 验证(全部通过): - 与 HEAD 比对:剥离全部注释后的正文逐行一致(唯一差异是删掉了一处行尾空注释), 行数不变、每行半角括号/引号数量不变 → 代码与字符串零改动; - node --check 通过(静态 JS + index.html 内联脚本),<style> 块花括号平衡; - 真实 uvicorn 冒烟:/health /user/status /api/topen → 200、/admin/site/backup/list → 403, /static/site_auth.js 响应体与本地文件 sha1 相同,/ 无「第 N 条」残留; - ruff All checks passed;pyright 0 errors / 0 warnings;pytest 144 passed。
This commit is contained in:
1 parent
6288a98ecc
commit
da3d013e9c
13 files changed
+246
-242
No files matched your search
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
# T1:构建镜像严禁带入的敏感/私有/无关内容
|
# 构建镜像严禁带入的敏感/私有/无关内容
|
||||||
LICENSE
|
LICENSE
|
||||||
README.md
|
README.md
|
||||||
.gitignore
|
.gitignore
|
||||||
@@ -15,7 +15,7 @@ _smoke_test_downloader.py
|
|||||||
_test_podcast_api.py
|
_test_podcast_api.py
|
||||||
部署文档.md
|
部署文档.md
|
||||||
功能介绍.md
|
功能介绍.md
|
||||||
# T1 新增:真实凭据、用户数据库、密钥、日志、交接文档、运维脚本与审查工作目录
|
# 真实凭据、用户数据库、密钥、日志、交接文档、运维脚本与审查工作目录
|
||||||
cookie.txt
|
cookie.txt
|
||||||
user_cookie.txt
|
user_cookie.txt
|
||||||
# Cookie 备份(qr_login._backup_cookie 产生,含真实 MUSIC_U):
|
# Cookie 备份(qr_login._backup_cookie 产生,含真实 MUSIC_U):
|
||||||
@@ -25,14 +25,14 @@ user_cookie.*.txt
|
|||||||
data/
|
data/
|
||||||
data/backups/
|
data/backups/
|
||||||
*.log
|
*.log
|
||||||
# 第 97 条(P3):轮转件 music_api.log.1/2/3 不匹配 *.log——含 IP/UA 的历史日志
|
# 轮转件 music_api.log.1/2/3 不匹配 *.log——含 IP/UA 的历史日志
|
||||||
# 曾随 _mkpkg 同类问题外泄(第 55 条),docker 轨道同步堵上
|
# 曾随 _mkpkg 同类问题外泄,docker 轨道同步堵上
|
||||||
music_api.log*
|
music_api.log*
|
||||||
*.log.*
|
*.log.*
|
||||||
交接文档.md
|
交接文档.md
|
||||||
修复验收台账_F01-F117.md
|
修复验收台账_F01-F117.md
|
||||||
待修问题清单_三轮汇总.md
|
待修问题清单_三轮汇总.md
|
||||||
# P2-9:补齐后产生的审计清单与内部引导文件(含内部路径/运维细节)
|
# 补齐后产生的审计清单与内部引导文件(含内部路径/运维细节)
|
||||||
待修问题清单_第四轮深挖.md
|
待修问题清单_第四轮深挖.md
|
||||||
待修问题清单_第五轮深挖.md
|
待修问题清单_第五轮深挖.md
|
||||||
新会话引导.md
|
新会话引导.md
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
# 复制为 .env 后按需修改(F59:不提供 .env 时 docker-compose 使用默认 TZ,可直接运行)
|
# 复制为 .env 后按需修改(不提供 .env 时 docker-compose 使用默认 TZ,可直接运行)
|
||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
# HTTPS 反代(Caddy/Nginx 等)上线后取消注释:会话 Cookie 仅经 HTTPS 传输
|
# HTTPS 反代(Caddy/Nginx 等)上线后取消注释:会话 Cookie 仅经 HTTPS 传输
|
||||||
# (纯 HTTP 部署不要开启,否则浏览器拒收 Cookie;详见交接文档 F11)
|
# (纯 HTTP 部署不要开启,否则浏览器拒收 Cookie)
|
||||||
# ENABLE_SECURE_COOKIE=1
|
# ENABLE_SECURE_COOKIE=1
|
||||||
# 第 100 条:跨域来源控制——默认 `*`(历史口径);自用收敛同源可改为实际域名
|
# 跨域来源控制——默认 `*`(历史口径);自用收敛同源可改为实际域名
|
||||||
# CORS_ORIGINS=https://qf.linxingran.top
|
# CORS_ORIGINS=https://qf.linxingran.top
|
||||||
+1
-1
@@ -9,7 +9,7 @@ __pycache__
|
|||||||
*.pyo
|
*.pyo
|
||||||
*.pyd
|
*.pyd
|
||||||
|
|
||||||
# P3-41:运行时敏感数据与私有文档——公开包买家若将本项目纳入 git 管理,
|
# 运行时敏感数据与私有文档——公开包买家若将本项目纳入 git 管理,
|
||||||
# 以下内容绝不能进版本库(用户数据库/网易云 Cookie/日志/审计文档)
|
# 以下内容绝不能进版本库(用户数据库/网易云 Cookie/日志/审计文档)
|
||||||
data/
|
data/
|
||||||
cookie.txt
|
cookie.txt
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
# T7:基础镜像升级到 Python 3.12(3.9 已于 2025-10 EOL);alpine + ffmpeg 组合保持不变
|
# 基础镜像 Python 3.12(3.9 已于 2025-10 EOL);alpine + ffmpeg 组合保持不变
|
||||||
FROM python:3.12-alpine3.21
|
FROM python:3.12-alpine3.21
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
# 使用阿里云 apk 镜像源加速安装 ffmpeg
|
# 使用阿里云 apk 镜像源加速安装 ffmpeg
|
||||||
# 第 97 条(P3):补 tzdata——alpine 无 zoneinfo,ENV TZ=Asia/Shanghai 不装它不生效
|
# 补 tzdata——alpine 无 zoneinfo,ENV TZ=Asia/Shanghai 不装它不生效
|
||||||
# (容器内日志/展示本地时间差 8 小时;计费/会话全部 epoch 秒不受影响)
|
# (容器内日志/展示本地时间差 8 小时;计费/会话全部 epoch 秒不受影响)
|
||||||
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories && \
|
RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories && \
|
||||||
apk add --no-cache tzdata ffmpeg
|
apk add --no-cache tzdata ffmpeg
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ from dataclasses import dataclass
|
|||||||
# 站点统一版本号(/health、/api/info、前端 CHANGELOG 全部以此为准,别处不得硬编码)
|
# 站点统一版本号(/health、/api/info、前端 CHANGELOG 全部以此为准,别处不得硬编码)
|
||||||
APP_VERSION = '2.4.0'
|
APP_VERSION = '2.4.0'
|
||||||
|
|
||||||
# 交付结果保留期(/D-1:服务端与前端「领取」入口统一 35 分钟——前端入口保留 35 分钟,
|
# 交付结果保留期(服务端与前端「领取」入口统一 35 分钟——前端入口保留 35 分钟,
|
||||||
# 原 30 分钟会在最后 5 分钟让领取必 404)。终态任务记录、任务目录、下载缓存均按此回收。
|
# 原 30 分钟会在最后 5 分钟让领取必 404)。终态任务记录、任务目录、下载缓存均按此回收。
|
||||||
RESULT_RETENTION_SECONDS = 35 * 60
|
RESULT_RETENTION_SECONDS = 35 * 60
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -3,12 +3,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
# F59:.env 缺失时不再启动失败(required 属性 compose v2.24+ 支持;老版本可删掉该行)
|
# .env 缺失时不再启动失败(required 属性 compose v2.24+ 支持;老版本可删掉该行)
|
||||||
env_file:
|
env_file:
|
||||||
- path: .env
|
- path: .env
|
||||||
required: false
|
required: false
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai # 未提供 .env 时的默认时区(F59:文档"默认即可运行"现在成立)
|
- TZ=Asia/Shanghai # 未提供 .env 时的默认时区("默认即可运行"现在成立)
|
||||||
- ENABLE_SECURE_COOKIE=${ENABLE_SECURE_COOKIE:-} # HTTPS 反代上线后设 1(见 .env.example)
|
- ENABLE_SECURE_COOKIE=${ENABLE_SECURE_COOKIE:-} # HTTPS 反代上线后设 1(见 .env.example)
|
||||||
ports:
|
ports:
|
||||||
- "5001:5000" # 对外 5001 端口(容器内服务监听 5000)
|
- "5001:5000" # 对外 5001 端口(容器内服务监听 5000)
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# F59:exec 让 python 成为 PID 1 直接接收 SIGTERM,容器停止不再经 shell 转发而超时
|
# exec 让 python 成为 PID 1 直接接收 SIGTERM,容器停止不再经 shell 转发而超时
|
||||||
# 第 93 条(P2-4):compose 挂载 ./user_cookie.txt 时若宿主不存在该文件,Docker 会
|
# compose 挂载 ./user_cookie.txt 时若宿主不存在该文件,Docker 会
|
||||||
# 在启动前创建同名**目录**并挂载——容器内 touch 只对无卷 docker run 有效。
|
# 在启动前创建同名**目录**并挂载——容器内 touch 只对无卷 docker run 有效。
|
||||||
# 第 97 条(P3):检测到同名目录直接报错退出(fail-fast),不再静默带病运行
|
# 检测到同名目录直接报错退出(fail-fast),不再静默带病运行
|
||||||
for f in user_cookie.txt cookie.txt; do
|
for f in user_cookie.txt cookie.txt; do
|
||||||
if [ -d "/app/$f" ]; then
|
if [ -d "/app/$f" ]; then
|
||||||
echo "FATAL: /app/$f 是目录(宿主机缺文件时 Docker 挂载会建同名目录)。请在宿主机先 touch 该文件再启动。" >&2
|
echo "FATAL: /app/$f 是目录(宿主机缺文件时 Docker 挂载会建同名目录)。请在宿主机先 touch 该文件再启动。" >&2
|
||||||
|
|||||||
+1
-1
@@ -69,7 +69,7 @@ _TASKS_SWEEP_LAST = {'ts': 0.0} # 上次 tasks/ 孤儿目录补扫时间
|
|||||||
|
|
||||||
|
|
||||||
def _sweep_rate_buckets() -> None:
|
def _sweep_rate_buckets() -> None:
|
||||||
"""限流桶周期清扫(O-5):把超过 10 分钟无新请求的桶移出,防止低流量长期运行下
|
"""限流桶周期清扫:把超过 10 分钟无新请求的桶移出,防止低流量长期运行下
|
||||||
缓慢累积(原实现只在"新建第 8001 个桶"时才触发清理,时机太晚)"""
|
缓慢累积(原实现只在"新建第 8001 个桶"时才触发清理,时机太晚)"""
|
||||||
now = time.time()
|
now = time.time()
|
||||||
with _RATE_LOCK:
|
with _RATE_LOCK:
|
||||||
|
|||||||
+2
-2
@@ -79,7 +79,7 @@ def _task_monitor_loop() -> None:
|
|||||||
- 终态任务按 completed_at 保留 35 分钟(与前端「领取」入口对齐)
|
- 终态任务按 completed_at 保留 35 分钟(与前端「领取」入口对齐)
|
||||||
- 运行中/排队/暂停任务超过 4 小时仍未结束 → 置取消(cancel Event 一并置位)
|
- 运行中/排队/暂停任务超过 4 小时仍未结束 → 置取消(cancel Event 一并置位)
|
||||||
- 下载缓存按年龄清扫(配套,同为 35 分钟口径)
|
- 下载缓存按年龄清扫(配套,同为 35 分钟口径)
|
||||||
- 顺带清理限流桶中的过期项(O-5:不再只依赖"新建桶时"的清理时机)
|
- 顺带清理限流桶中的过期项(不再只依赖"新建桶时"的清理时机)
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
time.sleep(HEARTBEAT_INTERVAL)
|
time.sleep(HEARTBEAT_INTERVAL)
|
||||||
@@ -167,7 +167,7 @@ def _task_monitor_loop() -> None:
|
|||||||
auth_system.ledger_settle(tid, 0, 'timeout')
|
auth_system.ledger_settle(tid, 0, 'timeout')
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# ---- 下载缓存清扫(保留期同交付口径 35 分钟)+ 限流桶过期项顺手清理(O-5)----
|
# ---- 下载缓存清扫(保留期同交付口径 35 分钟)+ 限流桶过期项顺手清理 ----
|
||||||
_sweep_download_cache(RESULT_RETENTION_SECONDS)
|
_sweep_download_cache(RESULT_RETENTION_SECONDS)
|
||||||
_sweep_rate_buckets()
|
_sweep_rate_buckets()
|
||||||
# 账本周期兜底(原仅启动执行一次)----
|
# 账本周期兜底(原仅启动执行一次)----
|
||||||
|
|||||||
+56
-56
@@ -5,7 +5,7 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/* P2-11:Promise.finally 老内核 polyfill(与 index.html 同一份标准实现——
|
/* Promise.finally 老内核 polyfill(与 index.html 同一份标准实现——
|
||||||
index.html 的脚本先加载,这里再兜底一次确保独立引用时也安全) */
|
index.html 的脚本先加载,这里再兜底一次确保独立引用时也安全) */
|
||||||
if (!Promise.prototype.finally) {
|
if (!Promise.prototype.finally) {
|
||||||
Promise.prototype.finally = function (cb) {
|
Promise.prototype.finally = function (cb) {
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
return r.json().then(function (d) { if (timer) clearTimeout(timer); return d; },
|
return r.json().then(function (d) { if (timer) clearTimeout(timer); return d; },
|
||||||
function (e) { if (timer) clearTimeout(timer); throw e; });
|
function (e) { if (timer) clearTimeout(timer); throw e; });
|
||||||
}).catch(function () {
|
}).catch(function () {
|
||||||
// F9:默认失败兜底——网络断/超时/非 JSON 响应不再产生 unhandledrejection
|
// 默认失败兜底——网络断/超时/非 JSON 响应不再产生 unhandledrejection
|
||||||
// 噪音(喂 fe_error 上报通道),统一转为标准失败对象走调用方既有的 toast
|
// 噪音(喂 fe_error 上报通道),统一转为标准失败对象走调用方既有的 toast
|
||||||
if (timer) clearTimeout(timer);
|
if (timer) clearTimeout(timer);
|
||||||
return { status: 0, success: false, message: '网络异常,请检查连接后重试' };
|
return { status: 0, success: false, message: '网络异常,请检查连接后重试' };
|
||||||
@@ -92,9 +92,9 @@
|
|||||||
|
|
||||||
/* ---------------- 设备标识 ---------------- */
|
/* ---------------- 设备标识 ---------------- */
|
||||||
function getDeviceId() {
|
function getDeviceId() {
|
||||||
/* F-7(第 79 条):与 index.html 的 getDeviceId 统一为同一键/同一值——
|
/* 与 index.html 的 getDeviceId 统一为同一键/同一值——
|
||||||
注册指纹与下载任务/心跳的设备号不再各走一套;
|
注册指纹与下载任务/心跳的设备号不再各走一套;
|
||||||
内存缓存兜底 localStorage 不可写时同一页面内保持稳定(F108 同款)。 */
|
内存缓存兜底 localStorage 不可写时同一页面内保持稳定。 */
|
||||||
if (window.__siteDeviceIdCache) return window.__siteDeviceIdCache;
|
if (window.__siteDeviceIdCache) return window.__siteDeviceIdCache;
|
||||||
var KEY = 'netease_device_id';
|
var KEY = 'netease_device_id';
|
||||||
var d = '';
|
var d = '';
|
||||||
@@ -111,8 +111,8 @@ function getDeviceId() {
|
|||||||
var user = null;
|
var user = null;
|
||||||
var durationText = '';
|
var durationText = '';
|
||||||
var ann = null;
|
var ann = null;
|
||||||
var smtpOk = false; // F-8.2:fail-closed——/auth/me 返回前不显示注册入口(防止未配置 SMTP 的窗口期误开)
|
var smtpOk = false; // fail-closed——/auth/me 返回前不显示注册入口(防止未配置 SMTP 的窗口期误开)
|
||||||
var registerCredits = 3; // F-6.1:注册赠送次数由 /auth/me 下发,文案不再硬编码
|
var registerCredits = 3; // 注册赠送次数由 /auth/me 下发,文案不再硬编码
|
||||||
|
|
||||||
/* ---------------- 弹窗 ---------------- */
|
/* ---------------- 弹窗 ---------------- */
|
||||||
function openModal(id) { var m = $('#' + id); if (m) m.classList.add('show'); }
|
function openModal(id) { var m = $('#' + id); if (m) m.classList.add('show'); }
|
||||||
@@ -129,9 +129,9 @@ function getDeviceId() {
|
|||||||
['goto-register', 'goto-register2'].forEach(function (id) {
|
['goto-register', 'goto-register2'].forEach(function (id) {
|
||||||
var a = $('#' + id); if (a) a.hidden = !showRegister;
|
var a = $('#' + id); if (a) a.hidden = !showRegister;
|
||||||
});
|
});
|
||||||
// F-8.1:两个弹窗(登录/注册)里的试用提示都要受管——原 querySelector 只命中第一个
|
// 两个弹窗(登录/注册)里的试用提示都要受管——原 querySelector 只命中第一个
|
||||||
$$('.auth-trial-box').forEach(function (box) { box.hidden = !showRegister; });
|
$$('.auth-trial-box').forEach(function (box) { box.hidden = !showRegister; });
|
||||||
$$('.reg-gift-num').forEach(function (el) { el.textContent = registerCredits; }); // F-6.1
|
$$('.reg-gift-num').forEach(function (el) { el.textContent = registerCredits; });
|
||||||
var guest = $('#auth-guest-area'), uarea = $('#auth-user-area');
|
var guest = $('#auth-guest-area'), uarea = $('#auth-user-area');
|
||||||
if (guest && uarea) {
|
if (guest && uarea) {
|
||||||
if (user) {
|
if (user) {
|
||||||
@@ -163,12 +163,12 @@ function getDeviceId() {
|
|||||||
durationText = d.duration_text || '';
|
durationText = d.duration_text || '';
|
||||||
ann = d.announcement || null;
|
ann = d.announcement || null;
|
||||||
smtpOk = !!d.smtp_configured;
|
smtpOk = !!d.smtp_configured;
|
||||||
// F-6.1:注册赠送次数动态下发(/auth/me),文案不再硬编码
|
// 注册赠送次数动态下发(/auth/me),文案不再硬编码
|
||||||
if (typeof d.register_credits === 'number') registerCredits = d.register_credits;
|
if (typeof d.register_credits === 'number') registerCredits = d.register_credits;
|
||||||
renderAuthArea();
|
renderAuthArea();
|
||||||
renderUserCenter();
|
renderUserCenter();
|
||||||
maybeShowAnnouncement();
|
maybeShowAnnouncement();
|
||||||
// A1:确认登录后再恢复下载任务(游客调任务接口会 401 误弹登录框)
|
// 确认登录后再恢复下载任务(游客调任务接口会 401 误弹登录框)
|
||||||
if (user && typeof window.SITE_TASKS_BOOTSTRAP === 'function') {
|
if (user && typeof window.SITE_TASKS_BOOTSTRAP === 'function') {
|
||||||
try { window.SITE_TASKS_BOOTSTRAP(); } catch (e) {}
|
try { window.SITE_TASKS_BOOTSTRAP(); } catch (e) {}
|
||||||
}
|
}
|
||||||
@@ -190,14 +190,14 @@ function getDeviceId() {
|
|||||||
showExpiredModal();
|
showExpiredModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// F53:会话失效时清掉内存里的旧用户状态再引导登录
|
// 会话失效时清掉内存里的旧用户状态再引导登录
|
||||||
//(原实现保留旧 user,页面继续显示"已登录"却处处 401)
|
//(原实现保留旧 user,页面继续显示"已登录"却处处 401)
|
||||||
user = null;
|
user = null;
|
||||||
durationText = '';
|
durationText = '';
|
||||||
renderAuthArea();
|
renderAuthArea();
|
||||||
renderUserCenter();
|
renderUserCenter();
|
||||||
authToastOnce(msg || '登录已失效,请重新登录');
|
authToastOnce(msg || '登录已失效,请重新登录');
|
||||||
// 第 87 条:登录弹窗已打开且正处于找回/两步验证视图时,不复位视图——
|
// 登录弹窗已打开且正处于找回/两步验证视图时,不复位视图——
|
||||||
// 未登录期间后台任务轮询/心跳会持续吃 401 反复进入本函数,若每次都把
|
// 未登录期间后台任务轮询/心跳会持续吃 401 反复进入本函数,若每次都把
|
||||||
// 弹窗拽回登录视图,管理员正在输入的验证码会被打断、票据被清空
|
// 弹窗拽回登录视图,管理员正在输入的验证码会被打断、票据被清空
|
||||||
var _lm = document.getElementById('login-modal');
|
var _lm = document.getElementById('login-modal');
|
||||||
@@ -209,7 +209,7 @@ function getDeviceId() {
|
|||||||
hideForgotView();
|
hideForgotView();
|
||||||
hideAdmin2faView();
|
hideAdmin2faView();
|
||||||
}
|
}
|
||||||
// 第 98 条:会话失效弹登录窗前,先关掉可能叠开的注册弹窗(双遮罩错乱),
|
// 会话失效弹登录窗前,先关掉可能叠开的注册弹窗(双遮罩错乱),
|
||||||
// 已 show 则短路——401 风暴(1.5s 轮询)期间不反复重开
|
// 已 show 则短路——401 风暴(1.5s 轮询)期间不反复重开
|
||||||
var _reg = document.getElementById('register-modal');
|
var _reg = document.getElementById('register-modal');
|
||||||
if (_reg && _reg.classList.contains('show')) closeModal('register-modal');
|
if (_reg && _reg.classList.contains('show')) closeModal('register-modal');
|
||||||
@@ -265,14 +265,14 @@ function getDeviceId() {
|
|||||||
durationText = d.duration_text || '';
|
durationText = d.duration_text || '';
|
||||||
renderAuthArea();
|
renderAuthArea();
|
||||||
renderUserCenter();
|
renderUserCenter();
|
||||||
// A1:登录成功后立即恢复本设备的进行中任务
|
// 登录成功后立即恢复本设备的进行中任务
|
||||||
if (user && typeof window.SITE_TASKS_BOOTSTRAP === 'function') {
|
if (user && typeof window.SITE_TASKS_BOOTSTRAP === 'function') {
|
||||||
try { window.SITE_TASKS_BOOTSTRAP(); } catch (e) {}
|
try { window.SITE_TASKS_BOOTSTRAP(); } catch (e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 登录 / 注册弹窗 ---------------- */
|
/* ---------------- 登录 / 注册弹窗 ---------------- */
|
||||||
// 第 81 条:注册唯一入口——导航按钮与登录弹窗两处链接(全站仅有的 3 条打开
|
// 注册唯一入口——导航按钮与登录弹窗两处链接(全站仅有的 3 条打开
|
||||||
// 路径)统一经此:未同意协议 → cookieGate('register') 弹「隐私与协议设置」
|
// 路径)统一经此:未同意协议 → cookieGate('register') 弹「隐私与协议设置」
|
||||||
// 注册模式(16 秒强制阅读计时)并中止;已同意 → 直接进注册弹窗。
|
// 注册模式(16 秒强制阅读计时)并中止;已同意 → 直接进注册弹窗。
|
||||||
// 提交侧 doRegister 另有 fail-closed 兜底闸,程序化打开也绕不过。
|
// 提交侧 doRegister 另有 fail-closed 兜底闸,程序化打开也绕不过。
|
||||||
@@ -294,7 +294,7 @@ function getDeviceId() {
|
|||||||
['login-close', 'register-close', 'announce-close'].forEach(function (id) {
|
['login-close', 'register-close', 'announce-close'].forEach(function (id) {
|
||||||
var b = $('#' + id);
|
var b = $('#' + id);
|
||||||
if (b) b.addEventListener('click', function () {
|
if (b) b.addEventListener('click', function () {
|
||||||
if (id.indexOf('login') === 0 && window.resetAuthViews) window.resetAuthViews(); // 第 87 条:关闭侧复位
|
if (id.indexOf('login') === 0 && window.resetAuthViews) window.resetAuthViews(); // 关闭侧复位
|
||||||
closeModal(id.indexOf('login') === 0 ? 'login-modal' : id.indexOf('register') === 0 ? 'register-modal' : 'announce-modal');
|
closeModal(id.indexOf('login') === 0 ? 'login-modal' : id.indexOf('register') === 0 ? 'register-modal' : 'announce-modal');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -302,7 +302,7 @@ function getDeviceId() {
|
|||||||
var m = $('#' + p[0]);
|
var m = $('#' + p[0]);
|
||||||
if (m) m.addEventListener('click', function (e) {
|
if (m) m.addEventListener('click', function (e) {
|
||||||
if (e.target === m) {
|
if (e.target === m) {
|
||||||
if (p[0] === 'login-modal' && window.resetAuthViews) window.resetAuthViews(); // 第 87 条:关闭侧复位
|
if (p[0] === 'login-modal' && window.resetAuthViews) window.resetAuthViews(); // 关闭侧复位
|
||||||
closeModal(p[0]);
|
closeModal(p[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -312,12 +312,12 @@ function getDeviceId() {
|
|||||||
if (g2) g2.addEventListener('click', function () { closeModal('login-modal'); openRegisterFlow(); });
|
if (g2) g2.addEventListener('click', function () { closeModal('login-modal'); openRegisterFlow(); });
|
||||||
var gl1 = $('#goto-login');
|
var gl1 = $('#goto-login');
|
||||||
if (gl1) gl1.addEventListener('click', function () { closeModal('register-modal'); hideForgotView(); hideAdmin2faView(); openModal('login-modal'); });
|
if (gl1) gl1.addEventListener('click', function () { closeModal('register-modal'); hideForgotView(); hideAdmin2faView(); openModal('login-modal'); });
|
||||||
// 第 86 条:找回密码入口/返回(F5.1 只交付 DOM 漏了绑定)
|
// 找回密码入口/返回(只交付 DOM 漏了绑定)
|
||||||
var gf = $('#goto-forgot');
|
var gf = $('#goto-forgot');
|
||||||
if (gf) gf.addEventListener('click', showForgotView);
|
if (gf) gf.addEventListener('click', showForgotView);
|
||||||
var fbl = $('#forgot-back-login');
|
var fbl = $('#forgot-back-login');
|
||||||
if (fbl) fbl.addEventListener('click', hideForgotView);
|
if (fbl) fbl.addEventListener('click', hideForgotView);
|
||||||
// 第 86 条:管理员两步验证视图的返回链接
|
// 管理员两步验证视图的返回链接
|
||||||
var a2b = $('#a2fa-back');
|
var a2b = $('#a2fa-back');
|
||||||
if (a2b) a2b.addEventListener('click', hideAdmin2faView);
|
if (a2b) a2b.addEventListener('click', hideAdmin2faView);
|
||||||
|
|
||||||
@@ -334,7 +334,7 @@ function getDeviceId() {
|
|||||||
api('/auth/login', { username: username, password: password })
|
api('/auth/login', { username: username, password: password })
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
if (data.status !== 200) throw new Error(data.message || '登录失败');
|
if (data.status !== 200) throw new Error(data.message || '登录失败');
|
||||||
// 第 86 条:管理员密码验证通过——转两步验证视图(服务端未建会话)
|
// 管理员密码验证通过——转两步验证视图(服务端未建会话)
|
||||||
if (data.data && data.data.admin_2fa) {
|
if (data.data && data.data.admin_2fa) {
|
||||||
showAdmin2faView(data.data.ticket, data.data.email_masked);
|
showAdmin2faView(data.data.ticket, data.data.email_masked);
|
||||||
toast('密码验证通过,请完成邮箱验证', 'success');
|
toast('密码验证通过,请完成邮箱验证', 'success');
|
||||||
@@ -358,7 +358,7 @@ function getDeviceId() {
|
|||||||
/* ---------------- 注册 ---------------- */
|
/* ---------------- 注册 ---------------- */
|
||||||
var _REG_EMAIL_RE = /^[\w.%+-]+@[\w.-]+\.[A-Za-z]{2,}$/;
|
var _REG_EMAIL_RE = /^[\w.%+-]+@[\w.-]+\.[A-Za-z]{2,}$/;
|
||||||
var _REG_NAME_RE = /^[A-Za-z0-9_\u4e00-\u9fa5]{3,20}$/;
|
var _REG_NAME_RE = /^[A-Za-z0-9_\u4e00-\u9fa5]{3,20}$/;
|
||||||
// F5.1:注册弹窗内常驻错误行(长文案 2.6s toast 看不完)
|
// 注册弹窗内常驻错误行(长文案 2.6s toast 看不完)
|
||||||
function regShowError(msg) {
|
function regShowError(msg) {
|
||||||
toast(msg, 'error');
|
toast(msg, 'error');
|
||||||
var line = $('#reg-error-line');
|
var line = $('#reg-error-line');
|
||||||
@@ -391,8 +391,8 @@ function getDeviceId() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 找回密码 ---------------- */
|
/* ---------------- 找回密码 ---------------- */
|
||||||
// 第 84 条:F5.1 只交付了找回视图 DOM 未实现 JS——此处补齐全链路。
|
// 只交付了找回视图 DOM 未实现 JS——此处补齐全链路。
|
||||||
// 后端 /auth/forgot_password(第 79 条)已就绪:验证码原子消费、防枚举
|
// 后端 /auth/forgot_password 已就绪:验证码原子消费、防枚举
|
||||||
// 统一文案、login_seq 踢全会话、5 次/分/IP 限流;发码复用 /auth/send_code。
|
// 统一文案、login_seq 踢全会话、5 次/分/IP 限流;发码复用 /auth/send_code。
|
||||||
function forgotShowError(msg) {
|
function forgotShowError(msg) {
|
||||||
toast(msg, 'error');
|
toast(msg, 'error');
|
||||||
@@ -470,7 +470,7 @@ function getDeviceId() {
|
|||||||
.finally(function () { btn.disabled = false; btn.textContent = '重置密码'; });
|
.finally(function () { btn.disabled = false; btn.textContent = '重置密码'; });
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 管理员两步验证(第 86 条) ----------------
|
/* ---------------- 管理员两步验证 ----------------
|
||||||
* Step1 用户名+密码 → 服务端签发票据(不建会话);本视图完成
|
* Step1 用户名+密码 → 服务端签发票据(不建会话);本视图完成
|
||||||
* Step2 绑定邮箱比对(触发发码)与 Step3 验证码登录。普通用户不经过此流程。 */
|
* Step2 绑定邮箱比对(触发发码)与 Step3 验证码登录。普通用户不经过此流程。 */
|
||||||
var _a2faTicket = '';
|
var _a2faTicket = '';
|
||||||
@@ -495,7 +495,7 @@ function getDeviceId() {
|
|||||||
_a2faTicket = ticket || '';
|
_a2faTicket = ticket || '';
|
||||||
var mask = $('#a2fa-email-mask');
|
var mask = $('#a2fa-email-mask');
|
||||||
if (mask) mask.textContent = emailMask || '-';
|
if (mask) mask.textContent = emailMask || '-';
|
||||||
// 第 87 条:清掉上次会话残留的输入(错码/旧邮箱不再被原样重复提交)
|
// 清掉上次会话残留的输入(错码/旧邮箱不再被原样重复提交)
|
||||||
var ae = $('#a2fa-email'), ac = $('#a2fa-code');
|
var ae = $('#a2fa-email'), ac = $('#a2fa-code');
|
||||||
if (ae) ae.value = '';
|
if (ae) ae.value = '';
|
||||||
if (ac) ac.value = '';
|
if (ac) ac.value = '';
|
||||||
@@ -515,7 +515,7 @@ function getDeviceId() {
|
|||||||
a2faClearError();
|
a2faClearError();
|
||||||
a2faStopTimer();
|
a2faStopTimer();
|
||||||
}
|
}
|
||||||
// 第 87 条:关闭侧统一复位——登录弹窗的 X/遮罩/Escape/doLogout 关闭路径都调用,
|
// 关闭侧统一复位——登录弹窗的 X/遮罩/Escape/doLogout 关闭路径都调用,
|
||||||
// 不再只依赖"打开侧纪律";同时供 index.html 的 Escape 通用分支使用
|
// 不再只依赖"打开侧纪律";同时供 index.html 的 Escape 通用分支使用
|
||||||
window.resetAuthViews = function () { hideForgotView(); hideAdmin2faView(); };
|
window.resetAuthViews = function () { hideForgotView(); hideAdmin2faView(); };
|
||||||
function doA2faSendCode() {
|
function doA2faSendCode() {
|
||||||
@@ -527,7 +527,7 @@ function getDeviceId() {
|
|||||||
api('/auth/admin_login/email', { ticket: _a2faTicket, email: email })
|
api('/auth/admin_login/email', { ticket: _a2faTicket, email: email })
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
if (data.status === 401) {
|
if (data.status === 401) {
|
||||||
// 第 87 条:先复位视图再提示——错误行随视图隐藏,此处 toast 是唯一可达提示
|
// 先复位视图再提示——错误行随视图隐藏,此处 toast 是唯一可达提示
|
||||||
hideAdmin2faView();
|
hideAdmin2faView();
|
||||||
a2faShowError(data.message || '请重新登录');
|
a2faShowError(data.message || '请重新登录');
|
||||||
return;
|
return;
|
||||||
@@ -558,7 +558,7 @@ function getDeviceId() {
|
|||||||
api('/auth/admin_login/verify', { ticket: _a2faTicket, code: code })
|
api('/auth/admin_login/verify', { ticket: _a2faTicket, code: code })
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
if (data.status === 401) {
|
if (data.status === 401) {
|
||||||
// 第 87 条:先复位视图再提示(同上,错误行随视图隐藏)
|
// 先复位视图再提示(同上,错误行随视图隐藏)
|
||||||
hideAdmin2faView();
|
hideAdmin2faView();
|
||||||
a2faShowError(data.message || '请重新登录');
|
a2faShowError(data.message || '请重新登录');
|
||||||
return;
|
return;
|
||||||
@@ -582,7 +582,7 @@ function getDeviceId() {
|
|||||||
var email = ($('#reg-email') || {}).value || '';
|
var email = ($('#reg-email') || {}).value || '';
|
||||||
var code = ($('#reg-code') || {}).value || '';
|
var code = ($('#reg-code') || {}).value || '';
|
||||||
regClearError();
|
regClearError();
|
||||||
// 第 81 条:提交侧兜底闸(入口已统一走 openRegisterFlow,此处防任何程序化
|
// 提交侧兜底闸(入口已统一走 openRegisterFlow,此处防任何程序化
|
||||||
// 打开路径绕过)。fail-closed:协议闸缺失视为未同意。注册语境只查协议同意
|
// 打开路径绕过)。fail-closed:协议闸缺失视为未同意。注册语境只查协议同意
|
||||||
// (功能性 Cookie 不拦注册,拦解析/下载时另有提示);未同意时闸内已自动
|
// (功能性 Cookie 不拦注册,拦解析/下载时另有提示);未同意时闸内已自动
|
||||||
// 弹「隐私与协议设置」注册模式并 toast,此处不再重复提示
|
// 弹「隐私与协议设置」注册模式并 toast,此处不再重复提示
|
||||||
@@ -591,7 +591,7 @@ function getDeviceId() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!window.SITE_COOKIE_GATE('register')) return;
|
if (!window.SITE_COOKIE_GATE('register')) return;
|
||||||
// F1.1:前端即时校验(口径与后端一致,省一次网络往返)
|
// 前端即时校验(口径与后端一致,省一次网络往返)
|
||||||
if (!username || !password || !email || !code) { regShowError('请填写完整注册信息'); return; }
|
if (!username || !password || !email || !code) { regShowError('请填写完整注册信息'); return; }
|
||||||
if (!_REG_NAME_RE.test(username)) { regShowError('用户名需 3-20 位,仅支持中文、字母、数字、下划线,且不能纯下划线'); return; }
|
if (!_REG_NAME_RE.test(username)) { regShowError('用户名需 3-20 位,仅支持中文、字母、数字、下划线,且不能纯下划线'); return; }
|
||||||
if (!_REG_EMAIL_RE.test(email.trim())) { regShowError('邮箱格式不正确'); return; }
|
if (!_REG_EMAIL_RE.test(email.trim())) { regShowError('邮箱格式不正确'); return; }
|
||||||
@@ -619,10 +619,10 @@ function getDeviceId() {
|
|||||||
api('/auth/logout', {}).then(function () {
|
api('/auth/logout', {}).then(function () {
|
||||||
user = null; durationText = '';
|
user = null; durationText = '';
|
||||||
renderAuthArea();
|
renderAuthArea();
|
||||||
renderUserCenter(); // 第 83 条:同步个人中心 gate/content 显隐,与其他登出路径一致
|
renderUserCenter(); // 同步个人中心 gate/content 显隐,与其他登出路径一致
|
||||||
if (window.resetAuthViews) window.resetAuthViews(); // 第 87 条:登出复位登录弹窗视图
|
if (window.resetAuthViews) window.resetAuthViews(); // 登出复位登录弹窗视图
|
||||||
closeModal('register-modal'); closeModal('login-modal');
|
closeModal('register-modal'); closeModal('login-modal');
|
||||||
// 第 102 条(U15 拍板):退出连带清除个人网易云登录(index.html 注册的钩子:
|
// 退出连带清除个人网易云登录(index.html 注册的钩子:
|
||||||
// 清 localStorage 的个人 Cookie/解析账号选择并复位账号栏)
|
// 清 localStorage 的个人 Cookie/解析账号选择并复位账号栏)
|
||||||
if (typeof window.SITE_ON_LOGOUT === 'function') {
|
if (typeof window.SITE_ON_LOGOUT === 'function') {
|
||||||
try { window.SITE_ON_LOGOUT(); } catch (e) {}
|
try { window.SITE_ON_LOGOUT(); } catch (e) {}
|
||||||
@@ -680,7 +680,7 @@ function getDeviceId() {
|
|||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
if (data.status !== 200) throw new Error(data.message || '兑换失败');
|
if (data.status !== 200) throw new Error(data.message || '兑换失败');
|
||||||
durationText = (data.data && data.data.duration_text) || durationText;
|
durationText = (data.data && data.data.duration_text) || durationText;
|
||||||
// F69:同步更新内存余额(批量下载预检读的是 user.duration_seconds,
|
// 同步更新内存余额(批量下载预检读的是 user.duration_seconds,
|
||||||
// 原实现只改显示文字,预检仍用充值前的旧值)
|
// 原实现只改显示文字,预检仍用充值前的旧值)
|
||||||
if (user && data.data && typeof data.data.duration_seconds === 'number') {
|
if (user && data.data && typeof data.data.duration_seconds === 'number') {
|
||||||
user.duration_seconds = data.data.duration_seconds;
|
user.duration_seconds = data.data.duration_seconds;
|
||||||
@@ -700,7 +700,7 @@ function getDeviceId() {
|
|||||||
|
|
||||||
/* ---------------- 公告 ---------------- */
|
/* ---------------- 公告 ---------------- */
|
||||||
function maybeShowAnnouncement(force) {
|
function maybeShowAnnouncement(force) {
|
||||||
// F81:手动"查看公告"(force=true)无条件展示;
|
// 手动"查看公告"(force=true)无条件展示;
|
||||||
// 自动弹出仍按 popup_mode 与 once 已读版本判断
|
// 自动弹出仍按 popup_mode 与 once 已读版本判断
|
||||||
if (!force && (!ann || !ann.content || ann.popup_mode === 'off')) return;
|
if (!force && (!ann || !ann.content || ann.popup_mode === 'off')) return;
|
||||||
if (!force && ann && ann.popup_mode === 'once') {
|
if (!force && ann && ann.popup_mode === 'once') {
|
||||||
@@ -717,7 +717,7 @@ function getDeviceId() {
|
|||||||
var t = $('#announce-title'), c = $('#announce-content');
|
var t = $('#announce-title'), c = $('#announce-content');
|
||||||
if (t) t.textContent = ann.title || '系统公告';
|
if (t) t.textContent = ann.title || '系统公告';
|
||||||
if (c) c.innerHTML = String(ann.content).split('\n').map(function (l) { return escapeHtml(l); }).join('<br>');
|
if (c) c.innerHTML = String(ann.content).split('\n').map(function (l) { return escapeHtml(l); }).join('<br>');
|
||||||
// 第 98 条:首访 Cookie 协议窗显示中不叠加公告弹窗(双遮罩错乱);
|
// 首访 Cookie 协议窗显示中不叠加公告弹窗(双遮罩错乱);
|
||||||
// 跳过时不记已读,下次进入页面再弹
|
// 跳过时不记已读,下次进入页面再弹
|
||||||
var _cm = document.getElementById('cookie-modal');
|
var _cm = document.getElementById('cookie-modal');
|
||||||
if (_cm && _cm.classList.contains('show')) return;
|
if (_cm && _cm.classList.contains('show')) return;
|
||||||
@@ -747,7 +747,7 @@ function getDeviceId() {
|
|||||||
|
|
||||||
function uaBindSide() {
|
function uaBindSide() {
|
||||||
$$('#admin-side .admin-side-item[data-view]').forEach(function (item) {
|
$$('#admin-side .admin-side-item[data-view]').forEach(function (item) {
|
||||||
// 第 97 条(P3):侧栏项补键盘语义(F109 同型——tabindex/role + Enter/空格激活)
|
// 侧栏项补键盘语义(同型——tabindex/role + Enter/空格激活)
|
||||||
item.setAttribute('tabindex', '0');
|
item.setAttribute('tabindex', '0');
|
||||||
item.setAttribute('role', 'button');
|
item.setAttribute('role', 'button');
|
||||||
item.addEventListener('click', function () {
|
item.addEventListener('click', function () {
|
||||||
@@ -767,7 +767,7 @@ function getDeviceId() {
|
|||||||
});
|
});
|
||||||
var lo = $('#ua-logout');
|
var lo = $('#ua-logout');
|
||||||
if (lo) lo.addEventListener('click', doLogout);
|
if (lo) lo.addEventListener('click', doLogout);
|
||||||
// 第 97 条(P3):返回/退出两个无 data-view 的侧栏项同样补键盘语义
|
// 返回/退出两个无 data-view 的侧栏项同样补键盘语义
|
||||||
[back, lo].forEach(function (el) {
|
[back, lo].forEach(function (el) {
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.setAttribute('tabindex', '0');
|
el.setAttribute('tabindex', '0');
|
||||||
@@ -776,7 +776,7 @@ function getDeviceId() {
|
|||||||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); el.click(); }
|
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); el.click(); }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
/* ---- 播放 API 密钥(第 78 条) ---- */
|
/* ---- 播放 API 密钥 ---- */
|
||||||
var pkShow = $('#ua-playkey-show');
|
var pkShow = $('#ua-playkey-show');
|
||||||
var pkInput = $('#ua-playkey-value');
|
var pkInput = $('#ua-playkey-value');
|
||||||
function pkCopyToClipboard(text) {
|
function pkCopyToClipboard(text) {
|
||||||
@@ -891,12 +891,12 @@ function getDeviceId() {
|
|||||||
var isAdmin = u.role === 'admin';
|
var isAdmin = u.role === 'admin';
|
||||||
var disabled = (u.status === 0);
|
var disabled = (u.status === 0);
|
||||||
var banned = (u.status === 2);
|
var banned = (u.status === 2);
|
||||||
// 第 97 条(P3):数值字段同过转义(纵深防御——后端口径若变为字符串
|
// 数值字段同过转义(纵深防御——后端口径若变为字符串
|
||||||
// 不再形成管理后台存储型 XSS 面,与同表 username/email 口径对齐)
|
// 不再形成管理后台存储型 XSS 面,与同表 username/email 口径对齐)
|
||||||
var uid = escapeHtml(String(u.id));
|
var uid = escapeHtml(String(u.id));
|
||||||
var dur = escapeHtml(String(u.duration_seconds));
|
var dur = escapeHtml(String(u.duration_seconds));
|
||||||
var cred = u.duration_seconds < 0 ? '不限' : (dur + ' 次');
|
var cred = u.duration_seconds < 0 ? '不限' : (dur + ' 次');
|
||||||
var dev = [u.register_ip, u.trial_device].filter(Boolean).join(' · ') || '-'; // R5-79:IP 优先显示
|
var dev = [u.register_ip, u.trial_device].filter(Boolean).join(' · ') || '-'; // IP 优先显示
|
||||||
if (u.last_province) dev += ' · ' + u.last_province;
|
if (u.last_province) dev += ' · ' + u.last_province;
|
||||||
return '<tr' + ((disabled || banned) ? ' style="opacity:.55;"' : '') + '>' +
|
return '<tr' + ((disabled || banned) ? ' style="opacity:.55;"' : '') + '>' +
|
||||||
'<td>' + uid + '</td>' +
|
'<td>' + uid + '</td>' +
|
||||||
@@ -925,7 +925,7 @@ function getDeviceId() {
|
|||||||
.then(function (d) { toast(d.message || (d.status === 200 ? '已修改' : '修改失败'), d.status === 200 ? 'success' : 'error'); if (d.status === 200) uaLoadUsers(); });
|
.then(function (d) { toast(d.message || (d.status === 200 ? '已修改' : '修改失败'), d.status === 200 ? 'success' : 'error'); if (d.status === 200) uaLoadUsers(); });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// T3:管理员自助改密(需验证旧密码;改完其他设备全部下线)
|
// 管理员自助改密(需验证旧密码;改完其他设备全部下线)
|
||||||
$$('[data-uself]', body).forEach(function (b) {
|
$$('[data-uself]', body).forEach(function (b) {
|
||||||
b.addEventListener('click', function () {
|
b.addEventListener('click', function () {
|
||||||
var oldpwd = prompt('请输入当前密码:', '');
|
var oldpwd = prompt('请输入当前密码:', '');
|
||||||
@@ -990,7 +990,7 @@ function getDeviceId() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---- 全局 Cookie ---- */
|
/* ---- 全局 Cookie ---- */
|
||||||
var uaQrTimer = null, uaUnikey = '', uaQrGen = 0; // T2:轮询代次令牌(删除 Cookie 后作废旧轮询)
|
var uaQrTimer = null, uaUnikey = '', uaQrGen = 0; // 轮询代次令牌(删除 Cookie 后作废旧轮询)
|
||||||
function uaLoadCookieStatus() {
|
function uaLoadCookieStatus() {
|
||||||
api('/admin/cookie/status', {}).then(function (data) {
|
api('/admin/cookie/status', {}).then(function (data) {
|
||||||
var el = $('#ua-cookie-status');
|
var el = $('#ua-cookie-status');
|
||||||
@@ -1013,13 +1013,13 @@ function getDeviceId() {
|
|||||||
}
|
}
|
||||||
if (tip) tip.textContent = '请使用网易云音乐手机 APP 扫码';
|
if (tip) tip.textContent = '请使用网易云音乐手机 APP 扫码';
|
||||||
if (uaQrTimer) clearInterval(uaQrTimer);
|
if (uaQrTimer) clearInterval(uaQrTimer);
|
||||||
uaQrGen++; // T2:新轮询代次
|
uaQrGen++; // 新轮询代次
|
||||||
uaQrTimer = setInterval(uaQrPoll, 3000);
|
uaQrTimer = setInterval(uaQrPoll, 3000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function uaQrPoll() {
|
function uaQrPoll() {
|
||||||
if (!uaUnikey) { if (uaQrTimer) clearInterval(uaQrTimer); return; }
|
if (!uaUnikey) { if (uaQrTimer) clearInterval(uaQrTimer); return; }
|
||||||
var myGen = uaQrGen; // T2:本次轮询所属代次
|
var myGen = uaQrGen; // 本次轮询所属代次
|
||||||
api('/admin/qr/check', { unikey: uaUnikey }).then(function (data) {
|
api('/admin/qr/check', { unikey: uaUnikey }).then(function (data) {
|
||||||
if (myGen !== uaQrGen) return; // 期间已删除 Cookie/重开扫码,丢弃迟到响应
|
if (myGen !== uaQrGen) return; // 期间已删除 Cookie/重开扫码,丢弃迟到响应
|
||||||
var tip = $('#ua-qr-tip');
|
var tip = $('#ua-qr-tip');
|
||||||
@@ -1045,7 +1045,7 @@ function getDeviceId() {
|
|||||||
function uaCookieDelete() {
|
function uaCookieDelete() {
|
||||||
if (!window.confirm('确定删除全局 Cookie?删除后需重新扫码登录。')) return;
|
if (!window.confirm('确定删除全局 Cookie?删除后需重新扫码登录。')) return;
|
||||||
api('/admin/cookie/delete', {}).then(function (data) {
|
api('/admin/cookie/delete', {}).then(function (data) {
|
||||||
// T2:作废在途扫码轮询——迟到的 803 成功响应不得把刚删除的 Cookie 写回
|
// 作废在途扫码轮询——迟到的 803 成功响应不得把刚删除的 Cookie 写回
|
||||||
if (uaQrTimer) clearInterval(uaQrTimer);
|
if (uaQrTimer) clearInterval(uaQrTimer);
|
||||||
uaUnikey = '';
|
uaUnikey = '';
|
||||||
uaQrGen++;
|
uaQrGen++;
|
||||||
@@ -1099,7 +1099,7 @@ function getDeviceId() {
|
|||||||
var total = (data.data && data.data.total) || 0;
|
var total = (data.data && data.data.total) || 0;
|
||||||
var pages = (data.data && data.data.pages) || 1;
|
var pages = (data.data && data.data.pages) || 1;
|
||||||
uaLogPage = (data.data && data.data.page) || 1;
|
uaLogPage = (data.data && data.data.page) || 1;
|
||||||
window._uaLogPages = pages; // P3-43:next 按钮按真实页数封顶
|
window._uaLogPages = pages; // next 按钮按真实页数封顶
|
||||||
var el;
|
var el;
|
||||||
if ((el = $('#ua-log-page'))) el.textContent = uaLogPage;
|
if ((el = $('#ua-log-page'))) el.textContent = uaLogPage;
|
||||||
if ((el = $('#ua-log-pages'))) el.textContent = pages;
|
if ((el = $('#ua-log-pages'))) el.textContent = pages;
|
||||||
@@ -1147,7 +1147,7 @@ function getDeviceId() {
|
|||||||
uaLoadCards(0);
|
uaLoadCards(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 第 81 条:卡密管理操作列(禁用/启用/删除)+ 分页(F85 后端已支持,
|
// 卡密管理操作列(禁用/启用/删除)+ 分页(后端已支持,
|
||||||
// 不分页则超出最新 100 张的旧卡密在界面上永远管理不到)
|
// 不分页则超出最新 100 张的旧卡密在界面上永远管理不到)
|
||||||
var _uaCardPage = 0;
|
var _uaCardPage = 0;
|
||||||
var _uaCardTotal = 0;
|
var _uaCardTotal = 0;
|
||||||
@@ -1312,7 +1312,7 @@ function getDeviceId() {
|
|||||||
uaLoadDb();
|
uaLoadDb();
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
// 第 97 条(P2):全站唯一绕过统一请求封装的调用此前无失败兜底——
|
// 全站唯一绕过统一请求封装的调用此前无失败兜底——
|
||||||
// 网络失败/非 JSON 响应时管理员毫无感知还制造 unhandledrejection 噪音
|
// 网络失败/非 JSON 响应时管理员毫无感知还制造 unhandledrejection 噪音
|
||||||
toast((err && err.message) || '备份上传失败,请检查网络后重试', 'error');
|
toast((err && err.message) || '备份上传失败,请检查网络后重试', 'error');
|
||||||
});
|
});
|
||||||
@@ -1323,7 +1323,7 @@ function getDeviceId() {
|
|||||||
bindAuthModal();
|
bindAuthModal();
|
||||||
var s1 = $('#login-submit'); if (s1) s1.addEventListener('click', doLogin);
|
var s1 = $('#login-submit'); if (s1) s1.addEventListener('click', doLogin);
|
||||||
var s2 = $('#login-password'); if (s2) s2.addEventListener('keydown', function (e) { if (e.key === 'Enter') doLogin(); });
|
var s2 = $('#login-password'); if (s2) s2.addEventListener('keydown', function (e) { if (e.key === 'Enter') doLogin(); });
|
||||||
// F-3.1:回车提交覆盖全部输入框(登录用户名框 / 注册四框)
|
// 回车提交覆盖全部输入框(登录用户名框 / 注册四框)
|
||||||
var s2u = $('#login-username'); if (s2u) s2u.addEventListener('keydown', function (e) { if (e.key === 'Enter') doLogin(); });
|
var s2u = $('#login-username'); if (s2u) s2u.addEventListener('keydown', function (e) { if (e.key === 'Enter') doLogin(); });
|
||||||
var s3 = $('#reg-submit'); if (s3) s3.addEventListener('click', doRegister);
|
var s3 = $('#reg-submit'); if (s3) s3.addEventListener('click', doRegister);
|
||||||
var s4 = $('#reg-send-code'); if (s4) s4.addEventListener('click', sendCode);
|
var s4 = $('#reg-send-code'); if (s4) s4.addEventListener('click', sendCode);
|
||||||
@@ -1331,7 +1331,7 @@ function getDeviceId() {
|
|||||||
var el = $(sel);
|
var el = $(sel);
|
||||||
if (el) el.addEventListener('keydown', function (e) { if (e.key === 'Enter') doRegister(); });
|
if (el) el.addEventListener('keydown', function (e) { if (e.key === 'Enter') doRegister(); });
|
||||||
});
|
});
|
||||||
// F-4.1:密码明文切换(登录/注册/找回)
|
// 密码明文切换(登录/注册/找回)
|
||||||
[['#login-pwd-eye', '#login-password'], ['#reg-pwd-eye', '#reg-password'], ['#forgot-pwd-eye', '#forgot-password']].forEach(function (pair) {
|
[['#login-pwd-eye', '#login-password'], ['#reg-pwd-eye', '#reg-password'], ['#forgot-pwd-eye', '#forgot-password']].forEach(function (pair) {
|
||||||
var eye = $(pair[0]), input = $(pair[1]);
|
var eye = $(pair[0]), input = $(pair[1]);
|
||||||
if (!eye || !input) return;
|
if (!eye || !input) return;
|
||||||
@@ -1341,7 +1341,7 @@ function getDeviceId() {
|
|||||||
eye.textContent = show ? '隐藏' : '显示';
|
eye.textContent = show ? '隐藏' : '显示';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// F-2.1:发码后修改邮箱 → 提前终止倒计时并恢复按钮,提示需重发
|
// 发码后修改邮箱 → 提前终止倒计时并恢复按钮,提示需重发
|
||||||
var regEmail = $('#reg-email');
|
var regEmail = $('#reg-email');
|
||||||
if (regEmail) regEmail.addEventListener('input', function () {
|
if (regEmail) regEmail.addEventListener('input', function () {
|
||||||
var btn = $('#reg-send-code');
|
var btn = $('#reg-send-code');
|
||||||
@@ -1351,7 +1351,7 @@ function getDeviceId() {
|
|||||||
toast('邮箱已修改,请重新发送验证码', 'info');
|
toast('邮箱已修改,请重新发送验证码', 'info');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 第 84 条:找回视图——发码/提交/回车/改邮箱终止倒计时(对齐注册交互)
|
// 找回视图——发码/提交/回车/改邮箱终止倒计时(对齐注册交互)
|
||||||
var fsc = $('#forgot-send-code'); if (fsc) fsc.addEventListener('click', doForgotSendCode);
|
var fsc = $('#forgot-send-code'); if (fsc) fsc.addEventListener('click', doForgotSendCode);
|
||||||
var fsub = $('#forgot-submit'); if (fsub) fsub.addEventListener('click', doForgotReset);
|
var fsub = $('#forgot-submit'); if (fsub) fsub.addEventListener('click', doForgotReset);
|
||||||
['#forgot-email', '#forgot-code', '#forgot-password'].forEach(function (sel) {
|
['#forgot-email', '#forgot-code', '#forgot-password'].forEach(function (sel) {
|
||||||
@@ -1367,7 +1367,7 @@ function getDeviceId() {
|
|||||||
toast('邮箱已修改,请重新发送验证码', 'info');
|
toast('邮箱已修改,请重新发送验证码', 'info');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 第 86 条:管理员两步验证——发码/验证提交/回车提交
|
// 管理员两步验证——发码/验证提交/回车提交
|
||||||
var asc = $('#a2fa-send'); if (asc) asc.addEventListener('click', doA2faSendCode);
|
var asc = $('#a2fa-send'); if (asc) asc.addEventListener('click', doA2faSendCode);
|
||||||
var asb = $('#a2fa-submit'); if (asb) asb.addEventListener('click', doA2faVerify);
|
var asb = $('#a2fa-submit'); if (asb) asb.addEventListener('click', doA2faVerify);
|
||||||
['#a2fa-email', '#a2fa-code'].forEach(function (sel) {
|
['#a2fa-email', '#a2fa-code'].forEach(function (sel) {
|
||||||
@@ -1386,7 +1386,7 @@ function getDeviceId() {
|
|||||||
else { var t = $('#tab-usercenter'); if (t) t.click(); }
|
else { var t = $('#tab-usercenter'); if (t) t.click(); }
|
||||||
});
|
});
|
||||||
var ucRedeem = $('#uc-card-redeem'); if (ucRedeem) ucRedeem.addEventListener('click', doRedeem);
|
var ucRedeem = $('#uc-card-redeem'); if (ucRedeem) ucRedeem.addEventListener('click', doRedeem);
|
||||||
var ucAnn = $('#uc-ann-view'); if (ucAnn) ucAnn.addEventListener('click', function () { maybeShowAnnouncement(true); }); // F81:手动查看强制展示
|
var ucAnn = $('#uc-ann-view'); if (ucAnn) ucAnn.addEventListener('click', function () { maybeShowAnnouncement(true); }); // 手动查看强制展示
|
||||||
var ucLogout = $('#uc-logout'); if (ucLogout) ucLogout.addEventListener('click', doLogout);
|
var ucLogout = $('#uc-logout'); if (ucLogout) ucLogout.addEventListener('click', doLogout);
|
||||||
|
|
||||||
uaBindSide();
|
uaBindSide();
|
||||||
@@ -1400,7 +1400,7 @@ function getDeviceId() {
|
|||||||
var b8 = $('#ua-log-tab-ok'); if (b8) b8.addEventListener('click', function () { uaLogsType = 'success'; uaLogPage = 1; uaLoadLogs(); });
|
var b8 = $('#ua-log-tab-ok'); if (b8) b8.addEventListener('click', function () { uaLogsType = 'success'; uaLogPage = 1; uaLoadLogs(); });
|
||||||
var b9 = $('#ua-log-tab-fail'); if (b9) b9.addEventListener('click', function () { uaLogsType = 'fail'; uaLogPage = 1; uaLoadLogs(); });
|
var b9 = $('#ua-log-tab-fail'); if (b9) b9.addEventListener('click', function () { uaLogsType = 'fail'; uaLogPage = 1; uaLoadLogs(); });
|
||||||
var blogPrev = $('#ua-log-prev'); if (blogPrev) blogPrev.addEventListener('click', function () { if (uaLogPage > 1) { uaLogPage--; uaLoadLogs(); } });
|
var blogPrev = $('#ua-log-prev'); if (blogPrev) blogPrev.addEventListener('click', function () { if (uaLogPage > 1) { uaLogPage--; uaLoadLogs(); } });
|
||||||
// P3-43:next 补边界(原实现可翻到空页——前端无 pages 时放宽为 999 防呆)
|
// next 补边界(原实现可翻到空页——前端无 pages 时放宽为 999 防呆)
|
||||||
var blogNext = $('#ua-log-next'); if (blogNext) blogNext.addEventListener('click', function () { var maxP = window._uaLogPages || 999; if (uaLogPage < maxP) { uaLogPage++; uaLoadLogs(); } });
|
var blogNext = $('#ua-log-next'); if (blogNext) blogNext.addEventListener('click', function () { var maxP = window._uaLogPages || 999; if (uaLogPage < maxP) { uaLogPage++; uaLoadLogs(); } });
|
||||||
var blogClear = $('#ua-log-clear'); if (blogClear) blogClear.addEventListener('click', uaClearLogs);
|
var blogClear = $('#ua-log-clear'); if (blogClear) blogClear.addEventListener('click', uaClearLogs);
|
||||||
var b10 = $('#ua-card-gen'); if (b10) b10.addEventListener('click', uaGenCards);
|
var b10 = $('#ua-card-gen'); if (b10) b10.addEventListener('click', uaGenCards);
|
||||||
|
|||||||
+164
-164
File diff suppressed because it is too large.
Load diff
@@ -534,7 +534,7 @@
|
|||||||
"payload": null,
|
"payload": null,
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"body": null,
|
"body": null,
|
||||||
"html_len": 466261,
|
"html_len": 464333,
|
||||||
"html_sha256": "5ba897c2f07b43b6c34e998505fea385cb6a6c8bfd5115d014f80044a38c6200"
|
"html_sha256": "0596d87ccbe8e7b5a19494c5d1ab1fd11bc95b82665ddc600ba90e4f2f790b06"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -8,6 +8,10 @@ tests/golden/responses.json 是**删除 Flask 之前**从 Flask 应用捕获的
|
|||||||
说明:
|
说明:
|
||||||
- 会话 Cookie 在运行时用当前库的 login_seq 动态构造(快照不存 Cookie)。
|
- 会话 Cookie 在运行时用当前库的 login_seq 动态构造(快照不存 Cookie)。
|
||||||
- 请求为只读/确定性调用,不含实时上游数据(那些由 parity/单测覆盖)。
|
- 请求为只读/确定性调用,不含实时上游数据(那些由 parity/单测覆盖)。
|
||||||
|
- `index` 条目的 html_len/html_sha256 记的是**当前模板**渲染结果,不是 Flask 期数值
|
||||||
|
(该条目 body 为 null)。有意改模板后必须同步刷新这两项:
|
||||||
|
服务端渲染 = templates/index.html 去掉末尾换行,故
|
||||||
|
html_len = 文件字节数 - 1,html_sha256 = sha256(文件去掉末尾换行)。
|
||||||
"""
|
"""
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
|||||||
Reference in new issue
Block a user