"""文档服务实现。""" from __future__ import annotations import hashlib import mimetypes import time from pathlib import Path from sqlalchemy import text from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession from fastapi_common.fastapi_common_web.domain.responses import StatusCodeEnum from fastapi_common.fastapi_common_web.exception.LeauditException import LeauditException from fastapi_common.fastapi_common_storage.oss_path_utils import OssPathUtils from fastapi_modules.fastapi_leaudit.domian.vo.documentVo import DocumentUploadVO from fastapi_modules.fastapi_leaudit.models import LeauditDocument, LeauditDocumentFile from fastapi_modules.fastapi_leaudit.services import IAuditService, IDocumentService, IOssService from fastapi_modules.fastapi_leaudit.services.impl.auditServiceImpl import AuditServiceImpl from fastapi_modules.fastapi_leaudit.services.impl.ossServiceImpl import OssServiceImpl class DocumentServiceImpl(IDocumentService): """文档服务实现。""" def __init__( self, OssService: IOssService | None = None, AuditService: IAuditService | None = None, ) -> None: self.OssService = OssService or OssServiceImpl() self.AuditService = AuditService or AuditServiceImpl() async def Upload( self, FileName: str, FileContent: bytes, ContentType: str | None, TypeId: int | None = None, TypeCode: str | None = None, BizDocumentId: int | None = None, Region: str = "default", FileRole: str = "primary", CreatedBy: int | None = None, AutoRun: bool = False, ) -> DocumentUploadVO: """上传文档并建立 LeAudit document/file 记录。""" if not FileName: raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "上传文件名不能为空") if not FileContent: raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "上传文件内容不能为空") if not TypeId and not TypeCode: raise LeauditException(StatusCodeEnum.HTTP_400_BAD_REQUEST, "typeId 与 typeCode 至少传一个") normalizedRegion = (Region or "default").strip() or "default" normalizedFileRole = (FileRole or "primary").strip() or "primary" fileExt = Path(FileName).suffix.lstrip(".").lower() or None mimeType = ContentType or mimetypes.guess_type(FileName)[0] or "application/octet-stream" fileSha256 = hashlib.sha256(FileContent).hexdigest() fileSize = len(FileContent) async with GetAsyncSession() as Session: if TypeId is not None and TypeCode is not None: typeResult = await Session.execute( text( """ SELECT id, code FROM leaudit_document_types WHERE id = :type_id AND code = :type_code AND deleted_at IS NULL LIMIT 1 """ ), {"type_id": TypeId, "type_code": TypeCode}, ) elif TypeId is not None: typeResult = await Session.execute( text( """ SELECT id, code FROM leaudit_document_types WHERE id = :type_id AND deleted_at IS NULL LIMIT 1 """ ), {"type_id": TypeId}, ) else: typeResult = await Session.execute( text( """ SELECT id, code FROM leaudit_document_types WHERE code = :type_code AND deleted_at IS NULL LIMIT 1 """ ), {"type_code": TypeCode}, ) typeRow = typeResult.mappings().first() if not typeRow: raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "文档类型不存在或已停用") resolvedTypeId = int(typeRow["id"]) resolvedTypeCode = str(typeRow["code"]) resolvedBizDocumentId = BizDocumentId or int(time.time() * 1000) document = await LeauditDocument.upsert_by_biz_id( Session, bizDocumentId=resolvedBizDocumentId, typeId=resolvedTypeId, region=normalizedRegion, processingStatus="waiting", ) versionCount = await LeauditDocumentFile.count_by_document(Session, document.Id) versionNo = f"v{versionCount + 1}" objectKey = OssPathUtils.BuildBusinessDocKey( Region=normalizedRegion, TypeCode=resolvedTypeCode, DocumentId=document.Id, Version=versionNo, FileRole=normalizedFileRole, FileName=FileName, ) ossUrl = await self.OssService.UploadBytes( ObjectKey=objectKey, Content=FileContent, ContentType=mimeType, ) await LeauditDocumentFile.deactivate_active_by_document(Session, document.Id) documentFile = LeauditDocumentFile( documentId=document.Id, fileRole=normalizedFileRole, fileName=FileName, fileExt=fileExt, mimeType=mimeType, fileSize=fileSize, sha256=fileSha256, localPath=None, ossUrl=ossUrl, storageProvider="minio", isActive=True, createdBy=CreatedBy, ) Session.add(documentFile) await Session.flush() await Session.commit() await Session.refresh(document) await Session.refresh(documentFile) run = None processingStatus = document.processingStatus or "waiting" if AutoRun: run = await self.AuditService.Run(DocumentId=document.Id) processingStatus = "running" if run.status in {"pending", "running"} else run.status return DocumentUploadVO( documentId=document.Id, bizDocumentId=document.bizDocumentId, fileId=documentFile.Id, typeId=resolvedTypeId, typeCode=resolvedTypeCode, region=normalizedRegion, fileName=documentFile.fileName, ossUrl=ossUrl, processingStatus=processingStatus, autoRunTriggered=AutoRun, run=run, )