Spaces:
Running
Running
Upload 2 files
Browse files- app.py +3 -0
- router_proxy.py +64 -0
app.py
CHANGED
|
@@ -16,6 +16,8 @@ from router_items import router as items_router
|
|
| 16 |
from router_comments import router as comments_router
|
| 17 |
from router_messages import router as messages_router
|
| 18 |
from router_wallet import router as wallet_router
|
|
|
|
|
|
|
| 19 |
from database_sql import init_sql_db, get_db
|
| 20 |
from models_sql import Ownership
|
| 21 |
|
|
@@ -39,6 +41,7 @@ app.include_router(items_router)
|
|
| 39 |
app.include_router(comments_router)
|
| 40 |
app.include_router(messages_router)
|
| 41 |
app.include_router(wallet_router)
|
|
|
|
| 42 |
|
| 43 |
@app.get("/")
|
| 44 |
def read_root():
|
|
|
|
| 16 |
from router_comments import router as comments_router
|
| 17 |
from router_messages import router as messages_router
|
| 18 |
from router_wallet import router as wallet_router
|
| 19 |
+
from router_proxy import router as proxy_router # 【新增】:导入新的代理路由
|
| 20 |
+
|
| 21 |
from database_sql import init_sql_db, get_db
|
| 22 |
from models_sql import Ownership
|
| 23 |
|
|
|
|
| 41 |
app.include_router(comments_router)
|
| 42 |
app.include_router(messages_router)
|
| 43 |
app.include_router(wallet_router)
|
| 44 |
+
app.include_router(proxy_router) # 【新增】:挂载私有库 ZIP 下载路由
|
| 45 |
|
| 46 |
@app.get("/")
|
| 47 |
def read_root():
|
router_proxy.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# router_proxy.py
|
| 2 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 3 |
+
from fastapi.responses import StreamingResponse, JSONResponse
|
| 4 |
+
from sqlalchemy.orm import Session
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
import httpx
|
| 7 |
+
import os
|
| 8 |
+
import 数据库连接 as json_db
|
| 9 |
+
from database_sql import get_db
|
| 10 |
+
from models_sql import Ownership
|
| 11 |
+
|
| 12 |
+
router = APIRouter()
|
| 13 |
+
|
| 14 |
+
class ProxyGithubZipRequest(BaseModel):
|
| 15 |
+
url: str
|
| 16 |
+
item_id: str
|
| 17 |
+
account: str
|
| 18 |
+
|
| 19 |
+
@router.post("/api/proxy_github_zip")
|
| 20 |
+
async def proxy_github_zip(req_data: ProxyGithubZipRequest, db: Session = Depends(get_db)):
|
| 21 |
+
"""云端代理:校验所有权后,拉取 GitHub 私有库 ZIP 流透传给本地"""
|
| 22 |
+
items_db = json_db.load_data("items.json", default_data=[])
|
| 23 |
+
item = next((i for i in items_db if i["id"] == req_data.item_id), None)
|
| 24 |
+
if not item: return JSONResponse(content={"error": "资源不存在"}, status_code=404)
|
| 25 |
+
|
| 26 |
+
# 1. 核心鉴权:验证用户是否购买过该工具
|
| 27 |
+
price = int(item.get("price", 0))
|
| 28 |
+
if price > 0 and req_data.account != item.get("author"):
|
| 29 |
+
owned = db.query(Ownership).filter(Ownership.account == req_data.account, Ownership.item_id == req_data.item_id).first()
|
| 30 |
+
if not owned:
|
| 31 |
+
return JSONResponse(content={"error": "🚨 拒绝访问:未找到购买记录!"}, status_code=403)
|
| 32 |
+
|
| 33 |
+
# 2. 解析 GitHub 仓库信息
|
| 34 |
+
repo_url = item.get("link", "").rstrip("/")
|
| 35 |
+
if not repo_url.startswith("https://github.com/"):
|
| 36 |
+
return JSONResponse(content={"error": "无效的仓库地址,目前仅支持 GitHub 私有库代理"}, status_code=400)
|
| 37 |
+
|
| 38 |
+
repo_parts = repo_url.split("/")
|
| 39 |
+
if len(repo_parts) < 2: return JSONResponse(content={"error": "无效的仓库地址格式"}, status_code=400)
|
| 40 |
+
owner, repo = repo_parts[-2], repo_parts[-1]
|
| 41 |
+
|
| 42 |
+
# GitHub 官方提供的打包下载 API
|
| 43 |
+
github_zip_api = f"https://api.github.com/repos/{owner}/{repo}/zipball/main"
|
| 44 |
+
|
| 45 |
+
# 获取云端私密环境变量中的 GitHub Token
|
| 46 |
+
github_token = os.environ.get("GITHUB_PAT")
|
| 47 |
+
headers = {
|
| 48 |
+
"Accept": "application/vnd.github.v3+json",
|
| 49 |
+
"User-Agent": "ComfyUI-Ranking-SaaS"
|
| 50 |
+
}
|
| 51 |
+
if github_token:
|
| 52 |
+
headers["Authorization"] = f"Bearer {github_token}"
|
| 53 |
+
|
| 54 |
+
# 3. 异步请求 GitHub API 并以流形式透传回客户端 (防内存打爆)
|
| 55 |
+
async def stream_generator():
|
| 56 |
+
async with httpx.AsyncClient(follow_redirects=True) as client:
|
| 57 |
+
async with client.stream("GET", github_zip_api, headers=headers) as response:
|
| 58 |
+
if response.status_code != 200:
|
| 59 |
+
yield b"GITHUB_DOWNLOAD_FAILED"
|
| 60 |
+
return
|
| 61 |
+
async for chunk in response.aiter_bytes():
|
| 62 |
+
yield chunk
|
| 63 |
+
|
| 64 |
+
return StreamingResponse(stream_generator(), media_type="application/zip")
|