306 lines
8.5 KiB
Python
306 lines
8.5 KiB
Python
"""
|
||
证书管理 API 端点
|
||
|
||
提供证书相关的 RESTful API:
|
||
- 获取证书列表
|
||
- 获取证书详情
|
||
- 下载证书
|
||
- 验证证书
|
||
"""
|
||
|
||
from typing import Optional, List
|
||
from fastapi import APIRouter, Depends, HTTPException, status, Response, Query
|
||
from fastapi.responses import StreamingResponse
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
import io
|
||
|
||
from app.core.deps import get_db
|
||
from app.core.security import get_current_user
|
||
from app.models.user import User
|
||
from app.services.certificate_service import CertificateService
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
@router.get("/templates")
|
||
async def get_certificate_templates(
|
||
cert_type: Optional[str] = Query(None, description="证书类型: course/exam/achievement"),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""获取证书模板列表"""
|
||
service = CertificateService(db)
|
||
templates = await service.get_templates(cert_type)
|
||
return {
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": templates
|
||
}
|
||
|
||
|
||
@router.get("/me")
|
||
async def get_my_certificates(
|
||
cert_type: Optional[str] = Query(None, description="证书类型过滤"),
|
||
offset: int = Query(0, ge=0),
|
||
limit: int = Query(20, ge=1, le=100),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""获取当前用户的证书列表"""
|
||
service = CertificateService(db)
|
||
result = await service.get_user_certificates(
|
||
user_id=current_user.id,
|
||
cert_type=cert_type,
|
||
offset=offset,
|
||
limit=limit
|
||
)
|
||
return {
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": result
|
||
}
|
||
|
||
|
||
@router.get("/user/{user_id}")
|
||
async def get_user_certificates(
|
||
user_id: int,
|
||
cert_type: Optional[str] = Query(None),
|
||
offset: int = Query(0, ge=0),
|
||
limit: int = Query(20, ge=1, le=100),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""获取指定用户的证书列表(需要管理员权限)"""
|
||
# 只允许查看自己的证书或管理员查看
|
||
if current_user.id != user_id and current_user.role not in ["admin", "enterprise_admin"]:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_403_FORBIDDEN,
|
||
detail="无权查看其他用户的证书"
|
||
)
|
||
|
||
service = CertificateService(db)
|
||
result = await service.get_user_certificates(
|
||
user_id=user_id,
|
||
cert_type=cert_type,
|
||
offset=offset,
|
||
limit=limit
|
||
)
|
||
return {
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": result
|
||
}
|
||
|
||
|
||
@router.get("/{cert_id}")
|
||
async def get_certificate_detail(
|
||
cert_id: int,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""获取证书详情"""
|
||
service = CertificateService(db)
|
||
cert = await service.get_certificate_by_id(cert_id)
|
||
|
||
if not cert:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail="证书不存在"
|
||
)
|
||
|
||
return {
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": cert
|
||
}
|
||
|
||
|
||
@router.get("/{cert_id}/image")
|
||
async def get_certificate_image(
|
||
cert_id: int,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""获取证书分享图片"""
|
||
service = CertificateService(db)
|
||
|
||
try:
|
||
# 获取基础URL
|
||
base_url = "https://kpl.example.com/certificates" # 可从配置读取
|
||
|
||
image_bytes = await service.generate_certificate_image(cert_id, base_url)
|
||
|
||
return StreamingResponse(
|
||
io.BytesIO(image_bytes),
|
||
media_type="image/png",
|
||
headers={
|
||
"Content-Disposition": f"inline; filename=certificate_{cert_id}.png"
|
||
}
|
||
)
|
||
except ValueError as e:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=str(e)
|
||
)
|
||
except Exception as e:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"生成证书图片失败: {str(e)}"
|
||
)
|
||
|
||
|
||
@router.get("/{cert_id}/download")
|
||
async def download_certificate_pdf(
|
||
cert_id: int,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""下载证书PDF"""
|
||
service = CertificateService(db)
|
||
cert = await service.get_certificate_by_id(cert_id)
|
||
|
||
if not cert:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail="证书不存在"
|
||
)
|
||
|
||
# 如果已有PDF URL则重定向
|
||
if cert.get("pdf_url"):
|
||
return {
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"download_url": cert["pdf_url"]
|
||
}
|
||
}
|
||
|
||
# 否则返回图片作为替代
|
||
try:
|
||
base_url = "https://kpl.example.com/certificates"
|
||
image_bytes = await service.generate_certificate_image(cert_id, base_url)
|
||
|
||
return StreamingResponse(
|
||
io.BytesIO(image_bytes),
|
||
media_type="image/png",
|
||
headers={
|
||
"Content-Disposition": f"attachment; filename=certificate_{cert['certificate_no']}.png"
|
||
}
|
||
)
|
||
except Exception as e:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"下载失败: {str(e)}"
|
||
)
|
||
|
||
|
||
@router.get("/verify/{cert_no}")
|
||
async def verify_certificate(
|
||
cert_no: str,
|
||
db: AsyncSession = Depends(get_db)
|
||
):
|
||
"""
|
||
验证证书真伪
|
||
|
||
此接口无需登录,可用于公开验证证书
|
||
"""
|
||
service = CertificateService(db)
|
||
cert = await service.get_certificate_by_no(cert_no)
|
||
|
||
if not cert:
|
||
return {
|
||
"code": 404,
|
||
"message": "证书不存在或编号错误",
|
||
"data": {
|
||
"valid": False,
|
||
"certificate_no": cert_no
|
||
}
|
||
}
|
||
|
||
return {
|
||
"code": 200,
|
||
"message": "证书验证通过",
|
||
"data": {
|
||
"valid": True,
|
||
"certificate_no": cert_no,
|
||
"title": cert.get("title"),
|
||
"type_name": cert.get("type_name"),
|
||
"issued_at": cert.get("issued_at"),
|
||
"user": cert.get("user", {}),
|
||
}
|
||
}
|
||
|
||
|
||
@router.post("/issue/course")
|
||
async def issue_course_certificate(
|
||
course_id: int,
|
||
course_name: str,
|
||
completion_rate: float = 100.0,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
颁发课程结业证书
|
||
|
||
通常由系统在用户完成课程时自动调用
|
||
"""
|
||
service = CertificateService(db)
|
||
|
||
try:
|
||
cert = await service.issue_course_certificate(
|
||
user_id=current_user.id,
|
||
course_id=course_id,
|
||
course_name=course_name,
|
||
completion_rate=completion_rate,
|
||
user_name=current_user.full_name or current_user.username
|
||
)
|
||
await db.commit()
|
||
|
||
return {
|
||
"code": 200,
|
||
"message": "证书颁发成功",
|
||
"data": cert
|
||
}
|
||
except ValueError as e:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail=str(e)
|
||
)
|
||
|
||
|
||
@router.post("/issue/exam")
|
||
async def issue_exam_certificate(
|
||
exam_id: int,
|
||
exam_name: str,
|
||
score: float,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
颁发考试合格证书
|
||
|
||
通常由系统在用户考试通过时自动调用
|
||
"""
|
||
service = CertificateService(db)
|
||
|
||
try:
|
||
cert = await service.issue_exam_certificate(
|
||
user_id=current_user.id,
|
||
exam_id=exam_id,
|
||
exam_name=exam_name,
|
||
score=score,
|
||
user_name=current_user.full_name or current_user.username
|
||
)
|
||
await db.commit()
|
||
|
||
return {
|
||
"code": 200,
|
||
"message": "证书颁发成功",
|
||
"data": cert
|
||
}
|
||
except ValueError as e:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail=str(e)
|
||
)
|