- 대시보드: 8컬럼 형식의 sheet.csv를 안정적으로 지원하도록 파싱 로직 개선 - 크롤러: Playwright 기반 크롤링 엔진 고도화 및 실시간 로그 전송 기능 강화 - UI/UX: 대시보드 동기화 버튼 및 헤더 레이아웃 최적화
136 lines
4.5 KiB
Python
136 lines
4.5 KiB
Python
import os
|
|
import sys
|
|
|
|
# 한글 환경 및 Tesseract 경로 강제 설정
|
|
os.environ["PYTHONIOENCODING"] = "utf-8"
|
|
os.environ["TESSDATA_PREFIX"] = r"C:\Users\User\AppData\Local\Programs\Tesseract-OCR\tessdata"
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import StreamingResponse, FileResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
from analyze import analyze_file_content
|
|
from crawler_service import run_crawler_service
|
|
import asyncio
|
|
from fastapi import Request
|
|
|
|
app = FastAPI(title="Project Master Overseas API")
|
|
templates = Jinja2Templates(directory="templates")
|
|
|
|
# --- 유틸리티: 동기 함수를 스레드 풀에서 실행 ---
|
|
async def run_in_threadpool(func, *args):
|
|
loop = asyncio.get_event_loop()
|
|
return await loop.run_in_executor(None, func, *args)
|
|
|
|
# 정적 파일 및 미들웨어 설정
|
|
app.mount("/style", StaticFiles(directory="style"), name="style")
|
|
app.mount("/js", StaticFiles(directory="js"), name="js")
|
|
app.mount("/sample_files", StaticFiles(directory="sample"), name="sample_files")
|
|
|
|
@app.get("/sample.png")
|
|
async def get_sample_img():
|
|
return FileResponse("sample.png")
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=False,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
# --- HTML 라우팅 ---
|
|
import csv
|
|
|
|
@app.get("/project-data")
|
|
async def get_project_data():
|
|
"""
|
|
sheet.csv 파일을 읽어서 프로젝트 현황 데이터를 반환
|
|
"""
|
|
projects = []
|
|
try:
|
|
if not os.path.exists("sheet.csv"):
|
|
return []
|
|
|
|
with open("sheet.csv", mode="r", encoding="utf-8-sig") as f:
|
|
reader = csv.reader(f)
|
|
rows = list(reader)
|
|
|
|
# "No." 헤더를 찾아 데이터 시작점 결정
|
|
start_idx = None
|
|
for i, row in enumerate(rows):
|
|
if any("No." in cell for cell in row):
|
|
start_idx = i + 1
|
|
break
|
|
|
|
if start_idx is not None:
|
|
for row in rows[start_idx:]:
|
|
if len(row) >= 8:
|
|
# [프로젝트명, 담당부서, 담당자, 최근활동로그, 파일수]
|
|
# 복구된 sheet.csv 형식에 맞춰 인덱스 추출 (1, 2, 3, 5, 7)
|
|
try:
|
|
# 파일 수 숫자로 변환 (공백 제거 후 처리)
|
|
raw_count = row[7].strip()
|
|
file_count = int(raw_count) if raw_count.isdigit() else 0
|
|
except (ValueError, IndexError):
|
|
file_count = 0
|
|
|
|
projects.append([
|
|
row[1], # 프로젝트 명
|
|
row[2], # 담당부서
|
|
row[3], # 담당자
|
|
row[5], # 최근 활동로그
|
|
file_count # 파일 수
|
|
])
|
|
except Exception as e:
|
|
print(f"Error reading sheet.csv: {e}")
|
|
return {"error": str(e)}
|
|
|
|
return projects
|
|
|
|
@app.get("/")
|
|
async def root(request: Request):
|
|
return templates.TemplateResponse("index.html", {"request": request})
|
|
|
|
@app.get("/dashboard")
|
|
async def get_dashboard(request: Request):
|
|
return templates.TemplateResponse("dashboard.html", {"request": request})
|
|
|
|
@app.get("/mailTest")
|
|
@app.get("/mailTest.html")
|
|
async def get_mail_test(request: Request):
|
|
return templates.TemplateResponse("mailTest.html", {"request": request})
|
|
|
|
# --- 데이터 API ---
|
|
@app.get("/attachments")
|
|
async def get_attachments():
|
|
sample_path = "sample"
|
|
if not os.path.exists(sample_path):
|
|
os.makedirs(sample_path)
|
|
files = []
|
|
for f in os.listdir(sample_path):
|
|
f_path = os.path.join(sample_path, f)
|
|
if os.path.isfile(f_path):
|
|
files.append({
|
|
"name": f,
|
|
"size": f"{os.path.getsize(f_path) / 1024:.1f} KB"
|
|
})
|
|
return files
|
|
|
|
@app.get("/analyze-file")
|
|
async def analyze_file(filename: str):
|
|
"""
|
|
분석 서비스(analyze.py) 호출 - 스레드 풀에서 비차단 방식으로 실행
|
|
"""
|
|
return await run_in_threadpool(analyze_file_content, filename)
|
|
|
|
@app.get("/sync")
|
|
async def sync_data():
|
|
"""
|
|
크롤링 서비스(crawler_service.py) 호출
|
|
"""
|
|
print(">>> /sync request received")
|
|
return StreamingResponse(run_crawler_service(), media_type="text_event-stream")
|