python端登录完成

This commit is contained in:
super
2026-05-27 12:41:59 +08:00
parent 73ac9187a6
commit ca4a2cd07a
13 changed files with 170 additions and 89 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.module-page[data-v-49f8fcb3]{min-height:100vh;background:#1a1a1a}.main-content[data-v-49f8fcb3]{display:flex;height:calc(100vh - 56px);min-height:calc(100vh - 56px)}.left-panel[data-v-49f8fcb3]{width:400px;background:#1e1e1e;padding:20px;overflow-y:auto;border-right:1px solid #2a2a2a}.right-panel[data-v-49f8fcb3]{flex:1;min-width:0;background:#1a1a1a;display:flex;flex-direction:column}.section-title[data-v-49f8fcb3],.subsection-title[data-v-49f8fcb3]{font-size:13px;color:#bbb;margin-bottom:10px}.upload-zone[data-v-49f8fcb3]{border:1px dashed #3a3a3a;border-radius:10px;padding:18px;background:#252525;margin-bottom:18px}.hint[data-v-49f8fcb3],.loading-msg[data-v-49f8fcb3],.files[data-v-49f8fcb3],.muted[data-v-49f8fcb3]{color:#888;font-size:12px;line-height:1.5}.link[data-v-49f8fcb3]{color:#6ea8fe;text-decoration:none}.link[data-v-49f8fcb3]:hover{color:#9fc5ff}.btns[data-v-49f8fcb3],.run-row[data-v-49f8fcb3]{display:flex;gap:10px;flex-wrap:wrap}.opt-btn[data-v-49f8fcb3],.btn-run[data-v-49f8fcb3],.btn-delete[data-v-49f8fcb3],.download[data-v-49f8fcb3]{border:none;cursor:pointer;border-radius:7px}.opt-btn[data-v-49f8fcb3]{padding:8px 14px;color:#ccc;background:#2a2a2a;border:1px solid #3a3a3a}.btn-run[data-v-49f8fcb3]{padding:10px 18px;color:#fff;background:#3498db;font-weight:600}.btn-queue[data-v-49f8fcb3]{background:#27ae60}.btn-run[data-v-49f8fcb3]:disabled{opacity:.55;cursor:not-allowed}.selected-files[data-v-49f8fcb3]{margin-top:14px;color:#999;font-size:12px;word-break:break-all}.selected-files span[data-v-49f8fcb3]{display:block;margin:4px 0}.parse-card[data-v-49f8fcb3],.queue-payload[data-v-49f8fcb3]{margin-top:14px;padding:12px;border:1px solid #2a2a2a;border-radius:8px;background:#202020;color:#b8c1cc;font-size:12px}.queue-payload[data-v-49f8fcb3]{max-height:220px;overflow:auto;color:#8fd3ff;white-space:pre-wrap}.panel-header[data-v-49f8fcb3]{padding:16px 20px;border-bottom:1px solid #2a2a2a;font-size:15px;font-weight:600;color:#ddd}.task-list-wrap[data-v-49f8fcb3]{flex:1;padding:16px 20px;overflow:auto}.clean-result-summary[data-v-49f8fcb3]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:16px}.summary-card[data-v-49f8fcb3]{padding:14px 16px;border:1px solid #2a2a2a;border-radius:8px;background:#1e1e1e}.summary-card strong[data-v-49f8fcb3]{display:block;margin-top:8px;color:#eaf4ff;font-size:22px}.summary-label[data-v-49f8fcb3]{color:#8d8d8d;font-size:12px}.result-list-wrap[data-v-49f8fcb3]{border:1px solid #2a2a2a;border-radius:8px;background:#1e1e1e;min-height:180px;margin:0 0 16px}.result-list-header[data-v-49f8fcb3]{display:flex;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #2a2a2a;color:#ddd;font-size:14px}.empty-tasks[data-v-49f8fcb3]{color:#666;font-size:13px;padding:18px;text-align:center}.task-list[data-v-49f8fcb3]{list-style:none;margin:0;padding:12px}.task-item[data-v-49f8fcb3]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:12px 14px;border:1px solid #2a2a2a;border-radius:8px;margin-bottom:8px;background:#222}.left[data-v-49f8fcb3]{flex:1;min-width:0}.id[data-v-49f8fcb3]{color:#e0e0e0;font-size:13px;font-weight:600}.task-right[data-v-49f8fcb3]{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.status[data-v-49f8fcb3]{padding:4px 10px;border-radius:6px;font-size:12px}.status.success[data-v-49f8fcb3]{background:#2ecc712e;color:#2ecc71}.status.failed[data-v-49f8fcb3]{background:#e74c3c2e;color:#ff6b6b}.status.running[data-v-49f8fcb3]{background:#3498db2e;color:#3498db}.status.pending[data-v-49f8fcb3]{background:#95a5a62e;color:#bdc3c7}.result-hint[data-v-49f8fcb3]{margin-top:6px;color:#e0b96d}.file-progress[data-v-49f8fcb3]{margin-top:8px;max-width:520px}.file-progress-meta[data-v-49f8fcb3]{display:flex;justify-content:space-between;gap:12px;color:#d8c278;font-size:12px}.file-progress-track[data-v-49f8fcb3]{margin-top:5px;height:8px;border-radius:999px;overflow:hidden;background:#303030;border:1px solid #3b3b3b}.file-progress-bar[data-v-49f8fcb3]{height:100%;border-radius:inherit;background:linear-gradient(90deg,#4aa3ff,#f0c75e);transition:width .25s ease}.file-progress-count[data-v-49f8fcb3]{margin-top:4px;color:#858585;font-size:11px}.download[data-v-49f8fcb3]{padding:6px 10px;color:#d6ecff;background:#3498db2e}.btn-delete[data-v-49f8fcb3]{padding:6px 10px;color:#ff8f8f;background:#e74c3c1f}@media(max-width:1100px){.main-content[data-v-49f8fcb3]{flex-direction:column;height:auto}.left-panel[data-v-49f8fcb3]{width:100%;border-right:none;border-bottom:1px solid #2a2a2a}.clean-result-summary[data-v-49f8fcb3]{grid-template-columns:repeat(2,minmax(0,1fr))}}

View File

@@ -0,0 +1 @@
.module-page[data-v-a27e815e]{min-height:100vh;background:#1a1a1a}.main-content[data-v-a27e815e]{display:flex;height:calc(100vh - 56px);min-height:calc(100vh - 56px)}.left-panel[data-v-a27e815e]{width:400px;background:#1e1e1e;padding:20px;overflow-y:auto;border-right:1px solid #2a2a2a}.right-panel[data-v-a27e815e]{flex:1;min-width:0;background:#1a1a1a;display:flex;flex-direction:column}.section-title[data-v-a27e815e],.subsection-title[data-v-a27e815e]{font-size:13px;color:#bbb;margin-bottom:10px}.upload-zone[data-v-a27e815e]{border:1px dashed #3a3a3a;border-radius:10px;padding:18px;background:#252525;margin-bottom:18px}.hint[data-v-a27e815e],.loading-msg[data-v-a27e815e],.files[data-v-a27e815e],.muted[data-v-a27e815e]{color:#888;font-size:12px;line-height:1.5}.link[data-v-a27e815e]{color:#6ea8fe;text-decoration:none}.link[data-v-a27e815e]:hover{color:#9fc5ff}.btns[data-v-a27e815e],.run-row[data-v-a27e815e]{display:flex;gap:10px;flex-wrap:wrap}.opt-btn[data-v-a27e815e],.btn-run[data-v-a27e815e],.btn-delete[data-v-a27e815e],.download[data-v-a27e815e]{border:none;cursor:pointer;border-radius:7px}.opt-btn[data-v-a27e815e]{padding:8px 14px;color:#ccc;background:#2a2a2a;border:1px solid #3a3a3a}.btn-run[data-v-a27e815e]{padding:10px 18px;color:#fff;background:#3498db;font-weight:600}.btn-queue[data-v-a27e815e]{background:#27ae60}.btn-run[data-v-a27e815e]:disabled{opacity:.55;cursor:not-allowed}.selected-files[data-v-a27e815e]{margin-top:14px;color:#999;font-size:12px;word-break:break-all}.selected-files span[data-v-a27e815e]{display:block;margin:4px 0}.parse-card[data-v-a27e815e],.queue-payload[data-v-a27e815e]{margin-top:14px;padding:12px;border:1px solid #2a2a2a;border-radius:8px;background:#202020;color:#b8c1cc;font-size:12px}.queue-payload[data-v-a27e815e]{max-height:220px;overflow:auto;color:#8fd3ff;white-space:pre-wrap}.panel-header[data-v-a27e815e]{padding:16px 20px;border-bottom:1px solid #2a2a2a;font-size:15px;font-weight:600;color:#ddd}.task-list-wrap[data-v-a27e815e]{flex:1;padding:16px 20px;overflow:auto}.clean-result-summary[data-v-a27e815e]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:16px}.summary-card[data-v-a27e815e]{padding:14px 16px;border:1px solid #2a2a2a;border-radius:8px;background:#1e1e1e}.summary-card strong[data-v-a27e815e]{display:block;margin-top:8px;color:#eaf4ff;font-size:22px}.summary-label[data-v-a27e815e]{color:#8d8d8d;font-size:12px}.result-list-wrap[data-v-a27e815e]{border:1px solid #2a2a2a;border-radius:8px;background:#1e1e1e;min-height:180px;margin:0 0 16px}.result-list-header[data-v-a27e815e]{display:flex;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #2a2a2a;color:#ddd;font-size:14px}.empty-tasks[data-v-a27e815e]{color:#666;font-size:13px;padding:18px;text-align:center}.task-list[data-v-a27e815e]{list-style:none;margin:0;padding:12px}.task-item[data-v-a27e815e]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:12px 14px;border:1px solid #2a2a2a;border-radius:8px;margin-bottom:8px;background:#222}.left[data-v-a27e815e]{flex:1;min-width:0}.id[data-v-a27e815e]{color:#e0e0e0;font-size:13px;font-weight:600}.task-right[data-v-a27e815e]{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.status[data-v-a27e815e]{padding:4px 10px;border-radius:6px;font-size:12px}.status.success[data-v-a27e815e]{background:#2ecc712e;color:#2ecc71}.status.failed[data-v-a27e815e]{background:#e74c3c2e;color:#ff6b6b}.status.running[data-v-a27e815e]{background:#3498db2e;color:#3498db}.status.pending[data-v-a27e815e]{background:#95a5a62e;color:#bdc3c7}.result-hint[data-v-a27e815e]{margin-top:6px;color:#e0b96d}.file-progress[data-v-a27e815e]{margin-top:8px;max-width:520px}.file-progress-meta[data-v-a27e815e]{display:flex;justify-content:space-between;gap:12px;color:#d8c278;font-size:12px}.file-progress-track[data-v-a27e815e]{margin-top:5px;height:8px;border-radius:999px;overflow:hidden;background:#303030;border:1px solid #3b3b3b}.file-progress-bar[data-v-a27e815e]{height:100%;border-radius:inherit;background:linear-gradient(90deg,#4aa3ff,#f0c75e);transition:width .25s ease}.file-progress-count[data-v-a27e815e]{margin-top:4px;color:#858585;font-size:11px}.download[data-v-a27e815e]{padding:6px 10px;color:#d6ecff;background:#3498db2e}.btn-delete[data-v-a27e815e]{padding:6px 10px;color:#ff8f8f;background:#e74c3c1f}@media(max-width:1100px){.main-content[data-v-a27e815e]{flex-direction:column;height:auto}.left-panel[data-v-a27e815e]{width:100%;border-right:none;border-bottom:1px solid #2a2a2a}.clean-result-summary[data-v-a27e815e]{grid-template-columns:repeat(2,minmax(0,1fr))}}

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,7 @@ JWT 工具:解析 Java 端签发的 token、从请求中提取 token。
"""
import os
import sys
import time
from typing import Optional, Tuple
from flask import request
@@ -25,6 +26,14 @@ except ImportError as _e:
_DEFAULT_SECRET = "please-change-this-secret-please-rotate-at-least-32-bytes"
COOKIE_NAME = os.getenv("AIIMAGE_AUTH_COOKIE_NAME", "aiimage_token")
# 桌面端与 Java 服务器时钟可能漂移(笔记本休眠、用户手动改时间、跨时区等),
# 给 JWT exp/nbf 校验留出容差,避免 /api/auth/sync 因为时间不同步而 401。
# 默认 5 分钟,可通过 AIIMAGE_JWT_LEEWAY_SECONDS 覆盖。
try:
_JWT_LEEWAY_SECONDS = int(os.getenv("AIIMAGE_JWT_LEEWAY_SECONDS", "300"))
except ValueError:
_JWT_LEEWAY_SECONDS = 300
def _signing_key() -> bytes:
"""与 Java JwtService.signingKey 保持一致UTF-8 字节,不足 32 字节右侧补 0。"""
@@ -35,6 +44,26 @@ def _signing_key() -> bytes:
return key_bytes
def _describe_clock_skew(token: str) -> str:
"""解析 token 里的 iat/exp与本地时钟比较返回 ' iat=.. exp=.. now=.. skew=..s' 字符串。
仅供日志使用,不做安全决策;解析失败返回空串,不影响主流程。"""
if pyjwt is None:
return ""
try:
unverified = pyjwt.decode(token, options={"verify_signature": False, "verify_exp": False, "verify_iat": False, "verify_nbf": False})
except Exception:
return ""
iat = unverified.get("iat")
exp = unverified.get("exp")
now = int(time.time())
parts = [f"now={now}"]
if isinstance(iat, (int, float)):
parts.append(f"iat={int(iat)} skew_iat={now - int(iat)}s")
if isinstance(exp, (int, float)):
parts.append(f"exp={int(exp)} skew_exp={now - int(exp)}s")
return ", " + " ".join(parts)
def parse_token_with_reason(token: str) -> Tuple[Optional[dict], Optional[str]]:
"""解析 JWT返回 (payload, error_reason)。
payload 命中时 error_reason 为 None失败时 payload 为 Noneerror_reason 描述根因。"""
@@ -49,10 +78,17 @@ def parse_token_with_reason(token: str) -> Tuple[Optional[dict], Optional[str]]:
return None, f"无法解析 token header: {type(e).__name__}: {e}"
alg = header.get("alg") or "?"
try:
payload = pyjwt.decode(token, _signing_key(), algorithms=["HS256", "HS384", "HS512"])
payload = pyjwt.decode(
token,
_signing_key(),
algorithms=["HS256", "HS384", "HS512"],
leeway=_JWT_LEEWAY_SECONDS,
)
except Exception as e:
# 常见ExpiredSignatureError / InvalidSignatureError / DecodeError / InvalidAlgorithmError
return None, f"{type(e).__name__}: {e} (token alg={alg})"
# 把 token 内 exp/iat 与本地时钟一起打出来,定位"时间漂移"类失败更直接
skew_info = _describe_clock_skew(token)
return None, f"{type(e).__name__}: {e} (token alg={alg}{skew_info})"
sub = payload.get("sub")
try:
user_id = int(sub) if sub is not None else None