first commit
This commit is contained in:
commit
fd6815f11a
|
|
@ -0,0 +1,6 @@
|
||||||
|
bin/
|
||||||
|
include/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
share/
|
||||||
|
pyvenv.cfg
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY ./app /app/app
|
||||||
|
|
||||||
|
EXPOSE 7860
|
||||||
|
EXPOSE 5555
|
||||||
|
|
||||||
|
# 컨테이너 실행시 아무 것도 안 띄움 (docker-compose에서 실행)
|
||||||
|
CMD ["bash"]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# FastAPI + Celery + Redis + Flower 메인서버 예시
|
||||||
|
|
||||||
|
## 실행 방법
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up --build
|
||||||
|
FastAPI: http://localhost:7860
|
||||||
|
|
||||||
|
Flower: http://localhost:5555
|
||||||
|
|
||||||
|
API 예시
|
||||||
|
POST /process (파일 업로드)
|
||||||
|
|
||||||
|
GET /result/{task_id} (결과 확인)
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,11 @@
|
||||||
|
# app/beat_schedule.py
|
||||||
|
from celery.schedules import crontab
|
||||||
|
|
||||||
|
beat_schedule = {
|
||||||
|
"delete-old-files-every-10-mins": {
|
||||||
|
"task": "app.tasks.delete_old_files",
|
||||||
|
"schedule": crontab(minute="*/10"),
|
||||||
|
"args": ("/app/images", 1800), # 30분 이상된 파일 삭제
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# celery_app.conf.beat_schedule = beat_schedule
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
from celery import Celery
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Redis URL 설정
|
||||||
|
redis_url = os.getenv("REDIS_URL", "redis://redis:6379/0")
|
||||||
|
|
||||||
|
# Celery 앱 생성
|
||||||
|
celery_app = Celery(
|
||||||
|
"worker",
|
||||||
|
broker=redis_url,
|
||||||
|
backend=redis_url,
|
||||||
|
include=['app.tasks'] # 태스크 모듈 포함
|
||||||
|
)
|
||||||
|
|
||||||
|
# Celery 설정
|
||||||
|
celery_app.conf.update(
|
||||||
|
task_serializer='json',
|
||||||
|
accept_content=['json'],
|
||||||
|
result_serializer='json',
|
||||||
|
timezone='Asia/Seoul',
|
||||||
|
enable_utc=True,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
from fastapi import FastAPI, Request, HTTPException, Response, UploadFile, File, Form
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Dict, Optional, List
|
||||||
|
from app.celery_worker import celery_app
|
||||||
|
from app.supabase_auth import check_user_permission
|
||||||
|
from celery.result import AsyncResult
|
||||||
|
import time
|
||||||
|
import os, shutil
|
||||||
|
import uuid
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# 로거 설정
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
# ==== 입력 모델 ====
|
||||||
|
class TranslateRequest(BaseModel):
|
||||||
|
toggle_states: Dict
|
||||||
|
unwanted_texts: Dict
|
||||||
|
image_data: str
|
||||||
|
user_id: str
|
||||||
|
ocr_method: Optional[str] = "paddleocr"
|
||||||
|
inpaint_method: Optional[str] = "lama"
|
||||||
|
|
||||||
|
class InpaintRequest(BaseModel):
|
||||||
|
mask_image_data: str
|
||||||
|
image_data: str
|
||||||
|
user_id: str
|
||||||
|
inpaint_method: Optional[str] = "lama"
|
||||||
|
|
||||||
|
class OCRRequest(BaseModel):
|
||||||
|
image_data: str
|
||||||
|
user_id: str
|
||||||
|
ocr_method: Optional[str] = "paddleocr"
|
||||||
|
|
||||||
|
# ==== 리턴 모델 ====
|
||||||
|
class OCRBox(BaseModel):
|
||||||
|
text: str
|
||||||
|
box: List[int] # [x1, y1, x2, y2, ...]
|
||||||
|
|
||||||
|
class TranslateResponse(BaseModel):
|
||||||
|
ocr_texts: List[str]
|
||||||
|
ocr_boxes: List[OCRBox]
|
||||||
|
translated_texts: List[str]
|
||||||
|
inpainted_image: str # base64
|
||||||
|
|
||||||
|
class InpaintResponse(BaseModel):
|
||||||
|
inpainted_image: str # base64
|
||||||
|
|
||||||
|
class OCRResponse(BaseModel):
|
||||||
|
ocr_texts: List[str]
|
||||||
|
ocr_boxes: List[OCRBox]
|
||||||
|
|
||||||
|
# ==== 사용자 인증 ====
|
||||||
|
async def validate_user(user_id):
|
||||||
|
allowed = await check_user_permission(user_id)
|
||||||
|
if not allowed:
|
||||||
|
raise HTTPException(status_code=403, detail="권한이 없습니다.")
|
||||||
|
|
||||||
|
# ==== 셀러리 태스크 ====
|
||||||
|
def start_celery_task(task_name, **kwargs):
|
||||||
|
return celery_app.send_task(task_name, kwargs=kwargs)
|
||||||
|
|
||||||
|
# ==== 엔드포인트 ====
|
||||||
|
@app.post("/translate_me")
|
||||||
|
async def translate_me(req: TranslateRequest):
|
||||||
|
await validate_user(req.user_id)
|
||||||
|
filename = f"{uuid.uuid4().hex}_{int(time.time())}.png"
|
||||||
|
# (여기서 파일 경로 생성 및 저장)
|
||||||
|
task = start_celery_task("app.tasks.translate_task", **req.dict(), filename=filename)
|
||||||
|
logger.info(f"태스크 등록: {task.id}, 파일명: {filename}")
|
||||||
|
return {"task_id": task.id, "filename": filename}
|
||||||
|
|
||||||
|
@app.post("/inpaint_me")
|
||||||
|
async def inpaint_me(req: InpaintRequest):
|
||||||
|
await validate_user(req.user_id)
|
||||||
|
task = start_celery_task("app.tasks.inpaint_task", **req.dict())
|
||||||
|
return {"task_id": task.id}
|
||||||
|
|
||||||
|
@app.post("/ocr_me")
|
||||||
|
async def ocr_me(req: OCRRequest):
|
||||||
|
await validate_user(req.user_id)
|
||||||
|
task = start_celery_task("app.tasks.ocr_task", **req.dict())
|
||||||
|
return {"task_id": task.id}
|
||||||
|
|
||||||
|
@app.post("/upload_image")
|
||||||
|
async def upload_image(user_id: str = Form(...), file: UploadFile = File(...)):
|
||||||
|
original_dir = f"images/{user_id}/original/"
|
||||||
|
os.makedirs(original_dir, exist_ok=True)
|
||||||
|
filename = file.filename
|
||||||
|
file_path = os.path.join(original_dir, filename)
|
||||||
|
with open(file_path, "wb") as buffer:
|
||||||
|
shutil.copyfileobj(file.file, buffer)
|
||||||
|
url = f"/images/{user_id}/original/{filename}"
|
||||||
|
return {"url": url}
|
||||||
|
|
||||||
|
@app.get("/download_image/")
|
||||||
|
def download_image(user_id: str, filename: str):
|
||||||
|
file_path = f"images/{user_id}/translated/{filename}"
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return {"error": "파일이 없습니다"}
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
data = f.read()
|
||||||
|
os.remove(file_path) # 다운로드 직후 삭제
|
||||||
|
return Response(content=data, media_type="image/png")
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
from supabase import create_client
|
||||||
|
import os
|
||||||
|
|
||||||
|
SUPABASE_URL = 'http://146.56.101.199:8000'
|
||||||
|
SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE'
|
||||||
|
|
||||||
|
#SUPABASE_URL = os.getenv("SUPABASE_URL")
|
||||||
|
#SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
||||||
|
|
||||||
|
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
|
||||||
|
|
||||||
|
async def check_user_permission(user_id: str) -> bool:
|
||||||
|
# 실제 쿼리 구조는 supabase 테이블에 맞게!
|
||||||
|
user = supabase.table("users").select("id, membership_level").eq("id", user_id).execute()
|
||||||
|
print(f"user info : {user}")
|
||||||
|
if user.data and user.data[0]["membership_level"] in ["premiun", "vip"]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
from app.celery_worker import celery_app
|
||||||
|
|
||||||
|
@celery_app.task(name="app.tasks.translate_task")
|
||||||
|
def translate_task(**kwargs):
|
||||||
|
# 실제 번역 처리 로직
|
||||||
|
return {
|
||||||
|
"ocr_texts": ["중국어1", "중국어2"],
|
||||||
|
"ocr_boxes": [
|
||||||
|
{"text": "중국어1", "box": [10, 20, 100, 120]},
|
||||||
|
{"text": "중국어2", "box": [110, 120, 200, 220]}
|
||||||
|
],
|
||||||
|
"translated_texts": ["한글1", "한글2"],
|
||||||
|
"inpainted_image": "base64string...."
|
||||||
|
}
|
||||||
|
|
||||||
|
@celery_app.task(name="app.tasks.inpaint_task")
|
||||||
|
def inpaint_task(**kwargs):
|
||||||
|
# 실제 인페인팅 처리 로직
|
||||||
|
return {
|
||||||
|
"inpainted_image": "base64string...."
|
||||||
|
}
|
||||||
|
|
||||||
|
@celery_app.task(name="app.tasks.ocr_task")
|
||||||
|
def ocr_task(**kwargs):
|
||||||
|
# 실제 OCR 처리 로직
|
||||||
|
return {
|
||||||
|
"ocr_texts": ["중국어1", "중국어2"],
|
||||||
|
"ocr_boxes": [
|
||||||
|
{"text": "중국어1", "box": [10, 20, 100, 120]},
|
||||||
|
{"text": "중국어2", "box": [110, 120, 200, 220]}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,45 @@
|
||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
fastapi:
|
||||||
|
build: .
|
||||||
|
container_name: fastapi_app
|
||||||
|
command: uvicorn app.main:app --host 0.0.0.0 --port 7890
|
||||||
|
volumes:
|
||||||
|
- ./app:/app/app
|
||||||
|
- ./images:/app/images
|
||||||
|
ports:
|
||||||
|
- "7890:7890"
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
celery:
|
||||||
|
build: .
|
||||||
|
container_name: celery_worker
|
||||||
|
command: celery -A app.celery_worker worker --loglevel=info --concurrency=2
|
||||||
|
volumes:
|
||||||
|
- ./app:/app/app
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
||||||
|
celery-beat:
|
||||||
|
build: .
|
||||||
|
container_name: celery_beat
|
||||||
|
command: celery -A app.celery_worker beat --loglevel=info
|
||||||
|
volumes:
|
||||||
|
- ./app:/app/app
|
||||||
|
- ./images:/app/images
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:6.2
|
||||||
|
container_name: redis_server
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
flower:
|
||||||
|
build: .
|
||||||
|
container_name: flower_monitor
|
||||||
|
command: celery -A app.celery_worker flower --port=5555
|
||||||
|
ports:
|
||||||
|
- "5555:5555"
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
fastapi
|
||||||
|
uvicorn[standard]
|
||||||
|
celery[redis]
|
||||||
|
redis
|
||||||
|
flower
|
||||||
|
python-dotenv
|
||||||
|
pillow
|
||||||
|
supabase
|
||||||
|
python-multipart
|
||||||
Loading…
Reference in New Issue