package jnpf.dmsService;
|
|
import jnpf.base.UserInfo;
|
import jnpf.dmsEntity.permission.entity.DmsDocumentEntity;
|
import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity;
|
import jnpf.dmsEntity.permission.entity.DmsPermissionResourceEntity;
|
import jnpf.dmsEntity.permission.enums.DmsPermissionAction;
|
import jnpf.dmsEntity.permission.enums.DmsResourceType;
|
import jnpf.dmsEntity.storage.entity.DmsFileObjectEntity;
|
import jnpf.dmsEntity.storage.form.DmsFileUploadRegisterForm;
|
import jnpf.dmsEntity.storage.vo.DmsFileUploadVO;
|
import jnpf.dmsIntegration.audit.DmsAuditAdapter;
|
import jnpf.dmsIntegration.audit.DmsAuditRequestSignResolver;
|
import jnpf.dmsIntegration.audit.DmsFileAuditEvent;
|
import jnpf.dmsIntegration.audit.DmsFileAuditSnapshot;
|
import jnpf.dmsIntegration.audit.DmsFileVersionAuditSnapshot;
|
import jnpf.dmsIntegration.audit.DmsVersionStateAuditRow;
|
import jnpf.dmsMapper.DmsDocumentMapper;
|
import jnpf.dmsMapper.DmsFolderMapper;
|
import jnpf.dmsMapper.DmsFileObjectMapper;
|
import jnpf.dmsMapper.DmsFileVersionMapper;
|
import jnpf.dmsMapper.DmsPermissionResourceMapper;
|
import jnpf.dmsMapper.DmsVersionStateMapper;
|
import jnpf.dmsPermission.DmsCurrentIdentity;
|
import jnpf.dmsPermission.DmsPermissionError;
|
import jnpf.dmsPermission.DmsPermissionException;
|
import jnpf.dmsService.search.DmsSearchIndexJobService;
|
import jnpf.dmsService.pdf.DmsPdfGenerationTaskService;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.util.Locale;
|
import java.util.Objects;
|
|
@Service
|
@RequiredArgsConstructor
|
public class DmsFileUploadService {
|
private final DmsVersionPropertiesService versionProperties;
|
private static final String SOURCE_PREFIX = "upload:";
|
|
private final DmsCurrentIdentity currentIdentity;
|
private final DmsPermissionDecisionService decisionService;
|
private final DmsDocumentMapper documentMapper;
|
private final DmsFolderMapper folderMapper;
|
private final DmsFileVersionMapper fileVersionMapper;
|
private final DmsPermissionResourceMapper resourceMapper;
|
private final DmsFileObjectMapper fileObjectMapper;
|
private final DmsVersionStateMapper versionStateMapper;
|
private final DmsAuditAdapter auditAdapter;
|
private final DmsFileOperationLogService operationLogService;
|
private final DmsSearchIndexJobService searchIndexJobService;
|
private final DmsPdfGenerationTaskService pdfGenerationTaskService;
|
private final DmsBusinessLifecycleGuard businessOccupancy;
|
|
@Transactional
|
public DmsFileUploadVO register(DmsFileUploadRegisterForm form, String serviceId) {
|
return register(form, serviceId, false);
|
}
|
|
@Transactional
|
public DmsFileUploadVO registerWorkflowPending(DmsFileUploadRegisterForm form, String serviceId,
|
String flowId, String taskId) {
|
return register(form, serviceId, true, flowId, taskId);
|
}
|
|
private DmsFileUploadVO register(DmsFileUploadRegisterForm form, String serviceId,
|
boolean workflowPending) {
|
return register(form, serviceId, workflowPending, null, null);
|
}
|
|
/** Java-only entry: caller has locked and verified the complete approved batch. */
|
@Transactional
|
public DmsFileUploadVO registerApproved(DmsFileUploadRegisterForm form, String serviceId,
|
String flowId, String taskId, UserInfo actor) {
|
if (!"jnpf-file".equals(serviceId) || actor == null || actor.getUserId() == null
|
|| actor.getTenantId() == null || flowId == null || taskId == null)
|
throw new jnpf.exception.DataException("批准登记缺少可信来源");
|
return register(form, serviceId, true, flowId, taskId, actor);
|
}
|
|
private DmsFileUploadVO register(DmsFileUploadRegisterForm form, String serviceId,
|
boolean workflowPending, String flowId, String taskId) {
|
return register(form, serviceId, workflowPending, flowId, taskId, null);
|
}
|
|
private DmsFileUploadVO register(DmsFileUploadRegisterForm form, String serviceId,
|
boolean workflowPending, String flowId, String taskId, UserInfo approvedActor) {
|
UserInfo user = approvedActor == null ? currentIdentity.requireUser() : approvedActor;
|
String tenantId = currentIdentity.tenantId(user);
|
String sourceId = form.getSourceId().trim();
|
String shifouShoukong = approvedActor == null
|
? "yes" : requireApprovedControlFlag(form.getShifouShoukong());
|
if (approvedActor == null) decisionService.requireAllowed(DmsResourceType.FOLDER, form.getFolderId(), DmsPermissionAction.CREATE);
|
if (approvedActor == null) businessOccupancy.guardStandaloneRegistration(tenantId,
|
form.getDocumentNo()==null ? null : form.getDocumentNo().trim());
|
|
DmsFileUploadVO existing = fileObjectMapper.selectUploadBySource(tenantId, serviceId, sourceId);
|
if (existing != null) {
|
versionProperties.verifyRetry(tenantId, existing.getFileVersionId(), form.getExtendedProperties());
|
validateRetry(existing, form, shifouShoukong);
|
pdfGenerationTaskService.enqueueEligible(tenantId, form.getFileVersionId(),
|
form.getFileName(), form.getChecksumSha256());
|
if (!workflowPending) {
|
searchIndexJobService.enqueueContentUpsert(tenantId, existing.getDocumentId(),
|
existing.getFileVersionId(), existing.getChecksumSha256());
|
}
|
operationLogService.recordUpload(user, tenantId, existing.getDocumentId(),
|
existing.getFileVersionId(), existing.getFileName(), serviceId, sourceId);
|
return existing;
|
}
|
|
try {
|
DmsDocumentEntity document = insertDocument(form, tenantId, user.getUserId(), serviceId, sourceId);
|
DmsFileVersionEntity version = insertFileVersion(form, tenantId, user.getUserId(), serviceId,
|
sourceId, shifouShoukong);
|
if (workflowPending) {
|
versionStateMapper.insertWorkflowPending(tenantId, form.getDocumentId(),
|
form.getFileVersionId(), sourceId, flowId, taskId, "CREATE", user.getUserId());
|
} else {
|
insertDirectEffectiveState(form, tenantId, user.getUserId());
|
}
|
insertFileObject(form, tenantId, user.getUserId(), serviceId, sourceId);
|
pdfGenerationTaskService.enqueueEligible(tenantId, form.getFileVersionId(),
|
form.getFileName(), form.getChecksumSha256());
|
if (!workflowPending) {
|
searchIndexJobService.enqueueContentUpsert(tenantId, form.getDocumentId(),
|
form.getFileVersionId(), form.getChecksumSha256());
|
}
|
DmsVersionStateAuditRow state = versionStateMapper.selectAuditState(
|
tenantId, form.getFileVersionId());
|
auditAdapter.fileChange(DmsFileAuditEvent.builder()
|
.documentId(form.getDocumentId())
|
.actionCode("CREATE")
|
.actionLabel(workflowPending ? "暂存待审批 DMS 文件" : "上传 DMS 文件")
|
.after(snapshot(document, version, state,
|
folderMapper.selectPath(tenantId, form.getFolderId()),
|
workflowPending ? "PENDING_APPROVAL" : "EFFECTIVE"))
|
.sourceServiceId(serviceId)
|
.sourceId(sourceId)
|
.relatedSigns(DmsAuditRequestSignResolver.resolve(form.getAuditSignParameters()))
|
.build());
|
} catch (DuplicateKeyException exception) {
|
throw conflict("上传登记的资源 ID、source_id、版本号或对象键已被占用", exception);
|
}
|
|
operationLogService.recordUpload(user, tenantId, form.getDocumentId(),
|
form.getFileVersionId(), form.getFileName(), serviceId, sourceId);
|
return fileObjectMapper.selectUploadBySource(tenantId, serviceId, sourceId);
|
}
|
|
private DmsDocumentEntity insertDocument(DmsFileUploadRegisterForm form, String tenantId, String userId,
|
String serviceId, String sourceId) {
|
DmsDocumentEntity document = new DmsDocumentEntity();
|
document.setId(form.getDocumentId());
|
document.setTenantId(tenantId);
|
document.setFolderId(form.getFolderId());
|
document.setDocumentNo(form.getDocumentNo().trim());
|
document.setName(form.getDocumentName().trim());
|
document.setDocumentType(form.getDocumentType().trim());
|
document.setResponsibleDeptId(form.getResponsibleDeptId().trim());
|
document.setCreatedBy(userId);
|
documentMapper.insert(document);
|
insertRegistry(tenantId, form.getDocumentId(), DmsResourceType.DOCUMENT,
|
registrySource(sourceId, "document"), serviceId);
|
return document;
|
}
|
|
private DmsFileVersionEntity insertFileVersion(DmsFileUploadRegisterForm form, String tenantId, String userId,
|
String serviceId, String sourceId, String shifouShoukong) {
|
DmsFileVersionEntity version = new DmsFileVersionEntity();
|
version.setId(form.getFileVersionId());
|
version.setTenantId(tenantId);
|
version.setDocumentId(form.getDocumentId());
|
version.setVersionNo(1L);
|
version.setVersionLabel(form.getVersionLabel().trim());
|
version.setFileName(form.getFileName().trim());
|
version.setMimeType(trimToNull(form.getMimeType()));
|
version.setSizeBytes(form.getSizeBytes());
|
version.setChecksumSha256(form.getChecksumSha256().toLowerCase(Locale.ROOT));
|
version.setPreparedBy(form.getPreparedBy().trim());
|
version.setPreparedOn(form.getPreparedOn());
|
version.setShifouShoukong(shifouShoukong);
|
version.setCreatedBy(userId);
|
versionProperties.insertVersion(version, form.getExtendedProperties());
|
insertRegistry(tenantId, form.getFileVersionId(), DmsResourceType.FILE_VERSION,
|
registrySource(sourceId, "version:1"), serviceId);
|
return version;
|
}
|
|
private void insertFileObject(DmsFileUploadRegisterForm form, String tenantId, String userId,
|
String serviceId, String sourceId) {
|
DmsFileObjectEntity object = new DmsFileObjectEntity();
|
object.setId(form.getFileObjectId());
|
object.setTenantId(tenantId);
|
object.setFileVersionId(form.getFileVersionId());
|
object.setStoragePlatform(form.getStoragePlatform().trim());
|
object.setBucketName(form.getBucketName().trim());
|
object.setBasePath(trimToEmpty(form.getBasePath()));
|
object.setObjectPath(form.getObjectPath().trim());
|
object.setObjectName(form.getObjectName().trim());
|
object.setSourceServiceId(serviceId);
|
object.setSourceId(sourceId);
|
object.setCreatedBy(userId);
|
fileObjectMapper.insert(object);
|
}
|
|
private void insertDirectEffectiveState(DmsFileUploadRegisterForm form, String tenantId,
|
String userId) {
|
versionStateMapper.insertDirectEffective(tenantId, form.getDocumentId(),
|
form.getFileVersionId(), userId);
|
}
|
|
private DmsFileAuditSnapshot snapshot(DmsDocumentEntity document, DmsFileVersionEntity version,
|
DmsVersionStateAuditRow state, String folderPath,
|
String fallbackStatus) {
|
return DmsFileAuditSnapshot.builder()
|
.documentId(document.getId())
|
.documentNo(document.getDocumentNo())
|
.documentName(document.getName())
|
.documentType(document.getDocumentType())
|
.folderId(document.getFolderId())
|
.folderPath(folderPath)
|
.responsibleDeptId(document.getResponsibleDeptId())
|
.currentVersion(DmsFileVersionAuditSnapshot.builder()
|
.fileVersionId(version.getId())
|
.versionNo(version.getVersionNo())
|
.versionLabel(version.getVersionLabel())
|
.fileName(version.getFileName())
|
.mimeType(version.getMimeType())
|
.sizeBytes(version.getSizeBytes())
|
.checksumSha256(version.getChecksumSha256())
|
.preparedBy(version.getPreparedBy())
|
.preparedOn(version.getPreparedOn())
|
.lifecycleStatus(state == null ? fallbackStatus : state.getLifecycleStatus())
|
.effectiveFrom(state == null ? null : state.getEffectiveFrom())
|
.effectiveTo(state == null ? null : state.getEffectiveTo())
|
.statusReason(state == null ? ("PENDING_APPROVAL".equals(fallbackStatus)
|
? "工作流文件已暂存,等待审批" : "DMS 临时上传直接生效")
|
: state.getStatusReason())
|
.build())
|
.build();
|
}
|
|
private void insertRegistry(String tenantId, java.util.UUID id, DmsResourceType type,
|
String sourceId, String serviceId) {
|
DmsPermissionResourceEntity registry = new DmsPermissionResourceEntity();
|
registry.setId(id);
|
registry.setTenantId(tenantId);
|
registry.setResourceType(type.name());
|
registry.setSourceType("SYSTEM");
|
registry.setSourceId(sourceId);
|
registry.setRegisteredBy(serviceId);
|
resourceMapper.insert(registry);
|
}
|
|
private void validateRetry(DmsFileUploadVO existing, DmsFileUploadRegisterForm form,
|
String shifouShoukong) {
|
if (!Objects.equals(existing.getFolderId(), form.getFolderId())
|
|| !Objects.equals(existing.getDocumentNo(), form.getDocumentNo().trim())
|
|| !Objects.equals(existing.getDocumentName(), form.getDocumentName().trim())
|
|| !Objects.equals(existing.getDocumentType(), form.getDocumentType().trim())
|
|| !Objects.equals(existing.getResponsibleDeptId(), form.getResponsibleDeptId().trim())
|
|| !Objects.equals(existing.getVersionLabel(), form.getVersionLabel().trim())
|
|| !Objects.equals(existing.getFileName(), form.getFileName().trim())
|
|| !Objects.equals(existing.getMimeType(), trimToNull(form.getMimeType()))
|
|| !Objects.equals(existing.getSizeBytes(), form.getSizeBytes())
|
|| !equalsIgnoreCase(existing.getChecksumSha256(), form.getChecksumSha256())
|
|| !Objects.equals(existing.getPreparedBy(), form.getPreparedBy().trim())
|
|| !Objects.equals(existing.getPreparedOn(), form.getPreparedOn())
|
|| !Objects.equals(existing.getShifouShoukong(), shifouShoukong)) {
|
throw DmsPermissionException.of(DmsPermissionError.IDEMPOTENCY_CONFLICT,
|
"source_id 对应的上传内容与本次请求不一致");
|
}
|
}
|
|
private String registrySource(String sourceId, String suffix) {
|
return SOURCE_PREFIX + sourceId + ":" + suffix;
|
}
|
|
private String trimToNull(String value) {
|
return value == null || value.trim().isEmpty() ? null : value.trim();
|
}
|
|
private String trimToEmpty(String value) {
|
return value == null ? "" : value.trim();
|
}
|
|
private String requireApprovedControlFlag(String value) {
|
if ("yes".equals(value) || "no".equals(value)) return value;
|
throw new jnpf.exception.DataException("批准文件受控属性必须为 yes 或 no");
|
}
|
|
private boolean equalsIgnoreCase(String left, String right) {
|
return left == null ? right == null : right != null && left.equalsIgnoreCase(right);
|
}
|
|
private DmsPermissionException conflict(String message, Throwable cause) {
|
DmsPermissionException exception = DmsPermissionException.of(DmsPermissionError.SOURCE_CONFLICT, message);
|
exception.initCause(cause);
|
return exception;
|
}
|
}
|