Paper2Any / fastapi_app /utils.py
Nichengjin
Clean commit without faiss cache
a23394a
Raw
History Blame Contribute Delete
4.36 kB
from __future__ import annotations
import os
from pathlib import Path
from typing import Set
from urllib.parse import urlparse
from fastapi import HTTPException, Request
from dataflow_agent.logger import get_logger
from dataflow_agent.utils import get_project_root
log = get_logger(__name__)
# 简单的邀请码校验:从本地文本文件加载白名单
INVITE_CODES_FILE = Path(os.getenv("INVITE_CODES_FILE", f"{get_project_root()}/invite_codes.txt"))
def _to_outputs_url(abs_path: str, request: Request | None = None) -> str:
"""
将绝对路径转换为浏览器可访问的完整 URL。
默认认为所有输出文件都位于项目根目录下的 outputs/ 目录中。
"""
project_root = get_project_root()
outputs_root = project_root / "outputs"
log.info(f"[DEBUG] project_root: {project_root}")
log.info(f"[DEBUG] outputs_root: {outputs_root}")
log.info(f"[DEBUG] abs_path: {abs_path}")
p = Path(abs_path)
try:
rel = p.relative_to(outputs_root)
# 构造完整 URL(包含协议、域名和端口)
if request is not None:
base_url = str(request.base_url).rstrip("/")
url = f"{base_url}/outputs/{rel.as_posix()}"
else:
# 降级:使用相对路径
url = f"/outputs/{rel.as_posix()}"
log.warning(f"[DEBUG] generated URL: {url}")
return url
except ValueError as e:
log.error(f"[ERROR] Path conversion failed: {e}")
if "/outputs/" in abs_path:
idx = abs_path.index("/outputs/")
fallback_url = abs_path[idx:]
log.warning(f"[WARN] Using fallback URL: {fallback_url}")
return fallback_url
log.error(f"[ERROR] Cannot convert path to URL: {abs_path}")
return abs_path
def _from_outputs_url(url_or_path: str) -> str:
"""
尝试将前端传来的 URL (包含 /outputs/) 转换回本地绝对路径。
如果不是 URL 或者转换失败,则返回原值。
"""
if not url_or_path or not isinstance(url_or_path, str):
return url_or_path
# 如果已经是绝对路径且存在,直接返回
if os.path.isabs(url_or_path) and os.path.exists(url_or_path):
return url_or_path
# 简单判断是否是 http URL
if not url_or_path.startswith("http") and not url_or_path.startswith("/outputs/"):
return url_or_path
# 查找 /outputs/ 的位置
if "/outputs/" not in url_or_path:
return url_or_path
try:
# 获取 /outputs/ 之后的部分
path_str = url_or_path
if url_or_path.startswith("http"):
parsed = urlparse(url_or_path)
path_str = parsed.path
if "/outputs/" in path_str:
idx = path_str.index("/outputs/")
# outputs/xxx/yyy
rel_path = path_str[idx + len("/outputs/") :]
# 去除可能的开头的 / (虽然 relative_to 不需要,但拼接待会儿用)
rel_path = rel_path.lstrip("/")
project_root = get_project_root()
outputs_root = project_root / "outputs"
abs_path = (outputs_root / rel_path).resolve()
log.info(f"[DEBUG] Converted URL {url_or_path} to path {abs_path}")
return str(abs_path)
except Exception as e:
log.warning(f"[WARN] Failed to convert URL to path: {e}")
return url_or_path
def load_invite_codes() -> Set[str]:
"""
从 invite_codes.txt 中加载邀请码列表。
文件格式:每行一个邀请码,忽略空行和以 # 开头的注释行。
"""
codes: Set[str] = set()
if not INVITE_CODES_FILE.exists():
return codes
for line in INVITE_CODES_FILE.read_text(encoding="utf-8").splitlines():
code = line.strip()
if not code or code.startswith("#"):
continue
codes.add(code)
return codes
VALID_INVITE_CODES = load_invite_codes()
def validate_invite_code(code: str | None) -> None:
"""
校验邀请码是否有效。无效则抛出 403。
"""
# 邀请码机制已取消,始终通过校验
pass
# if not code:
# raise HTTPException(status_code=403, detail="invite_code is required")
# if code not in VALID_INVITE_CODES:
# raise HTTPException(status_code=403, detail="Invalid invite_code")