feat: complete T-530 cmhub URL normalization

This commit is contained in:
chengma
2026-07-06 10:31:36 +08:00
parent 42dd8a993f
commit b99019e954
14 changed files with 142 additions and 31 deletions
+33 -2
View File
@@ -9,6 +9,7 @@ import copy
import json
import os
import urllib.error
import urllib.parse
import urllib.request
@@ -129,6 +130,17 @@ def _deep_merge(defaults, loaded):
return merged
def _normalize_config_values(config):
if not isinstance(config, dict):
return config
ai = config.get("ai")
if isinstance(ai, dict):
cmhub = ai.get("cmhub")
if isinstance(cmhub, dict):
cmhub["base_url"] = normalize_cmhub_base_url(cmhub.get("base_url", ""))
return config
def _assert_no_secrets(config):
def visit(value, path):
if isinstance(value, dict):
@@ -247,6 +259,7 @@ def save_config(config, path=CONFIG_PATH) -> dict:
"""Persist config to JSON and return the normalized config."""
normalized = _deep_merge(DEFAULT_CONFIG, config)
_normalize_config_values(normalized)
_assert_no_secrets(normalized)
directory = os.path.dirname(os.path.abspath(path))
if directory:
@@ -268,6 +281,7 @@ def load_config(path=CONFIG_PATH) -> dict:
except json.JSONDecodeError as exc:
raise ConfigError(f"配置文件不是有效 JSON: {path}") from exc
normalized = _deep_merge(DEFAULT_CONFIG, loaded)
_normalize_config_values(normalized)
_assert_no_secrets(normalized)
return normalized
@@ -334,7 +348,7 @@ def cmhub_config(config=None) -> dict:
if not isinstance(value, dict):
raise ConfigError("ai.cmhub 必须是对象")
merged = _deep_merge(DEFAULT_CONFIG["ai"]["cmhub"], value)
merged["base_url"] = str(merged.get("base_url", "") or "").strip()
merged["base_url"] = normalize_cmhub_base_url(merged.get("base_url", ""))
merged["title_alias"] = str(merged.get("title_alias", "") or "").strip()
merged["image_alias"] = str(merged.get("image_alias", "") or "").strip()
merged["connect_timeout"] = int(merged.get("connect_timeout", 10) or 10)
@@ -344,8 +358,25 @@ def cmhub_config(config=None) -> dict:
return merged
def normalize_cmhub_base_url(base_url) -> str:
"""Return the cmhub gateway root URL without path/query/fragment."""
text = str(base_url or "").strip()
if not text:
return ""
parts = urllib.parse.urlsplit(text)
if parts.scheme and parts.netloc:
return urllib.parse.urlunsplit((parts.scheme, parts.netloc, "", "", ""))
if parts.netloc:
return urllib.parse.urlunsplit((parts.scheme, parts.netloc, "", "", ""))
without_query = text.split("?", 1)[0].split("#", 1)[0].strip().strip("/")
if "://" not in without_query:
return without_query.split("/", 1)[0]
return text.rstrip("/")
def cmhub_request_url(base_url, endpoint) -> str:
base = str(base_url or "").strip().rstrip("/")
base = normalize_cmhub_base_url(base_url)
path = "/" + str(endpoint or "").strip().lstrip("/")
if not base:
return path