Spaces:
Running
Running
| # ⚙️ 后端逻辑/核心服务端.py (Hugging Face Spaces app.py) | |
| from fastapi import FastAPI, File, UploadFile, Form, Depends, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import Response, JSONResponse, FileResponse | |
| from sqlalchemy.orm import Session | |
| from pydantic import BaseModel | |
| from huggingface_hub import hf_hub_download, HfApi | |
| import hashlib | |
| import urllib.parse | |
| import urllib.request | |
| import urllib.error | |
| import os | |
| import mimetypes | |
| import 数据库连接 as db | |
| from router_users import router as users_router | |
| from router_items import router as items_router | |
| from router_comments import router as comments_router | |
| from router_messages import router as messages_router | |
| from router_wallet import router as wallet_router | |
| from router_proxy import router as proxy_router | |
| from database_sql import init_sql_db, get_db | |
| from models_sql import Ownership | |
| app = FastAPI(title="ComfyUI Ranking Community API") | |
| def health_check(): | |
| return {"status": "ok", "message": "ComfyUI Ranking API is running perfectly!"} | |
| def on_startup(): | |
| init_sql_db() | |
| print("关系型数据库加载完毕,金融表同步完成。") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=False, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| app.include_router(users_router) | |
| app.include_router(items_router) | |
| app.include_router(comments_router) | |
| app.include_router(messages_router) | |
| app.include_router(wallet_router) | |
| app.include_router(proxy_router) | |
| # ========================================== | |
| # 🟢 私有图床代理中心 (Image Proxy) | |
| # 解决 Private 仓库下,本地客户端报 401 Unauthorized 的终极方案 | |
| # ========================================== | |
| def proxy_hf_image(url: str = None, path: str = None): | |
| """云端图片代理:使用云端的 HF_TOKEN 提取私有图床图片并返回给没有任何权限的本地端""" | |
| # 兼容处理:如果本地发来的是已经被污染的老版本 HF 直链,我们自动将其转换为相对路径 | |
| if url and url.startswith("https://huggingface.co/datasets/"): | |
| try: | |
| path = url.split("resolve/main/")[-1] | |
| path = urllib.parse.unquote(path) | |
| except: | |
| raise HTTPException(status_code=400, detail="无效的 HF 原链接格式") | |
| if not path: | |
| raise HTTPException(status_code=400, detail="缺少路径参数") | |
| # 🛡️ 绝对的安全红线:限制只能代理下载图片目录,严禁黑客通过此接口下载 users.json 或账本数据! | |
| allowed_dirs = ["uploads/", "avatars/", "covers/"] | |
| if not any(path.startswith(d) for d in allowed_dirs): | |
| raise HTTPException(status_code=403, detail="非法访问:该接口仅允许代理图片资源") | |
| hf_token = os.environ.get("HF_TOKEN") | |
| dataset_repo_id = db.DATASET_REPO_ID | |
| try: | |
| # hf_hub_download 会自动利用云端容器的缓存,只有第一次会去真实请求 Dataset | |
| cached_file_path = hf_hub_download( | |
| repo_id=dataset_repo_id, | |
| repo_type="dataset", | |
| filename=path, | |
| token=hf_token | |
| ) | |
| # 智能识别文件类型 (image/jpeg, image/png 等) | |
| content_type, _ = mimetypes.guess_type(cached_file_path) | |
| return FileResponse(cached_file_path, media_type=content_type or "application/octet-stream") | |
| except Exception as e: | |
| return JSONResponse(content={"error": f"代理获取图片失败: {str(e)}"}, status_code=404) | |
| # ========================================== | |
| # 上传接口 (将返回的 URL 替换为 Proxy 代理链接) | |
| # ========================================== | |
| def upload_file(file: UploadFile = File(...), file_type: str = Form(...)): | |
| content = file.file.read() | |
| # 🟢 动态文件大小风控 | |
| max_size = 10 * 1024 * 1024 | |
| if file_type == "avatar": | |
| max_size = 2 * 1024 * 1024 | |
| elif file_type == "cover": | |
| max_size = 5 * 1024 * 1024 | |
| if len(content) > max_size: | |
| raise HTTPException(status_code=400, detail=f"文件过大,{file_type} 类型请限制在 {max_size // (1024*1024)}MB 以内") | |
| ext = file.filename.split(".")[-1].lower() | |
| if ext not in ["jpg", "jpeg", "png", "gif", "webp", "json", "mp4"]: | |
| raise HTTPException(status_code=400, detail="不支持的文件格式") | |
| file_hash = hashlib.md5(content).hexdigest()[:10] | |
| safe_filename = f"{file_type}_{file_hash}.{ext}" | |
| local_tmp_path = f"/tmp/{safe_filename}" | |
| with open(local_tmp_path, "wb") as f: | |
| f.write(content) | |
| hf_token = os.environ.get("HF_TOKEN") | |
| dataset_repo_id = "ZHIWEI666/ComfyUI-Ranking" | |
| try: | |
| api = HfApi() | |
| api.upload_file( | |
| path_or_fileobj=local_tmp_path, | |
| path_in_repo=f"uploads/{file_type}/{safe_filename}", | |
| repo_id=dataset_repo_id, | |
| repo_type="dataset", | |
| token=hf_token, | |
| commit_message=f"Upload media: {safe_filename}" | |
| ) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"图床同步失败: {str(e)}") | |
| finally: | |
| if os.path.exists(local_tmp_path): | |
| os.remove(local_tmp_path) | |
| # 🚀 核心修复:不再返回暴露隐私且报 401 的 HF 直链,而是返回我们刚写好的 Proxy 代理链接 | |
| permanent_url = f"https://zhiwei666-comfyui-ranking-api.hf.space/api/image_proxy?path=uploads/{file_type}/{safe_filename}" | |
| return {"status": "success", "url": permanent_url} | |
| class ValidateResourceRequest(BaseModel): | |
| url: str | |
| item_id: str | |
| account: str | |
| def validate_resource(req_data: ValidateResourceRequest, sql_db: Session = Depends(get_db)): | |
| target_url = req_data.url | |
| if not target_url.startswith("https://huggingface.co/datasets/") and not target_url.startswith("https://github.com/"): | |
| return JSONResponse(content={"error": "无效的下载链接"}, status_code=400) | |
| items_db = db.load_data("items.json", default_data=[]) | |
| item = next((i for i in items_db if i["id"] == req_data.item_id), None) | |
| if not item: return JSONResponse(content={"error": "资源不存在或已被删除"}, status_code=404) | |
| price = int(item.get("price", 0)) | |
| author = item.get("author") | |
| if price > 0 and req_data.account != author: | |
| owned = sql_db.query(Ownership).filter(Ownership.account == req_data.account, Ownership.item_id == req_data.item_id).first() | |
| if not owned: | |
| return JSONResponse(content={"error": "🚨 非法下载:云端数据库未找到您的购买凭证!"}, status_code=403) | |
| hf_token = os.environ.get("HF_TOKEN") | |
| if not hf_token: return JSONResponse(content={"error": "云端环境变量未配置 HF_TOKEN"}, status_code=401) | |
| try: | |
| if target_url.startswith("https://github.com/"): | |
| creator_token = item.get("github_token") | |
| fallback_token = os.environ.get("GITHUB_PAT") | |
| active_token = creator_token if creator_token else fallback_token | |
| headers = {"User-Agent": "ComfyUI-Ranking-SaaS"} | |
| if active_token: | |
| headers["Authorization"] = f"Bearer {active_token}" | |
| repo_parts = target_url.rstrip("/").split("/") | |
| if len(repo_parts) < 2: return JSONResponse(content={"error": "无效的仓库地址格式"}, status_code=400) | |
| owner, repo = repo_parts[-2], repo_parts[-1] | |
| api_url = f"https://api.github.com/repos/{owner}/{repo}" | |
| req = urllib.request.Request(api_url, headers=headers) | |
| with urllib.request.urlopen(req) as response: | |
| if response.status != 200: | |
| return JSONResponse(content={"error": "资源仓库不可访问,可能已被作者删除或设为私有"}, status_code=404) | |
| return {"status": "success", "message": "资源有效"} | |
| elif target_url.startswith("https://huggingface.co/datasets/"): | |
| repo_path_encoded = target_url.split("resolve/main/")[-1] | |
| repo_path = urllib.parse.unquote(repo_path_encoded) | |
| cached_file_path = hf_hub_download( | |
| repo_id=db.DATASET_REPO_ID, | |
| repo_type="dataset", | |
| filename=repo_path, | |
| token=hf_token | |
| ) | |
| if not os.path.exists(cached_file_path): | |
| return JSONResponse(content={"error": "云端文件不存在,可能已被作者删除"}, status_code=404) | |
| return {"status": "success", "message": "资源有效"} | |
| except urllib.error.HTTPError as e: | |
| return JSONResponse(content={"error": f"资源探测失败,源站返回: {e.code}。请联系作者处理。"}, status_code=400) | |
| except Exception as e: | |
| return JSONResponse(content={"error": f"资源探测异常: {str(e)}"}, status_code=500) | |
| return {"status": "success"} |