191 lines
6.2 KiB
Python
191 lines
6.2 KiB
Python
# app/routers/contact_qrcodes.py
|
|
import os
|
|
import uuid
|
|
from typing import List, Union
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
import qrcode
|
|
from qrcode.image.pil import PilImage
|
|
|
|
from app.database import get_db
|
|
from app.models.contact_qrcode import ContactQRCode
|
|
from app.models.admin import Admin
|
|
from app.auth import get_current_admin
|
|
from app.config import get_settings
|
|
|
|
router = APIRouter()
|
|
settings = get_settings()
|
|
|
|
|
|
# Schemas
|
|
class ContactQRCodeCreate(BaseModel):
|
|
content: str
|
|
description: str = ""
|
|
sort_order: int = 0
|
|
is_enabled: bool = True
|
|
|
|
|
|
class ContactQRCodeUpdate(BaseModel):
|
|
content: str = None
|
|
description: str = None
|
|
sort_order: int = None
|
|
is_enabled: bool = None
|
|
|
|
|
|
class ContactQRCodeResponse(BaseModel):
|
|
code: int = 200
|
|
data: Union[dict, list] = None
|
|
message: str = "success"
|
|
|
|
|
|
# Helper
|
|
def get_qrcode_dir():
|
|
# __file__ = backend/app/routers/contact_qrcodes.py
|
|
# 需要向上3级才能到达项目根目录
|
|
routers_dir = os.path.dirname(os.path.abspath(__file__)) # backend/app/routers
|
|
app_dir = os.path.dirname(routers_dir) # backend/app
|
|
backend_dir = os.path.dirname(app_dir) # backend
|
|
project_root = os.path.dirname(backend_dir) # project root
|
|
qrcode_dir = os.path.join(project_root, "storage", "admin", "images", "qrcode")
|
|
os.makedirs(qrcode_dir, exist_ok=True)
|
|
return qrcode_dir
|
|
|
|
|
|
def generate_qrcode_image(content: str, filename: str) -> str:
|
|
"""Generate QR code image and save to storage, return URL path"""
|
|
qrcode_dir = get_qrcode_dir()
|
|
filepath = os.path.join(qrcode_dir, filename)
|
|
|
|
qr = qrcode.QRCode(
|
|
version=1,
|
|
error_correction=qrcode.constants.ERROR_CORRECT_H,
|
|
box_size=10,
|
|
border=4,
|
|
)
|
|
qr.add_data(content)
|
|
qr.make(fit=True)
|
|
|
|
img = qr.make_image(fill_color="black", back_color="white")
|
|
img.save(filepath)
|
|
|
|
return f"/storage/admin/images/qrcode/{filename}"
|
|
|
|
|
|
def qrcode_to_dict(qr: ContactQRCode) -> dict:
|
|
return {
|
|
"id": qr.id,
|
|
"content": qr.content,
|
|
"description": qr.description,
|
|
"image_path": qr.image_path,
|
|
"sort_order": qr.sort_order,
|
|
"is_enabled": qr.is_enabled,
|
|
}
|
|
|
|
|
|
# Public routes
|
|
@router.get("/contact-qrcodes", response_model=ContactQRCodeResponse)
|
|
def get_public_qrcodes(db: Session = Depends(get_db)):
|
|
"""Get enabled QR codes for public display"""
|
|
qrcodes = db.query(ContactQRCode).filter(
|
|
ContactQRCode.is_enabled == True
|
|
).order_by(ContactQRCode.sort_order.asc()).all()
|
|
return ContactQRCodeResponse(data=[qrcode_to_dict(q) for q in qrcodes])
|
|
|
|
|
|
# Admin routes
|
|
@router.get("/admin/contact-qrcodes", response_model=ContactQRCodeResponse)
|
|
def get_admin_qrcodes(
|
|
db: Session = Depends(get_db),
|
|
current_admin: Admin = Depends(get_current_admin)
|
|
):
|
|
"""Get all QR codes for admin"""
|
|
qrcodes = db.query(ContactQRCode).order_by(ContactQRCode.sort_order.asc()).all()
|
|
return ContactQRCodeResponse(data=[qrcode_to_dict(q) for q in qrcodes])
|
|
|
|
|
|
@router.post("/admin/contact-qrcodes", response_model=ContactQRCodeResponse)
|
|
def create_qrcode(
|
|
request: ContactQRCodeCreate,
|
|
db: Session = Depends(get_db),
|
|
current_admin: Admin = Depends(get_current_admin)
|
|
):
|
|
"""Create new QR code, auto-generate image"""
|
|
if not request.content.strip():
|
|
raise HTTPException(status_code=400, detail="内容不能为空")
|
|
|
|
filename = f"qrcode_{uuid.uuid4().hex[:8]}.png"
|
|
image_path = generate_qrcode_image(request.content.strip(), filename)
|
|
|
|
qr = ContactQRCode(
|
|
content=request.content.strip(),
|
|
description=request.description.strip() if request.description else "",
|
|
image_path=image_path,
|
|
sort_order=request.sort_order,
|
|
is_enabled=request.is_enabled,
|
|
)
|
|
db.add(qr)
|
|
db.commit()
|
|
db.refresh(qr)
|
|
return ContactQRCodeResponse(message="创建成功", data=qrcode_to_dict(qr))
|
|
|
|
|
|
@router.put("/admin/contact-qrcodes/{qr_id}", response_model=ContactQRCodeResponse)
|
|
def update_qrcode(
|
|
qr_id: int,
|
|
request: ContactQRCodeUpdate,
|
|
db: Session = Depends(get_db),
|
|
current_admin: Admin = Depends(get_current_admin)
|
|
):
|
|
"""Update QR code, regenerate image if content changed"""
|
|
qr = db.query(ContactQRCode).filter(ContactQRCode.id == qr_id).first()
|
|
if not qr:
|
|
raise HTTPException(status_code=404, detail="QR code not found")
|
|
|
|
update_data = request.dict(exclude_unset=True)
|
|
|
|
# If content changed, regenerate QR code image
|
|
new_content = update_data.get("content", "").strip()
|
|
if new_content and new_content != qr.content:
|
|
# Delete old image
|
|
if qr.image_path:
|
|
old_filename = os.path.basename(qr.image_path)
|
|
old_filepath = os.path.join(get_qrcode_dir(), old_filename)
|
|
if os.path.exists(old_filepath):
|
|
os.remove(old_filepath)
|
|
# Generate new image
|
|
filename = f"qrcode_{uuid.uuid4().hex[:8]}.png"
|
|
qr.image_path = generate_qrcode_image(new_content, filename)
|
|
qr.content = new_content
|
|
update_data.pop("content", None)
|
|
|
|
for field, value in update_data.items():
|
|
setattr(qr, field, value)
|
|
|
|
db.commit()
|
|
db.refresh(qr)
|
|
return ContactQRCodeResponse(message="更新成功", data=qrcode_to_dict(qr))
|
|
|
|
|
|
@router.delete("/admin/contact-qrcodes/{qr_id}", response_model=ContactQRCodeResponse)
|
|
def delete_qrcode(
|
|
qr_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_admin: Admin = Depends(get_current_admin)
|
|
):
|
|
"""Delete QR code and its image file"""
|
|
qr = db.query(ContactQRCode).filter(ContactQRCode.id == qr_id).first()
|
|
if not qr:
|
|
raise HTTPException(status_code=404, detail="QR code not found")
|
|
|
|
# Delete image file
|
|
if qr.image_path:
|
|
filename = os.path.basename(qr.image_path)
|
|
filepath = os.path.join(get_qrcode_dir(), filename)
|
|
if os.path.exists(filepath):
|
|
os.remove(filepath)
|
|
|
|
db.delete(qr)
|
|
db.commit()
|
|
return ContactQRCodeResponse(message="删除成功")
|