init: 初始化项目
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import uuid
|
||||
import bleach
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, Query
|
||||
from fastapi.responses import Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.core.database import get_db
|
||||
from app.core.security_middleware import validate_file_extension
|
||||
from app.models.template import Template
|
||||
from app.schemas.template import TemplateResponse, TemplateListItem, HTMLContentResponse, HTMLUpdateRequest
|
||||
from app.services.file_storage import save_upload, get_file_content, delete_file, TEMPLATES_DIR
|
||||
from app.services.document_processor import docx_to_html, html_to_docx_bytes, docx_to_pdf_bytes
|
||||
|
||||
ALLOWED_TAGS = [
|
||||
"p", "div", "span", "br", "hr",
|
||||
"h1", "h2", "h3", "h4", "h5", "h6",
|
||||
"ul", "ol", "li",
|
||||
"a", "img", "table", "thead", "tbody", "tr", "td", "th",
|
||||
"b", "i", "u", "strong", "em", "del", "sub", "sup",
|
||||
"pre", "code", "blockquote",
|
||||
]
|
||||
ALLOWED_ATTRS = {
|
||||
"a": ["href", "title", "target"],
|
||||
"img": ["src", "alt", "width", "height"],
|
||||
"td": ["colspan", "rowspan"],
|
||||
"th": ["colspan", "rowspan"],
|
||||
"p": ["style"],
|
||||
"span": ["style"],
|
||||
"div": ["style"],
|
||||
"table": ["style"],
|
||||
}
|
||||
|
||||
router = APIRouter(prefix="/templates", tags=["模板管理"])
|
||||
|
||||
|
||||
@router.post("", response_model=TemplateResponse)
|
||||
async def upload_template(
|
||||
file: UploadFile = File(...),
|
||||
name: str | None = Form(None),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not file.filename or not file.filename.endswith(".docx"):
|
||||
raise HTTPException(status_code=400, detail="仅支持 .docx 文件")
|
||||
|
||||
validate_file_extension(file.filename)
|
||||
file_path = await save_upload(file, TEMPLATES_DIR)
|
||||
file_content = await get_file_content(file_path)
|
||||
html_content = await docx_to_html(file_content)
|
||||
|
||||
template = Template(
|
||||
name=name or file.filename.replace(".docx", ""),
|
||||
file_path=file_path,
|
||||
html_content=html_content,
|
||||
)
|
||||
db.add(template)
|
||||
await db.flush()
|
||||
await db.refresh(template)
|
||||
return template
|
||||
|
||||
|
||||
@router.get("", response_model=list[TemplateListItem])
|
||||
async def list_templates(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
query = select(Template).offset(skip).limit(limit).order_by(Template.created_at.desc())
|
||||
result = await db.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@router.get("/{template_id}", response_model=TemplateResponse)
|
||||
async def get_template(template_id: str, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Template).where(Template.id == template_id))
|
||||
template = result.scalar_one_or_none()
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
return template
|
||||
|
||||
|
||||
@router.get("/{template_id}/html", response_model=HTMLContentResponse)
|
||||
async def get_template_html(template_id: str, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Template).where(Template.id == template_id))
|
||||
template = result.scalar_one_or_none()
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
return HTMLContentResponse(html_content=template.html_content or "")
|
||||
|
||||
|
||||
@router.put("/{template_id}/html")
|
||||
async def update_template_html(
|
||||
template_id: str,
|
||||
data: HTMLUpdateRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(Template).where(Template.id == template_id))
|
||||
template = result.scalar_one_or_none()
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
|
||||
template.html_content = bleach.clean(
|
||||
data.html_content, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS, strip=True
|
||||
)
|
||||
|
||||
docx_bytes = html_to_docx_bytes(template.html_content)
|
||||
with open(template.file_path, "wb") as f:
|
||||
f.write(docx_bytes)
|
||||
|
||||
await db.flush()
|
||||
return {"detail": "保存成功"}
|
||||
|
||||
|
||||
@router.get("/{template_id}/download")
|
||||
async def download_template(
|
||||
template_id: str,
|
||||
format: str = Query("docx", pattern="^(docx|pdf)$"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(Template).where(Template.id == template_id))
|
||||
template = result.scalar_one_or_none()
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
|
||||
file_content = await get_file_content(template.file_path)
|
||||
|
||||
if format == "pdf":
|
||||
file_content = docx_to_pdf_bytes(file_content)
|
||||
media_type = "application/pdf"
|
||||
filename = f"{template.name}.pdf"
|
||||
else:
|
||||
media_type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
filename = f"{template.name}.docx"
|
||||
|
||||
return Response(
|
||||
content=file_content,
|
||||
media_type=media_type,
|
||||
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{template_id}")
|
||||
async def delete_template(template_id: str, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(Template).where(Template.id == template_id))
|
||||
template = result.scalar_one_or_none()
|
||||
if not template:
|
||||
raise HTTPException(status_code=404, detail="模板不存在")
|
||||
|
||||
delete_file(template.file_path)
|
||||
await db.delete(template)
|
||||
return {"detail": "删除成功"}
|
||||
Reference in New Issue
Block a user