feat: 优化发布清单和安装结构自动识别
This commit is contained in:
@@ -57,7 +57,6 @@ def default_client_main_executable() -> str:
|
||||
|
||||
def default_client_config_values(request: Request) -> dict:
|
||||
return {
|
||||
"api_base_url": public_api_base_url(request),
|
||||
"client_token": VALID_CLIENT_TOKEN or "",
|
||||
"launch_token": os.getenv("CLIENT_LAUNCH_TOKEN", "SimCAE_Launch_Token_2026_ChangeMe_32Bytes"),
|
||||
"request_timeout_ms": os.getenv("CLIENT_REQUEST_TIMEOUT_MS", "5000"),
|
||||
|
||||
@@ -37,6 +37,41 @@ PUBLISH_MAX_FILES = int(os.getenv("PUBLISH_MAX_FILES", "20000"))
|
||||
PUBLISH_MAX_FIELDS = int(os.getenv("PUBLISH_MAX_FIELDS", str(PUBLISH_MAX_FILES + 100)))
|
||||
|
||||
|
||||
def normalize_install_root(raw_value: str) -> str:
|
||||
value = str(raw_value or os.getenv("CLIENT_INSTALL_ROOT", "..")).strip().replace(chr(92), "/")
|
||||
if value in ("", "."):
|
||||
return "."
|
||||
if value == "..":
|
||||
return ".."
|
||||
raise HTTPException(status_code=400, detail="install_root 目前仅支持 . 或 ..")
|
||||
|
||||
|
||||
def normalize_publish_executable(raw_value: str) -> str:
|
||||
value = str(raw_value or "").strip().replace(chr(92), "/").strip("/")
|
||||
if not value:
|
||||
value = RELEASE_MAIN_EXECUTABLE.rsplit("/", 1)[-1]
|
||||
if TARGET_PLATFORM == "linux" and value.casefold().endswith(".exe"):
|
||||
value = value[:-4]
|
||||
elif TARGET_PLATFORM == "windows":
|
||||
parts = value.split("/")
|
||||
leaf = parts[-1]
|
||||
if "." not in leaf:
|
||||
leaf += ".exe"
|
||||
parts[-1] = leaf
|
||||
value = "/".join(parts)
|
||||
return normalize_relative_path(value)
|
||||
|
||||
|
||||
def release_main_executable_for_install(install_root: str, main_executable: str) -> str:
|
||||
normalized_install_root = normalize_install_root(install_root)
|
||||
normalized_main = normalize_publish_executable(main_executable)
|
||||
if normalized_install_root == ".":
|
||||
return normalized_main
|
||||
if normalized_main.casefold().startswith("bin/"):
|
||||
return normalized_main
|
||||
return normalize_relative_path(f"bin/{normalized_main}")
|
||||
|
||||
|
||||
def normalize_relative_path(raw_path: str) -> str:
|
||||
if not isinstance(raw_path, str):
|
||||
raise HTTPException(status_code=400, detail="文件相对路径无效")
|
||||
@@ -256,7 +291,7 @@ def extract_release_archive(archive_path: Path, filename: str, extract_dir: Path
|
||||
raise HTTPException(status_code=400, detail="压缩发布包仅支持 zip、tar.gz、tgz、tar.bz2、tbz2、rar")
|
||||
|
||||
|
||||
def release_items_from_extracted_dir(extract_dir: Path):
|
||||
def release_items_from_extracted_dir(extract_dir: Path, required_main_executable: str):
|
||||
items = []
|
||||
for path in extract_dir.rglob("*"):
|
||||
if path.is_symlink() or not path.is_file():
|
||||
@@ -265,13 +300,13 @@ def release_items_from_extracted_dir(extract_dir: Path):
|
||||
if should_skip_release_path(rel_path):
|
||||
continue
|
||||
items.append({"path": rel_path, "local_path": path})
|
||||
return normalize_release_item_roots(items)
|
||||
return normalize_release_item_roots(items, required_main_executable)
|
||||
|
||||
|
||||
def normalize_release_item_roots(items: list[dict]):
|
||||
def normalize_release_item_roots(items: list[dict], required_main_executable: str):
|
||||
if not items:
|
||||
return items
|
||||
required_main_key = RELEASE_MAIN_EXECUTABLE.casefold()
|
||||
required_main_key = required_main_executable.casefold()
|
||||
lower_paths = [item["path"].casefold() for item in items]
|
||||
if required_main_key in lower_paths:
|
||||
return items
|
||||
@@ -298,7 +333,7 @@ def normalize_release_item_roots(items: list[dict]):
|
||||
return normalized
|
||||
|
||||
|
||||
def validate_release_items(items: list[dict]):
|
||||
def validate_release_items(items: list[dict], required_main_executable: str):
|
||||
if not items:
|
||||
raise HTTPException(status_code=400, detail="未选择任何文件,请选择程序文件夹或压缩发布包")
|
||||
if len(items) > PUBLISH_MAX_FILES:
|
||||
@@ -322,15 +357,15 @@ def validate_release_items(items: list[dict]):
|
||||
if not filtered:
|
||||
raise HTTPException(status_code=400, detail="发布内容为空;请确认发布目录或压缩包中包含程序文件")
|
||||
|
||||
required_main_key = RELEASE_MAIN_EXECUTABLE.casefold()
|
||||
required_main_key = required_main_executable.casefold()
|
||||
if required_main_key not in seen_paths:
|
||||
nested_main = next((path for path in seen_paths if path.endswith("/" + required_main_key)), None)
|
||||
if nested_main:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"{RELEASE_MAIN_EXECUTABLE} 位于更深层目录,请选择包含该相对路径的正确发布根目录。当前发现: {nested_main}",
|
||||
detail=f"{required_main_executable} 位于更深层目录,请选择包含该相对路径的正确发布根目录。当前发现: {nested_main}",
|
||||
)
|
||||
raise HTTPException(status_code=400, detail=f"发布目录中缺少 {RELEASE_MAIN_EXECUTABLE}")
|
||||
raise HTTPException(status_code=400, detail=f"发布目录中缺少 {required_main_executable}")
|
||||
nested_main = next((path for path in seen_paths if path.endswith("/" + required_main_key)), None)
|
||||
if nested_main:
|
||||
raise HTTPException(
|
||||
@@ -461,6 +496,9 @@ async def collect_publish_items(request: Request):
|
||||
app_id = form.get("app_id")
|
||||
channel = form.get("channel") or ""
|
||||
version = form.get("version")
|
||||
install_root = normalize_install_root(str(form.get("install_root") or ""))
|
||||
main_executable = str(form.get("main_executable") or "")
|
||||
required_main_executable = release_main_executable_for_install(install_root, main_executable)
|
||||
try:
|
||||
client_protocol = int(form.get("client_protocol") or 2)
|
||||
except (TypeError, ValueError):
|
||||
@@ -526,8 +564,8 @@ async def collect_publish_items(request: Request):
|
||||
extract_dir = archive_temp_dir / "extracted"
|
||||
extract_dir.mkdir(parents=True, exist_ok=True)
|
||||
extract_release_archive(archive_path, archive_name, extract_dir)
|
||||
raw_items = release_items_from_extracted_dir(extract_dir)
|
||||
upload_items = validate_release_items(raw_items)
|
||||
raw_items = release_items_from_extracted_dir(extract_dir, required_main_executable)
|
||||
upload_items = validate_release_items(raw_items, required_main_executable)
|
||||
extracted_total = sum(item["local_path"].stat().st_size for item in upload_items)
|
||||
check_extracted_publish_limits(extracted_total, len(upload_items))
|
||||
else:
|
||||
@@ -540,7 +578,7 @@ async def collect_publish_items(request: Request):
|
||||
for index, file in enumerate(files):
|
||||
raw_path = relative_paths[index] if relative_paths else file.filename
|
||||
raw_items.append({"upload": file, "path": str(raw_path)})
|
||||
upload_items = validate_release_items(raw_items)
|
||||
upload_items = validate_release_items(raw_items, required_main_executable)
|
||||
except Exception:
|
||||
if archive_upload is not None:
|
||||
await close_upload_safely(archive_upload)
|
||||
@@ -552,6 +590,8 @@ async def collect_publish_items(request: Request):
|
||||
"channel": str(channel),
|
||||
"version": str(version),
|
||||
"client_protocol": client_protocol,
|
||||
"install_root": install_root,
|
||||
"required_main_executable": required_main_executable,
|
||||
"upload_items": upload_items,
|
||||
"archive_upload": archive_upload,
|
||||
"archive_temp_dir": archive_temp_dir,
|
||||
|
||||
Reference in New Issue
Block a user