"""登录助手 — 解析浏览器导出的 cookies JSON,保存为 session.json。 Cookie 白名单基于 HAR 抓包分析: - pickup-web 所有业务 API 统一使用 9 个 cookie(见 _REQUIRED_COOKIE_NAMES) - JSESSIONID 仅 portal 主页面需要(加载 pickup 页面时作为 Referer) - enable_HFZrs6pXKulz 仅 portal UI 配置,pickup-web 不需要 """ from __future__ import annotations import json import os import sys # 基于 portal-to-pickup-full-flow.har 分析得出的必需 cookie # pickup-web (/pickup-web/a/*) 所有业务 API 请求都携带这 10 个 cookie # 缺失任何一个都会导致请求失败 REQUIRED_COOKIE_NAMES: set[str] = { # 会话认证(核心 — 缺失则 401/重定向到登录页) "jdpt.session.id", # 组织层级(固定值,session 生命周期内不变,缺失则权限异常) "CORAL_GRAYLEVEL", "PROV_GRAYLEVEL", "CITY_GRAYLEVEL", "COUNTY_GRAYLEVEL", "ORG_GRAYLEVEL", # $_ts 安全框架 token(缺失则 c93l5Fya 校验失败,接口拒绝服务) "HFZrs6pXKulzS", # 会话级 token,固定不变 "HFZrs6pXKulzO", # 组织级 token,固定不变 "HFZrs6pXKulzP", # 请求级动态 token,服务端 Set-Cookie 持续更新 # portal 主页面会话(缺失则无法加载 pickup 页面,fetch_page_resources 失败) "JSESSIONID", } def _is_cookie_required(name: str) -> bool: """判断 cookie 是否为 pickup 流程所必需。""" return name in REQUIRED_COOKIE_NAMES def get_session_path() -> str: if getattr(sys, "frozen", False): base = os.path.dirname(sys.executable) else: base = os.path.dirname(os.path.abspath(__file__)) return os.path.join(base, "session.json") def parse_json_and_save(json_text: str) -> int: """解析浏览器导出的 cookies JSON 数组,过滤并保存必需 cookie。 只保存 _REQUIRED_COOKIE_NAMES 白名单中的 cookie(基于 HAR 分析)。 返回保存的 cookie 数量。 """ raw = json.loads(json_text) if not isinstance(raw, list): raise ValueError("JSON 必须是数组格式") # 过滤目标域名 + 必需 cookie 名称 target = "10.4.188.1" cookies = [] seen = set() skipped = [] for c in raw: domain = c.get("domain", "") if target not in domain: continue name = c.get("name", "").strip() path = c.get("path", "/") if not name: continue if not _is_cookie_required(name): skipped.append(name) continue key = (name, domain, path) if key in seen: continue seen.add(key) cookies.append({ "name": name, "value": c.get("value", ""), "domain": domain, "path": path, }) # 检查必需 cookie 是否齐全(全部 10 个缺一不可) found_names = {c["name"] for c in cookies} missing = REQUIRED_COOKIE_NAMES - found_names if missing: raise ValueError( f"缺少必需 cookie ({len(missing)} 个): {', '.join(sorted(missing))}" ) data = { "cookies": cookies, "saved_from": f"https://{target}", "filtered_count": len(skipped), } session_path = get_session_path() os.makedirs(os.path.dirname(session_path) or ".", exist_ok=True) with open(session_path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) return len(cookies) def load_session_info() -> dict | None: """读取已保存的 session 信息用于 UI 展示。""" try: with open(get_session_path(), "r") as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return None