File size: 7,031 Bytes
b4f2238 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | import logging
from typing import Any
from django.utils.translation import gettext_lazy as _
from ninja import File, Router, Schema
from ninja.files import UploadedFile
from apps.client.schemas import IdentityDocDetailOut, IdentityRecognizeOut
from apps.client.services.wiring import get_task_service_port
from apps.core.infrastructure.throttling import rate_limit_from_settings
logger = logging.getLogger(__name__)
router = Router()
def _get_identity_doc_service() -> Any:
"""工厂函数:创建 ClientIdentityDocService 实例"""
from apps.client.services import ClientIdentityDocService
return ClientIdentityDocService()
def _get_identity_extraction_service() -> Any:
"""工厂函数:创建 IdentityExtractionService 实例"""
from apps.client.services.identity_extraction.extraction_service import IdentityExtractionService
return IdentityExtractionService()
def _get_id_card_merge_service() -> Any:
"""工厂函数:创建 IdCardMergeService 实例"""
from apps.client.services.id_card_merge import IdCardMergeService
return IdCardMergeService()
@router.post("/identity-doc/recognize", response=IdentityRecognizeOut)
def recognize_identity_doc(
request: Any,
file: UploadedFile = File(...),
doc_type: str = "auto",
enable_ollama: bool = False,
) -> IdentityRecognizeOut:
"""识别证件信息"""
image_bytes = file.read()
service = _get_identity_extraction_service()
normalized_doc_type = (doc_type or "").strip() or "auto"
logger.info(
"证件识别请求入参: doc_type=%s, enable_ollama=%s, filename=%s",
normalized_doc_type,
enable_ollama,
getattr(file, "name", ""),
)
result = service.safe_extract(
image_bytes,
normalized_doc_type,
enable_ollama=enable_ollama,
source_name=getattr(file, "name", None),
)
return IdentityRecognizeOut(
success=result["success"],
doc_type=result["doc_type"],
extracted_data=result["extracted_data"],
confidence=result["confidence"],
error=result.get("error"),
)
@router.post("/clients/{client_id}/identity-docs")
def add_identity_doc(
request: Any,
client_id: int,
doc_type: str,
file: UploadedFile = File(...),
) -> dict[str, Any]:
"""添加证件文档"""
service = _get_identity_doc_service()
identity_doc = service.add_identity_doc_from_upload(
client_id=client_id,
doc_type=doc_type,
uploaded_file=file,
user=getattr(request, "user", None),
)
return {"success": True, "doc_id": identity_doc.id, "message": _("证件文档添加成功")}
class MergeIdCardManualIn(Schema):
"""手动合并身份证请求体"""
front_image_path: str
back_image_path: str
front_corners: list[list[int]]
back_corners: list[list[int]]
@router.post("/identity-docs/merge-id-card")
def merge_id_card(
request: Any,
front_image: UploadedFile = File(...),
back_image: UploadedFile = File(...),
client_id: int | None = None,
) -> dict[str, Any]:
"""自动检测并合并身份证正反面为 PDF,可选保存到客户证件"""
service = _get_id_card_merge_service()
result: dict[str, Any] = service.merge_id_card_with_detection(front_image, back_image)
if result.get("success") and client_id:
doc_service = _get_identity_doc_service()
doc = doc_service.add_identity_doc(
client_id=client_id,
doc_type="id_card",
file_path=result["pdf_path"],
)
result["doc_id"] = doc.id
return result
@router.post("/identity-docs/merge-id-card-direct")
def merge_id_card_direct(
request: Any,
front_image: UploadedFile = File(...),
back_image: UploadedFile = File(...),
client_id: int | None = None,
) -> dict[str, Any]:
"""直接合并已裁剪的身份证正反面为 PDF(前端已完成裁剪),可选保存到客户证件"""
service = _get_id_card_merge_service()
result: dict[str, Any] = service.merge_id_card(front_image, back_image)
if result.get("success") and client_id:
doc_service = _get_identity_doc_service()
doc = doc_service.add_identity_doc(
client_id=client_id,
doc_type="id_card",
file_path=result["pdf_path"],
)
result["doc_id"] = doc.id
return result
@router.post("/identity-docs/merge-id-card-manual")
def merge_id_card_manual(
request: Any,
data: MergeIdCardManualIn,
) -> dict[str, Any]:
"""手动指定四角坐标合并身份证"""
service = _get_id_card_merge_service()
result: dict[str, Any] = service.merge_id_card_manual(
front_image_path=data.front_image_path,
back_image_path=data.back_image_path,
front_corners=data.front_corners,
back_corners=data.back_corners,
)
return result
@router.get("/identity-docs/{doc_id}", response=IdentityDocDetailOut)
def get_identity_doc(request: Any, doc_id: int) -> IdentityDocDetailOut:
"""
获取证件文档
Args:
request: HTTP 请求
doc_id: 证件文档 ID
Returns:
证件文档信息
"""
service = _get_identity_doc_service()
identity_doc = service.get_identity_doc(doc_id)
return IdentityDocDetailOut(
id=identity_doc.id,
client_id=identity_doc.client_id,
doc_type=identity_doc.doc_type,
file_path=identity_doc.file_path,
uploaded_at=identity_doc.uploaded_at,
media_url=identity_doc.media_url,
)
@router.delete("/identity-docs/{doc_id}")
def delete_identity_doc(request: Any, doc_id: int) -> dict[str, Any]:
"""
删除证件文档
Args:
request: HTTP 请求
doc_id: 证件文档 ID
Returns:
操作结果
"""
service = _get_identity_doc_service()
service.delete_identity_doc(doc_id=doc_id, user=getattr(request, "user", None))
return {"success": True, "message": _("证件文档删除成功")}
@router.post("/identity-doc/recognize/submit")
@rate_limit_from_settings("TASK", by_user=True)
def submit_recognize_task(
request: Any,
file: UploadedFile = File(...),
) -> dict[str, Any]:
"""提交证件识别异步任务"""
service = _get_identity_doc_service()
rel_path = service.save_uploaded_file_to_dir(file, rel_dir="client_docs/recognize")
task_service = get_task_service_port()
task_id: str = task_service.submit_task(
"apps.client.tasks.execute_identity_doc_recognition",
rel_path,
)
logger.info("证件识别任务已提交", extra={"task_id": task_id, "file_path": rel_path})
return {"task_id": task_id, "status": "pending"}
@router.get("/identity-doc/task/{task_id}")
def get_recognize_task_status(
request: Any,
task_id: str,
) -> dict[str, Any]:
"""查询证件识别任务状态"""
task_service = get_task_service_port()
result: dict[str, Any] = task_service.get_task_status(task_id)
return result
|