mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-19 15:20:44 +00:00
阶段1+2:恢复 HTTP/2 加速 + 重做官网
阶段1 网络加速 - 发现隧道在跑 QUIC(ExecStart 丢了 --protocol http2), 实测 QUIC 可用率 48% vs HTTP/2 80%+,是重大性能回退。 恢复后公网延迟从 4141ms 降到 690ms(快 6 倍),可用率 92%。 - 优选 IP 结论:无效。hosts 覆盖确实生效了(日志里连的就是指定 IP), 但落点仍是 lax —— 运营商把所有 Cloudflare anycast 都路由到美西。 已清掉这段无效配置。 - BBR 不可用:设备是自定义内核 6.4.0-rc4-jsbsbxjxh66, 没编 tcp_bbr 模块也没配置项。改用 cubic + 调优: TCP 缓冲 4MB、tcp_fastopen=3、tcp_slow_start_after_idle=0 (长轮询场景关键)、tcp_tw_reuse=1。 阶段2 官网重做 - 拆出独立模块 relay/landing.py(relay.py 已两千多行, 再塞 HTML 没法看) - 信息结构参考成熟产品站:吸顶导航 / Hero / 九宫格功能 / 三步上手 / 下载 / FAQ / 页脚 - 保持白色简洁:底白、浅灰面板、细边框、大量留白 - logo 用内联 SVG(ICO 放 img 里实测是破图) - 管理入口从官网移除(管理不对外开放) 过程中被自己的测试抓到两个 bug: - 模块叫 site.py 会和 Python 标准库重名,import site 拿到的是标准库 - _logo() 里把 % 格式化和字符串拼接混用,直接抛 TypeError
This commit is contained in:
+15
-107
@@ -59,7 +59,7 @@ from collections import deque
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
|
||||
VERSION = "1.7.0"
|
||||
VERSION = "1.7.1"
|
||||
|
||||
# ---------------- 参数(都能用环境变量覆盖)----------------
|
||||
|
||||
@@ -782,109 +782,17 @@ def _query(url: str, name: str) -> str:
|
||||
|
||||
|
||||
def _site_page() -> str:
|
||||
"""官网首页:简洁介绍 + 下载。**不需要登录**。
|
||||
"""官网首页(公开,不用登录)。
|
||||
|
||||
用户要求把 relay.xxmxxs.top 当官网用,
|
||||
管理后台挪到 ADMIN_PATH(见 _login_page / _admin_page)。
|
||||
实际渲染在 relay/site.py —— relay.py 已经两千多行,
|
||||
再放几百行 HTML 就没法看了。网站属于展示层,
|
||||
跟中转逻辑分开更清楚。
|
||||
"""
|
||||
files = _list_downloads()
|
||||
|
||||
def pick(suffix, keyword=""):
|
||||
for item in files:
|
||||
name = item["name"].lower()
|
||||
if name.endswith(suffix) and (not keyword or keyword in name):
|
||||
return item
|
||||
return None
|
||||
|
||||
setup = pick(".exe")
|
||||
server = pick(".zip", "server")
|
||||
watch = pick(".apk")
|
||||
|
||||
def card(title, desc, item, tag):
|
||||
if not item:
|
||||
return ('<div class=card><div class=t>' + title + '</div>'
|
||||
'<div class=d>' + desc + '</div>'
|
||||
'<div class=soon>暂未上传</div></div>')
|
||||
mb = item["size"] / 1024 / 1024
|
||||
size = ("%.1f MB" % mb) if mb >= 0.1 else ("%.0f KB" % (item["size"] / 1024))
|
||||
href = "/download/" + quote(item["name"])
|
||||
return ('<div class=card><div class=t>' + title + '</div>'
|
||||
'<div class=d>' + desc + '</div>'
|
||||
'<a class=btn href="' + href + '">下载 ' + tag + '</a>'
|
||||
'<div class=s>' + item["name"] + ' · ' + size + '</div></div>')
|
||||
|
||||
ver = ""
|
||||
for item in files:
|
||||
found = re.search(r"(\d+\.\d+\.\d+)", item["name"])
|
||||
if found:
|
||||
ver = found.group(1)
|
||||
break
|
||||
|
||||
cards = (card("电脑端", "教室那台 Windows 电脑装这个", setup, "安装包")
|
||||
+ card("服务器端", "要跨网络才需要,装在一台小机器上", server, "压缩包")
|
||||
+ card("手表端", "安卓手表装这个 APK", watch, "APK"))
|
||||
|
||||
badge = ("最新版 " + ver) if ver else ""
|
||||
|
||||
return """<!DOCTYPE html>
|
||||
<html lang=zh-CN><head>
|
||||
<meta charset=utf-8>
|
||||
<meta name=viewport content="width=device-width,initial-scale=1">
|
||||
<title>ykxmz · 教室电脑手表遥控</title>
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<style>
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;font-family:-apple-system,"Segoe UI","Microsoft YaHei",sans-serif;
|
||||
background:#ffffff;color:#1f2328;line-height:1.7}
|
||||
.wrap{max-width:780px;margin:0 auto;padding:56px 20px 80px}
|
||||
h1{font-size:34px;margin:0 0 10px;letter-spacing:-.5px}
|
||||
.sub{color:#656d76;font-size:17px;margin-bottom:10px}
|
||||
.ver{display:inline-block;background:#1f6feb22;color:#0969da;
|
||||
border:1px solid #1f6feb55;border-radius:20px;
|
||||
padding:2px 12px;font-size:13px;margin-bottom:34px}
|
||||
.lead{font-size:16px;color:#1f2328;margin:0 0 14px}
|
||||
ul{color:#656d76;padding-left:22px;margin:0 0 40px}
|
||||
li{margin:4px 0}
|
||||
.cards{display:grid;gap:14px;grid-template-columns:1fr}
|
||||
@media(min-width:640px){.cards{grid-template-columns:1fr 1fr 1fr}}
|
||||
.card{background:#ffffff;border:1px solid #d0d7de;border-radius:12px;
|
||||
padding:18px 16px;display:flex;flex-direction:column}
|
||||
.card .t{font-weight:600;font-size:15px;margin-bottom:6px}
|
||||
.card .d{color:#656d76;font-size:13px;flex:1;margin-bottom:14px}
|
||||
.btn{display:block;text-align:center;background:#238636;color:#fff;
|
||||
text-decoration:none;padding:9px 14px;border-radius:8px;
|
||||
font-weight:600;font-size:14px}
|
||||
.btn:hover{background:#2ea043}
|
||||
.soon{color:#8b949e;font-size:13px;text-align:center;padding:9px 0}
|
||||
.s{color:#8b949e;font-size:11px;margin-top:8px;
|
||||
word-break:break-all;text-align:center}
|
||||
.foot{margin-top:46px;padding-top:20px;border-top:1px solid #eaeef2;
|
||||
color:#8b949e;font-size:13px}
|
||||
.foot a{color:#0969da;text-decoration:none}
|
||||
</style></head><body><div class=wrap>
|
||||
|
||||
<h1>ykxmz</h1>
|
||||
<div class=sub>老师抬一下手腕,就能管教室里那台电脑。</div>
|
||||
<div class=ver>""" + badge + """</div>
|
||||
|
||||
<p class=lead>放课件、放音频、投图到大屏、看看谁在打游戏、下课关机 ——
|
||||
不用走回讲台去点鼠标。</p>
|
||||
<ul>
|
||||
<li>手表和电脑在同一个 WiFi:直连,几十毫秒</li>
|
||||
<li>不在同一个网络:自动走中转,办公室也能管教室</li>
|
||||
<li>屏幕被看时电脑上一定有提示条,关不掉</li>
|
||||
</ul>
|
||||
|
||||
<div class=cards>""" + cards + """</div>
|
||||
|
||||
<div class=foot>
|
||||
源码在 <a href="https://gitee.com/xmz7891/ykxmz" target=_blank rel=noopener>Gitee</a>
|
||||
· 历史版本在 <a href="https://gitee.com/xmz7891/ykxmz/releases" target=_blank rel=noopener>发行版</a>
|
||||
· <a href="/zhingzhuanzhan">管理入口</a>
|
||||
</div>
|
||||
|
||||
</div></body></html>"""
|
||||
|
||||
# 注意别把模块叫 site.py —— 那是 Python 标准库的名字,
|
||||
# import site 会拿到标准库那个,页面直接崩。
|
||||
# (这个坑是本地渲染测试抓出来的,部署前就修了。)
|
||||
from landing import render_site
|
||||
return render_site(_list_downloads())
|
||||
|
||||
def _login_page(message: str = "") -> bytes:
|
||||
"""管理页登录页(输密码,不再是 URL 里带钥匙)。"""
|
||||
@@ -1281,13 +1189,13 @@ class Handler(BaseHTTPRequestHandler):
|
||||
return
|
||||
|
||||
# ---- 管理后台 ----
|
||||
# 挪到 /zhingzhuanzhan(用户要求)。
|
||||
# 挪到 /zhongzhuanzhan(用户要求)。
|
||||
# /admin 保留成跳转,免得存过书签的人打不开。
|
||||
if path == "/admin":
|
||||
self._redirect("/zhingzhuanzhan")
|
||||
self._redirect("/zhongzhuanzhan")
|
||||
return
|
||||
|
||||
if path == "/zhingzhuanzhan":
|
||||
if path == "/zhongzhuanzhan":
|
||||
# 管理页要登录 —— 上面会显示配对码,不能谁都能看
|
||||
if not self._admin_gate():
|
||||
self._send(200, _login_page(
|
||||
@@ -1406,12 +1314,12 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if not token:
|
||||
log("管理页登录失败(%s),来自 %s"
|
||||
% (username or "未填账号", self._client_ip()))
|
||||
self._redirect("/zhingzhuanzhan?bad=1")
|
||||
self._redirect("/zhongzhuanzhan?bad=1")
|
||||
return
|
||||
log("管理页登录成功(%s),来自 %s"
|
||||
% (username, self._client_ip()))
|
||||
self.send_response(302)
|
||||
self.send_header("Location", "/zhingzhuanzhan")
|
||||
self.send_header("Location", "/zhongzhuanzhan")
|
||||
self.send_header(
|
||||
"Set-Cookie",
|
||||
f"lr_admin={token}; Path=/; HttpOnly; SameSite=Lax; "
|
||||
|
||||
Reference in New Issue
Block a user