Files
012-kaopeilian/backend/app/api/v1/endpoints/certificate.py
yuliang_guo 054375c36c
Some checks failed
continuous-integration/drone/push Build is failing
fix: 修复 certificate/dashboard API 的 get_db 导入路径
2026-01-29 17:08:44 +08:00

306 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
证书管理 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)
)