package jnpf.fileStorage.dms; import jnpf.base.ActionResult; import jnpf.base.ActionResultCode; import jnpf.config.ConfigValueUtil; import jnpf.constant.MsgCode; import jnpf.dms.DmsResourceApi; import jnpf.dmsEntity.storage.form.DmsFileRevisionRegisterForm; import jnpf.dmsEntity.storage.form.DmsFileUploadRegisterForm; import jnpf.dmsEntity.storage.vo.DmsFileUploadVO; import jnpf.exception.DataException; import jnpf.util.OptimizeUtil; import jnpf.util.StringUtil; import jnpf.util.UpUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileStorageService; import org.dromara.x.file.storage.core.hash.HashInfo; import org.dromara.x.file.storage.core.platform.MinioFileStorage; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.nio.file.Paths; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Objects; import java.util.UUID; @Slf4j @Service @RequiredArgsConstructor public class DmsFileStorageService { static final String STORAGE_PLATFORM = "seaweedfs-1"; static final String SERVICE_ID = "jnpf-file"; private final FileStorageService fileStorageService; private final DmsResourceApi dmsResourceApi; private final ConfigValueUtil configValueUtil; public DmsFileUploadVO upload(UUID folderId, String sourceId, String documentNo, String documentName, String documentType, String responsibleDeptId, String versionLabel, String preparedBy, LocalDate preparedOn, MultipartFile file) { validate(sourceId, documentNo, documentName, documentType, responsibleDeptId, versionLabel, preparedBy, preparedOn, file); String originalFilename = safeFilename(file.getOriginalFilename()); String extension = UpUtil.getFileType(file); if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), extension)) { throw new DataException(MsgCode.FA017.get()); } UUID documentId = UuidV7Generator.generate(); UUID fileVersionId = UuidV7Generator.generate(); UUID fileObjectId = UuidV7Generator.generate(); String objectPath = objectPath(documentId); String objectName = fileVersionId + (StringUtil.isEmpty(extension) ? "" : "." + extension); FileInfo stored = fileStorageService.of(file) .setPlatform(STORAGE_PLATFORM) .setPath(objectPath) .setSaveFilename(objectName) .setOriginalFilename(originalFilename) .setHashCalculatorSha256() .upload(); if (stored == null || stored.getHashInfo() == null || StringUtil.isEmpty(stored.getHashInfo().getSha256())) { deleteQuietly(stored); throw new DataException("SeaweedFS 上传失败或未生成 SHA-256"); } DmsFileUploadRegisterForm form = registration(folderId, sourceId, documentNo, documentName, documentType, responsibleDeptId, versionLabel, preparedBy, preparedOn, originalFilename, documentId, fileVersionId, fileObjectId, stored); try { ActionResult result = dmsResourceApi.registerUpload(SERVICE_ID, form); if (result == null || !ActionResultCode.SUCCESS.getCode().equals(result.getCode()) || result.getData() == null) { throw new DataException(result == null ? "DMS 上传登记无响应" : result.getMsg()); } if (!sameStoredObject(result.getData(), stored)) { deleteQuietly(stored); } return result.getData(); } catch (RuntimeException exception) { deleteQuietly(stored); throw exception; } } public DmsFileUploadVO revise(UUID documentId, UUID baseFileVersionId, String sourceId, String versionLabel, String preparedBy, LocalDate preparedOn, String revisionReason, MultipartFile file) { validateRevision(documentId, baseFileVersionId, sourceId, versionLabel, preparedBy, preparedOn, revisionReason, file); String originalFilename = safeFilename(file.getOriginalFilename()); String extension = UpUtil.getFileType(file); if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), extension)) { throw new DataException(MsgCode.FA017.get()); } UUID fileVersionId = UuidV7Generator.generate(); UUID fileObjectId = UuidV7Generator.generate(); String objectPath = objectPath(documentId); String objectName = fileVersionId + (StringUtil.isEmpty(extension) ? "" : "." + extension); FileInfo stored = fileStorageService.of(file) .setPlatform(STORAGE_PLATFORM) .setPath(objectPath) .setSaveFilename(objectName) .setOriginalFilename(originalFilename) .setHashCalculatorSha256() .upload(); if (stored == null || stored.getHashInfo() == null || StringUtil.isEmpty(stored.getHashInfo().getSha256())) { deleteQuietly(stored); throw new DataException("SeaweedFS 上传失败或未生成 SHA-256"); } DmsFileRevisionRegisterForm form = revisionRegistration(documentId, baseFileVersionId, fileVersionId, fileObjectId, sourceId, versionLabel, preparedBy, preparedOn, revisionReason, originalFilename, stored); try { ActionResult result = dmsResourceApi.registerRevision(SERVICE_ID, form); if (result == null || !ActionResultCode.SUCCESS.getCode().equals(result.getCode()) || result.getData() == null) { throw new DataException(result == null ? "DMS 修订登记无响应" : result.getMsg()); } if (!sameStoredObject(result.getData(), stored)) { deleteQuietly(stored); } return result.getData(); } catch (RuntimeException exception) { deleteQuietly(stored); throw exception; } } private DmsFileUploadRegisterForm registration(UUID folderId, String sourceId, String documentNo, String documentName, String documentType, String responsibleDeptId, String versionLabel, String preparedBy, LocalDate preparedOn, String originalFilename, UUID documentId, UUID fileVersionId, UUID fileObjectId, FileInfo stored) { MinioFileStorage storage = fileStorageService.getFileStorage(STORAGE_PLATFORM); HashInfo hashInfo = stored.getHashInfo(); DmsFileUploadRegisterForm form = new DmsFileUploadRegisterForm(); form.setDocumentId(documentId); form.setFileVersionId(fileVersionId); form.setFileObjectId(fileObjectId); form.setFolderId(folderId); form.setDocumentNo(documentNo.trim()); form.setDocumentName(documentName.trim()); form.setDocumentType(documentType.trim()); form.setResponsibleDeptId(responsibleDeptId.trim()); form.setVersionLabel(versionLabel.trim()); form.setFileName(originalFilename); form.setMimeType(trimToNull(stored.getContentType())); form.setSizeBytes(stored.getSize()); form.setChecksumSha256(hashInfo.getSha256()); form.setPreparedBy(preparedBy.trim()); form.setPreparedOn(preparedOn); form.setStoragePlatform(stored.getPlatform()); form.setBucketName(storage.getBucketName()); form.setBasePath(trimToEmpty(stored.getBasePath())); form.setObjectPath(stored.getPath()); form.setObjectName(stored.getFilename()); form.setSourceId(sourceId.trim()); return form; } private DmsFileRevisionRegisterForm revisionRegistration(UUID documentId, UUID baseFileVersionId, UUID fileVersionId, UUID fileObjectId, String sourceId, String versionLabel, String preparedBy, LocalDate preparedOn, String revisionReason, String originalFilename, FileInfo stored) { MinioFileStorage storage = fileStorageService.getFileStorage(STORAGE_PLATFORM); HashInfo hashInfo = stored.getHashInfo(); DmsFileRevisionRegisterForm form = new DmsFileRevisionRegisterForm(); form.setDocumentId(documentId); form.setBaseFileVersionId(baseFileVersionId); form.setFileVersionId(fileVersionId); form.setFileObjectId(fileObjectId); form.setVersionLabel(versionLabel.trim()); form.setFileName(originalFilename); form.setMimeType(trimToNull(stored.getContentType())); form.setSizeBytes(stored.getSize()); form.setChecksumSha256(hashInfo.getSha256()); form.setPreparedBy(preparedBy.trim()); form.setPreparedOn(preparedOn); form.setRevisionReason(revisionReason.trim()); form.setStoragePlatform(stored.getPlatform()); form.setBucketName(storage.getBucketName()); form.setBasePath(trimToEmpty(stored.getBasePath())); form.setObjectPath(stored.getPath()); form.setObjectName(stored.getFilename()); form.setSourceId(sourceId.trim()); return form; } private boolean sameStoredObject(DmsFileUploadVO registered, FileInfo stored) { return Objects.equals(registered.getStoragePlatform(), stored.getPlatform()) && Objects.equals(registered.getBasePath(), trimToEmpty(stored.getBasePath())) && Objects.equals(registered.getObjectPath(), stored.getPath()) && Objects.equals(registered.getObjectName(), stored.getFilename()); } private String objectPath(UUID documentId) { return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd/")) + documentId + "/"; } private String safeFilename(String value) { if (value == null || value.trim().isEmpty()) { throw new DataException("文件名不能为空"); } String normalized = value.replace('\\', '/'); String filename = Paths.get(normalized).getFileName().toString().trim(); if (filename.isEmpty() || filename.length() > 300) { throw new DataException("文件名长度必须在 1 到 300 之间"); } return filename; } private void validate(String sourceId, String documentNo, String documentName, String documentType, String responsibleDeptId, String versionLabel, String preparedBy, LocalDate preparedOn, MultipartFile file) { if (file == null || file.isEmpty()) { throw new DataException("上传文件不能为空"); } requireText(sourceId, "sourceId", 160); requireText(documentNo, "文件编号", 200); requireText(documentName, "文件名称", 300); requireText(documentType, "文件类型", 100); requireText(responsibleDeptId, "归属部门", 100); requireText(versionLabel, "版本号", 50); requireText(preparedBy, "制定人", 100); if (preparedOn == null) { throw new DataException("制定日期不能为空"); } fileStorageService.getFileStorageVerify(STORAGE_PLATFORM); } private void validateRevision(UUID documentId, UUID baseFileVersionId, String sourceId, String versionLabel, String preparedBy, LocalDate preparedOn, String revisionReason, MultipartFile file) { if (documentId == null || baseFileVersionId == null) { throw new DataException("文件及当前版本不能为空"); } if (file == null || file.isEmpty()) { throw new DataException("修订文件不能为空"); } requireText(sourceId, "sourceId", 120); requireText(versionLabel, "新版本号", 50); requireText(preparedBy, "制定人", 100); requireText(revisionReason, "修订原因", 1000); if (preparedOn == null) { throw new DataException("制定日期不能为空"); } fileStorageService.getFileStorageVerify(STORAGE_PLATFORM); } private void requireText(String value, String label, int maxLength) { if (value == null || value.trim().isEmpty() || value.trim().length() > maxLength) { throw new DataException(label + "长度必须在 1 到 " + maxLength + " 之间"); } } private String trimToEmpty(String value) { return value == null ? "" : value.trim(); } private String trimToNull(String value) { return value == null || value.trim().isEmpty() ? null : value.trim(); } private void deleteQuietly(FileInfo stored) { if (stored == null) { return; } try { if (!fileStorageService.delete(stored)) { log.error("DMS 上传补偿删除失败: platform={}, path={}, filename={}", stored.getPlatform(), stored.getPath(), stored.getFilename()); } } catch (Exception exception) { log.error("DMS 上传补偿删除异常: platform={}, path={}, filename={}", stored.getPlatform(), stored.getPath(), stored.getFilename(), exception); } } }