144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
"""MinIO 附件 URL 规范化(数据迁移共用)。
|
||
|
||
背景
|
||
----
|
||
CRM / 地磅 / 报销与 shanxia2.0 共用同一 MinIO(bucket 默认 ``ruoyi``),
|
||
物理对象无需复制。旧 CRM ``sys_oss.url`` 存的是带域名的绝对 URL(含历史
|
||
``47.99.164.66:20004``、``121.43.228.102:9000`` 等),门户 ``sys_oss.url`` 与
|
||
业务表字段存 **相对 object key**(如 ``2025/10/08/xxx.png``)。
|
||
|
||
旧表 ``sys_oss.file_name`` 通常已是 object key,与 ``url`` 中的路径一致,
|
||
解析失败时可优先回退 ``file_name``。
|
||
|
||
报销 ``bx_attachment.file_path`` 本身即为相对路径(如 ``bx/2026-05-19/...``),
|
||
可直接写入目标库。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from typing import Iterable, Optional
|
||
from urllib.parse import unquote, urlparse
|
||
|
||
|
||
DEFAULT_BUCKET = "ruoyi"
|
||
|
||
# 历史绝对 URL 前缀(域名/网关变更);匹配时去掉前缀得到 object key
|
||
LEGACY_URL_PREFIXES = (
|
||
"http://47.99.164.66:20004/ruoyi/",
|
||
"https://47.99.164.66:20004/ruoyi/",
|
||
"http://121.43.228.102:9000/ruoyi/",
|
||
"https://121.43.228.102:9000/ruoyi/",
|
||
"http://www.chengyantek.cn:20004/ruoyi/",
|
||
"https://www.chengyantek.cn:20004/ruoyi/",
|
||
"http://www.chengyantek.cn/ruoyi/",
|
||
"https://www.chengyantek.cn/ruoyi/",
|
||
"http://www.chengyantek.cn/zkxw/",
|
||
"https://www.chengyantek.cn/zkxw/",
|
||
)
|
||
|
||
|
||
def strip_url_query(raw: str) -> str:
|
||
"""去掉预签名 query(``?X-Amz-...``)。"""
|
||
value = (raw or "").strip()
|
||
if not value:
|
||
return ""
|
||
return value.split("?", 1)[0].strip()
|
||
|
||
|
||
def _strip_legacy_prefix(url: str) -> Optional[str]:
|
||
normalized = strip_url_query(url)
|
||
for prefix in LEGACY_URL_PREFIXES:
|
||
if normalized.startswith(prefix):
|
||
return normalized[len(prefix) :].lstrip("/")
|
||
return None
|
||
|
||
|
||
def extract_minio_object_key(
|
||
raw_url: Optional[str],
|
||
*,
|
||
bucket: str = DEFAULT_BUCKET,
|
||
file_name_fallback: Optional[str] = None,
|
||
) -> str:
|
||
"""将绝对/相对 URL 规范为 MinIO object key(不含 bucket)。"""
|
||
url = strip_url_query(raw_url or "")
|
||
if not url:
|
||
fallback = (file_name_fallback or "").strip().lstrip("/")
|
||
return fallback
|
||
|
||
legacy = _strip_legacy_prefix(url)
|
||
if legacy:
|
||
return legacy
|
||
|
||
if url.startswith("http://") or url.startswith("https://"):
|
||
parsed = urlparse(url)
|
||
path = unquote((parsed.path or "").lstrip("/"))
|
||
bucket_prefix = f"{bucket}/"
|
||
if path.startswith(bucket_prefix):
|
||
return path[len(bucket_prefix) :]
|
||
# 少数 URL 路径不含 bucket 段
|
||
return path
|
||
|
||
return url.lstrip("/")
|
||
|
||
|
||
def normalize_oss_storage_url(
|
||
url: Optional[str],
|
||
file_name: Optional[str] = None,
|
||
*,
|
||
bucket: str = DEFAULT_BUCKET,
|
||
) -> str:
|
||
"""迁移 CRM ``sys_oss`` 行:``url`` + ``file_name`` → 门户相对路径。"""
|
||
from_url = extract_minio_object_key(url, bucket=bucket)
|
||
if from_url:
|
||
return from_url
|
||
return (file_name or "").strip().lstrip("/")
|
||
|
||
|
||
def join_relative_urls(urls: Iterable[str]) -> str:
|
||
"""逗号拼接相对路径(去空、去重保序)。"""
|
||
seen = set()
|
||
parts: list[str] = []
|
||
for item in urls:
|
||
key = (item or "").strip().lstrip("/")
|
||
if not key or key in seen:
|
||
continue
|
||
seen.add(key)
|
||
parts.append(key)
|
||
return ",".join(parts)
|
||
|
||
|
||
def resolve_oss_id_csv(
|
||
oss_id_csv: Optional[str],
|
||
oss_id_to_relative_url: dict[str, str],
|
||
) -> str:
|
||
"""业务表 OSS ID 串 → 相对 URL 串(用于客户证照、跟进附件等)。"""
|
||
if not oss_id_csv:
|
||
return ""
|
||
urls: list[str] = []
|
||
for token in str(oss_id_csv).split(","):
|
||
oss_id = token.strip()
|
||
if not oss_id:
|
||
continue
|
||
mapped = oss_id_to_relative_url.get(oss_id)
|
||
if mapped:
|
||
urls.append(mapped)
|
||
return join_relative_urls(urls)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
samples = [
|
||
(
|
||
"http://47.99.164.66:20004/ruoyi/2025/10/08/5ac0966ab3a74eadab571ce0bb3b441a.png",
|
||
"2025/10/08/5ac0966ab3a74eadab571ce0bb3b441a.png",
|
||
),
|
||
(
|
||
"http://121.43.228.102:9000/ruoyi/2025/11/01/0048348b3d6c439f99db454c8c05e59a.gif?X-Amz-Algorithm=AWS4",
|
||
"2025/11/01/0048348b3d6c439f99db454c8c05e59a.gif",
|
||
),
|
||
("2025/10/17/d601338282a44cf092bf68afe1aef239.jpeg", "2025/10/17/d601338282a44cf092bf68afe1aef239.jpeg"),
|
||
("bx/2026-05-19/4ca4ffbeb7fc4e22913936d0860b2e2d.png", "bx/2026-05-19/4ca4ffbeb7fc4e22913936d0860b2e2d.png"),
|
||
]
|
||
for raw, expected in samples:
|
||
got = extract_minio_object_key(raw)
|
||
assert got == expected, f"{raw!r} -> {got!r}, want {expected!r}"
|
||
print("minio_url self-check ok")
|