From 8c1956cdebbde7e4585190fc832b3a3f722f2ad8 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Thu, 30 Jul 2026 09:31:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(dnfs):=20=E5=B7=A6=E4=B8=8A=E8=A7=92?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF=E9=9D=A2=E6=9D=BF=EF=BC=88?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E6=8A=98=E5=8F=A0=EF=BC=89=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20/whoami=20=E8=BA=AB=E4=BB=BD=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dnfs 新增 POST /whoami:解 Token payload 返回 user/is_admin,无或畸形 Token 返回 login=false(HttpOnly Cookie 模式下 JS 无法读 Token,身份 必须由后端下发);static 加 /whoami 代理(Cookie 转 Bearer);前端 dnfs.html 左上新增 userpanel,镜像 minimap 样式默认折叠,展开显示 用户名/角色/退出登录(POST /logout 清 Cookie 后跳登录页)。 --- dnfs/dnfs.py | 22 ++++++++++++++ static/fsys.py | 16 ++++++++++ static/public/css/theme.css | 39 ++++++++++++++++++++---- static/public/dnfs.html | 1 + static/public/js/dnfs.js | 59 +++++++++++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 6 deletions(-) diff --git a/dnfs/dnfs.py b/dnfs/dnfs.py index c9733fb..60c49c8 100755 --- a/dnfs/dnfs.py +++ b/dnfs/dnfs.py @@ -273,6 +273,28 @@ def get_notes(): return jsonify(result), 200 +# 当前用户身份查询:配合前端用户面板,返回 user / is_admin。 +# 与 get_notes 同款可选解析:无 Token 或畸形 Token 按未登录处理(不 500)。 +@app.route('/whoami', methods=["POST"]) +def whoami(): + info = None + auth: str = request.headers.get('Authorization', '') + if auth.startswith('Bearer '): + try: + info = decode_token(auth[7:]) + except Exception: + info = None + + if info is None: + return jsonify({'login': False}), 200 + + return jsonify({ + 'login': True, + 'user': info.get('user'), + 'is_admin': bool(info.get('is_admin')) + }), 200 + + # eg: {"id": 1} @app.route('/delete_note', methods=['POST']) diff --git a/static/fsys.py b/static/fsys.py index 4ced9a8..dbbf611 100644 --- a/static/fsys.py +++ b/static/fsys.py @@ -474,6 +474,22 @@ def proxy_update_note(): return _cors(make_response(_json.dumps({"error": str(e)}), 502, {"Content-Type": "application/json"})) +@app.route('/whoami', methods=['POST', 'OPTIONS']) +def proxy_whoami(): + if request.method == "OPTIONS": return _cors(make_response("", 204)) + try: + data = request.get_json(force=True, silent=True) or {} + h = {"Content-Type": "application/json"} + if "Authorization" in request.headers: h["Authorization"] = request.headers["Authorization"] + elif request.cookies.get("q4_token"): h["Authorization"] = f"Bearer {request.cookies['q4_token']}" # HttpOnly Cookie 模式:Cookie 转 Bearer + req = _urlreq.Request(f"{DNFS_URL}/whoami", data=_json.dumps(data).encode(), headers=h, method="POST") + with _urlreq.urlopen(req, timeout=15) as r: + return _cors(make_response(r.read().decode(), 200, {"Content-Type": "application/json"})) + except Exception as e: + logs.warning("static", "dnfs whoami 代理失败", extra={"error": str(e)}) + return _cors(make_response(_json.dumps({"error": str(e)}), 502, {"Content-Type": "application/json"})) + + @app.route('/api/proxy_send_code', methods=['POST']) def proxy_send_code(): from flask import request, jsonify diff --git a/static/public/css/theme.css b/static/public/css/theme.css index 1e4ccfe..092dab6 100644 --- a/static/public/css/theme.css +++ b/static/public/css/theme.css @@ -365,6 +365,33 @@ input[type="button"]:disabled { font-weight: 600; } +/* ===== User Panel (dnfs 左上角用户信息,镜像 minimap 样式) ===== */ +.userpanel { + position: fixed; + left: 1rem; + top: 1rem; + z-index: 100; + display: flex; + flex-direction: column; + gap: 0.4rem; + max-height: 80vh; + overflow-y: auto; + padding: 0.6rem 0.75rem; + border-radius: var(--radius-md); + background: rgba(253, 246, 238, 0.72); + border: 1px solid var(--border-light); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + box-shadow: var(--shadow-md); + transition: background 0.2s, border-color 0.2s; +} +@media (prefers-color-scheme: dark) { + .userpanel { + background: rgba(30, 23, 18, 0.72); + border-color: var(--border); + } +} + /* ===== Minimap (dnfs) ===== */ .minimap { position: fixed; @@ -392,8 +419,8 @@ input[type="button"]:disabled { border-color: var(--border); } } -.minimap:empty { display: none; } -.minimap .tag { +.minimap:empty, .userpanel:empty { display: none; } +.minimap .tag, .userpanel .tag { display: inline-block; max-width: 140px; overflow: hidden; @@ -416,18 +443,18 @@ input[type="button"]:disabled { border-radius: 4px; } .minimap::-webkit-scrollbar-thumb:hover { background: var(--muted); } -.minimap .tag:hover { +.minimap .tag:hover, .userpanel .tag:hover { color: var(--accent-dark); background: var(--accent-soft); border-color: var(--accent); } -.minimap .tag.back { +.minimap .tag.back, .userpanel .tag.back { color: var(--accent); background: var(--accent-soft); border-color: var(--accent); margin-bottom: 0.25rem; } -.minimap .tag.fold-btn { +.minimap .tag.fold-btn, .userpanel .tag.fold-btn { color: var(--fg-secondary); background: transparent; border: 1px solid var(--border); @@ -438,7 +465,7 @@ input[type="button"]:disabled { cursor: pointer; transition: color 0.15s, background 0.15s, border-color 0.15s; } -.minimap .tag.fold-btn:hover { +.minimap .tag.fold-btn:hover, .userpanel .tag.fold-btn:hover { color: var(--accent-dark); background: var(--accent-soft); border-color: var(--accent); diff --git a/static/public/dnfs.html b/static/public/dnfs.html index 56d0f4e..31286b3 100644 --- a/static/public/dnfs.html +++ b/static/public/dnfs.html @@ -9,6 +9,7 @@