change to url

This commit is contained in:
Your Name
2026-03-13 02:53:16 +00:00
parent 80404217aa
commit b04797fc2e
9 changed files with 139 additions and 27 deletions
+6
View File
@@ -0,0 +1,6 @@
/usr/local/miniconda3/envs/py312/lib/python3.12/site-packages/requests/__init__.py:113: RequestsDependencyWarning: urllib3 (2.6.3) or chardet (7.0.1)/charset_normalizer (3.4.5) doesn't match a supported version!
warnings.warn(
INFO: Started server process [9669]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:12222 (Press CTRL+C to quit)
+56 -8
View File
@@ -10,6 +10,8 @@ import asyncio
import base64 import base64
import io import io
import json import json
import os
import tempfile
import time import time
import uuid import uuid
from pathlib import Path from pathlib import Path
@@ -21,6 +23,8 @@ from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel from pydantic import BaseModel
import oss2
COMFYUI_URL = "http://127.0.0.1:8188" COMFYUI_URL = "http://127.0.0.1:8188"
# COMFYUI_URL = "http://117.50.80.187:47697" # COMFYUI_URL = "http://117.50.80.187:47697"
WORKFLOW_PATH = Path(__file__).parent / "change2_1_ex.json" WORKFLOW_PATH = Path(__file__).parent / "change2_1_ex.json"
@@ -40,6 +44,37 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
def upload_to_oss(image_path, object_name=None):
# 配置信息(替换为你的实际信息)
access_key_id = 'LTAI5tGp1sLzedqxihcNC1eb'
access_key_secret = 'IFZE1b8YYreCP6zfA6GaZ9uBT678qO'
endpoint = 'oss-cn-beijing.aliyuncs.com' # 替换为你的Endpoint
bucket_name = 'xiangsilian'
# 创建Bucket实例
auth = oss2.Auth(access_key_id, access_key_secret)
bucket = oss2.Bucket(auth, endpoint, bucket_name)
# 如果没有指定OSS文件名,则使用本地文件名
if object_name is None:
object_name = image_path.split('/')[-1] # 取本地文件名
try:
# 上传文件
bucket.put_object_from_file(object_name, image_path)
# 获取文件URL(有效期默认10年)
url = bucket.sign_url('GET', object_name, 3600 * 24 * 365 * 10)
# 或者使用公共读Bucket的URL(如果Bucket是公共读权限)
# url = f"https://{bucket_name}.{endpoint}/{object_name}"
print(f"文件上传成功,URL: {url}")
return url
except Exception as e:
print(f"上传失败: {str(e)}")
return None
class TryOnRequest(BaseModel): class TryOnRequest(BaseModel):
model_image: str # base64 模特图片 model_image: str # base64 模特图片
@@ -47,7 +82,7 @@ class TryOnRequest(BaseModel):
class TryOnResponse(BaseModel): class TryOnResponse(BaseModel):
result_image: str # base64 结果图片 result_url: str # OSS 图片 URL
def decode_base64_image(b64_str: str) -> bytes: def decode_base64_image(b64_str: str) -> bytes:
@@ -96,14 +131,12 @@ async def wait_for_result(client: httpx.AsyncClient, prompt_id: str, timeout: in
raise HTTPException(status_code=504, detail="等待 ComfyUI 超时(300秒)") raise HTTPException(status_code=504, detail="等待 ComfyUI 超时(300秒)")
async def fetch_image_as_base64(client: httpx.AsyncClient, filename: str, subfolder: str = "", type_: str = "output") -> str: async def fetch_image_bytes(client: httpx.AsyncClient, filename: str, subfolder: str = "", type_: str = "output") -> bytes:
"""从 ComfyUI 下载图片并编码为 base64""" """从 ComfyUI 下载图片,返回原始字节"""
params = {"filename": filename, "subfolder": subfolder, "type": type_} params = {"filename": filename, "subfolder": subfolder, "type": type_}
resp = await client.get(f"{COMFYUI_URL}/view", params=params) resp = await client.get(f"{COMFYUI_URL}/view", params=params)
resp.raise_for_status() resp.raise_for_status()
content_type = resp.headers.get("content-type", "image/png") return resp.content
b64 = base64.b64encode(resp.content).decode()
return f"data:{content_type};base64,{b64}"
@app.post("/try-on", response_model=TryOnResponse) @app.post("/try-on", response_model=TryOnResponse)
@@ -173,9 +206,24 @@ async def try_on(req: TryOnRequest):
# 下载结果图片 # 下载结果图片
async with httpx.AsyncClient(timeout=30.0) as dl_client: async with httpx.AsyncClient(timeout=30.0) as dl_client:
result_b64 = await fetch_image_as_base64(dl_client, filename, subfolder, type_) result_bytes = await fetch_image_bytes(dl_client, filename, subfolder, type_)
return TryOnResponse(result_image=result_b64) # 将图片保存到临时文件,上传到 OSS
suffix = Path(filename).suffix or ".png"
object_name = f"tryon/{uuid.uuid4().hex}{suffix}"
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
tmp.write(result_bytes)
tmp_path = tmp.name
try:
result_url = upload_to_oss(tmp_path, object_name)
finally:
os.unlink(tmp_path)
if not result_url:
raise HTTPException(status_code=500, detail="上传结果图片到 OSS 失败")
return TryOnResponse(result_url=result_url)
@app.get("/health") @app.get("/health")
+3 -2
View File
@@ -309,15 +309,16 @@
} }
const data = await resp.json(); const data = await resp.json();
resultImg.src = data.result_image; resultImg.src = data.result_url;
resultSection.classList.add('visible'); resultSection.classList.add('visible');
setStatus(`✅ 换装完成!耗时 ${elapsed(start)}`, 'success'); setStatus(`✅ 换装完成!耗时 ${elapsed(start)}`, 'success');
// 下载按钮 // 下载按钮
document.getElementById('btn-download').onclick = () => { document.getElementById('btn-download').onclick = () => {
const a = document.createElement('a'); const a = document.createElement('a');
a.href = data.result_image; a.href = data.result_url;
a.download = `tryon_${Date.now()}.png`; a.download = `tryon_${Date.now()}.png`;
a.target = '_blank';
a.click(); a.click();
}; };
} catch (err) { } catch (err) {
+10
View File
@@ -0,0 +1,10 @@
/usr/local/miniconda3/envs/py312/lib/python3.12/site-packages/requests/__init__.py:113: RequestsDependencyWarning: urllib3 (2.6.3) or chardet (7.0.1)/charset_normalizer (3.4.5) doesn't match a supported version!
warnings.warn(
INFO: Started server process [9612]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:12223 (Press CTRL+C to quit)
静态文件目录: /root/change_cloth_server/change3/static
接收到换装请求,正在处理...
文件上传成功,URL: http://xiangsilian.oss-cn-beijing.aliyuncs.com/tryon%2F627bcee434384a7b847faf2dbbce90ae.png?OSSAccessKeyId=LTAI5tGp1sLzedqxihcNC1eb&Expires=2088730306&Signature=aATPXbjJCE3ArJnYfRyvSVSI8EE%3D
INFO: 127.0.0.1:39178 - "POST /try-on HTTP/1.1" 200 OK
+47 -8
View File
@@ -10,11 +10,14 @@ import asyncio
import base64 import base64
import io import io
import json import json
import os
import tempfile
import time import time
import uuid import uuid
from pathlib import Path from pathlib import Path
import httpx import httpx
import oss2
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
@@ -41,6 +44,28 @@ app.add_middleware(
) )
def upload_to_oss(image_path, object_name=None):
access_key_id = 'LTAI5tGp1sLzedqxihcNC1eb'
access_key_secret = 'IFZE1b8YYreCP6zfA6GaZ9uBT678qO'
endpoint = 'oss-cn-beijing.aliyuncs.com'
bucket_name = 'xiangsilian'
auth = oss2.Auth(access_key_id, access_key_secret)
bucket = oss2.Bucket(auth, endpoint, bucket_name)
if object_name is None:
object_name = image_path.split('/')[-1]
try:
bucket.put_object_from_file(object_name, image_path)
url = bucket.sign_url('GET', object_name, 3600 * 24 * 365 * 10)
print(f"文件上传成功,URL: {url}")
return url
except Exception as e:
print(f"上传失败: {str(e)}")
return None
class TryOnRequest(BaseModel): class TryOnRequest(BaseModel):
model_image: str # base64 模特图片 model_image: str # base64 模特图片
shirt_image: str # base64 上衣图片 shirt_image: str # base64 上衣图片
@@ -48,7 +73,7 @@ class TryOnRequest(BaseModel):
class TryOnResponse(BaseModel): class TryOnResponse(BaseModel):
result_image: str # base64 结果图片 result_url: str # OSS 图片 URL
def decode_base64_image(b64_str: str) -> bytes: def decode_base64_image(b64_str: str) -> bytes:
@@ -97,14 +122,12 @@ async def wait_for_result(client: httpx.AsyncClient, prompt_id: str, timeout: in
raise HTTPException(status_code=504, detail="等待 ComfyUI 超时(300秒)") raise HTTPException(status_code=504, detail="等待 ComfyUI 超时(300秒)")
async def fetch_image_as_base64(client: httpx.AsyncClient, filename: str, subfolder: str = "", type_: str = "output") -> str: async def fetch_image_bytes(client: httpx.AsyncClient, filename: str, subfolder: str = "", type_: str = "output") -> bytes:
"""从 ComfyUI 下载图片并编码为 base64""" """从 ComfyUI 下载图片,返回原始字节"""
params = {"filename": filename, "subfolder": subfolder, "type": type_} params = {"filename": filename, "subfolder": subfolder, "type": type_}
resp = await client.get(f"{COMFYUI_URL}/view", params=params) resp = await client.get(f"{COMFYUI_URL}/view", params=params)
resp.raise_for_status() resp.raise_for_status()
content_type = resp.headers.get("content-type", "image/png") return resp.content
b64 = base64.b64encode(resp.content).decode()
return f"data:{content_type};base64,{b64}"
@app.post("/try-on", response_model=TryOnResponse) @app.post("/try-on", response_model=TryOnResponse)
@@ -116,6 +139,7 @@ async def try_on(req: TryOnRequest):
- pants_image: 裤子图片 base64 - pants_image: 裤子图片 base64
返回 result_image: 换装结果 base64 返回 result_image: 换装结果 base64
""" """
print(f"接收到换装请求,正在处理...")
# 加载工作流模板 # 加载工作流模板
workflow = json.loads(WORKFLOW_PATH.read_text(encoding="utf-8")) workflow = json.loads(WORKFLOW_PATH.read_text(encoding="utf-8"))
@@ -175,9 +199,24 @@ async def try_on(req: TryOnRequest):
# 下载结果图片 # 下载结果图片
async with httpx.AsyncClient(timeout=30.0) as dl_client: async with httpx.AsyncClient(timeout=30.0) as dl_client:
result_b64 = await fetch_image_as_base64(dl_client, filename, subfolder, type_) result_bytes = await fetch_image_bytes(dl_client, filename, subfolder, type_)
return TryOnResponse(result_image=result_b64) # 将图片保存到临时文件,上传到 OSS
suffix = Path(filename).suffix or ".png"
object_name = f"tryon/{uuid.uuid4().hex}{suffix}"
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
tmp.write(result_bytes)
tmp_path = tmp.name
try:
result_url = upload_to_oss(tmp_path, object_name)
finally:
os.unlink(tmp_path)
if not result_url:
raise HTTPException(status_code=500, detail="上传结果图片到 OSS 失败")
return TryOnResponse(result_url=result_url)
@app.get("/health") @app.get("/health")
+3 -2
View File
@@ -320,15 +320,16 @@
} }
const data = await resp.json(); const data = await resp.json();
resultImg.src = data.result_image; resultImg.src = data.result_url;
resultSection.classList.add('visible'); resultSection.classList.add('visible');
setStatus(`✅ 换装完成!耗时 ${elapsed(start)}`, 'success'); setStatus(`✅ 换装完成!耗时 ${elapsed(start)}`, 'success');
// 下载按钮 // 下载按钮
document.getElementById('btn-download').onclick = () => { document.getElementById('btn-download').onclick = () => {
const a = document.createElement('a'); const a = document.createElement('a');
a.href = data.result_image; a.href = data.result_url;
a.download = `tryon_${Date.now()}.png`; a.download = `tryon_${Date.now()}.png`;
a.target = '_blank';
a.click(); a.click();
}; };
} catch (err) { } catch (err) {
+1 -1
View File
@@ -47,7 +47,7 @@ def change_cloth_base64():
response.raise_for_status() response.raise_for_status()
result = response.json() result = response.json()
result_image = result.get('result_image') result_image = result.get('result_url')
if not result_image: if not result_image:
return jsonify({"ret": -1, "state": -1, "msg": "Backend returned no image"}), 500 return jsonify({"ret": -1, "state": -1, "msg": "Backend returned no image"}), 500
+10
View File
@@ -0,0 +1,10 @@
/usr/local/miniconda3/envs/py312/lib/python3.12/site-packages/requests/__init__.py:113: RequestsDependencyWarning: urllib3 (2.6.3) or chardet (7.0.1)/charset_normalizer (3.4.5) doesn't match a supported version!
warnings.warn(
* Serving Flask app 'change_app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:28888
* Running on http://10.60.190.115:28888
Press CTRL+C to quit
1.202.76.38 - - [13/Mar/2026 02:51:46] "POST /change_cloth_base64 HTTP/1.1" 200 -
+3 -6
View File
@@ -342,12 +342,8 @@
clearInterval(timer); clearInterval(timer);
const data = await resp.json(); const data = await resp.json();
// 显示原始响应data 字段截断) // 显示原始响应
const preview = { ...data }; rawText.textContent = JSON.stringify(data, null, 2);
if (preview.data && preview.data.length > 80) {
preview.data = preview.data.slice(0, 80) + '…(已截断)';
}
rawText.textContent = JSON.stringify(preview, null, 2);
rawResp.classList.add('visible'); rawResp.classList.add('visible');
if (data.ret !== 0 || !data.data) { if (data.ret !== 0 || !data.data) {
@@ -362,6 +358,7 @@
const a = document.createElement('a'); const a = document.createElement('a');
a.href = data.data; a.href = data.data;
a.download = `tryon_${Date.now()}.png`; a.download = `tryon_${Date.now()}.png`;
a.target = '_blank';
a.click(); a.click();
}; };