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.DmsFileRevisionRegisterForm;
|
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 DmsFileRevisionService {
|
private final DmsVersionPropertiesService versionProperties;
|
private static final String SOURCE_PREFIX = "revision:";
|
|
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(DmsFileRevisionRegisterForm form, String serviceId) {
|
return register(form, serviceId, false, null, null);
|
}
|
|
@Transactional
|
public DmsFileUploadVO registerWorkflowPending(DmsFileRevisionRegisterForm form, String serviceId,
|
String flowId, String taskId) {
|
return register(form, serviceId, true, flowId, taskId);
|
}
|
|
/** Java-only entry: caller has locked and verified the complete approved batch. */
|
@Transactional
|
public DmsFileUploadVO registerApproved(DmsFileRevisionRegisterForm 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(DmsFileRevisionRegisterForm form, String serviceId,
|
boolean workflowPending, String flowId, String taskId) {
|
return register(form, serviceId, workflowPending, flowId, taskId, null);
|
}
|
|
private DmsFileUploadVO register(DmsFileRevisionRegisterForm 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();
|
if (approvedActor == null) decisionService.requireAllowed(DmsResourceType.DOCUMENT, form.getDocumentId(),
|
DmsPermissionAction.UPDATE);
|
if (approvedActor == null) businessOccupancy.guardStandaloneActivation(tenantId,form.getBaseFileVersionId());
|
|
fileObjectMapper.lockSource(tenantId, serviceId, sourceId);
|
DmsFileUploadVO existing = fileObjectMapper.selectUploadBySource(tenantId, serviceId, sourceId);
|
if (existing != null) {
|
versionProperties.verifyRetry(tenantId, existing.getFileVersionId(), form.getExtendedProperties());
|
validateRetry(existing, form, retryControlFlag(tenantId, form, approvedActor != null));
|
pdfGenerationTaskService.enqueueEligible(tenantId, form.getFileVersionId(),
|
form.getFileName(), form.getChecksumSha256());
|
if (!workflowPending) {
|
searchIndexJobService.enqueueContentUpsert(tenantId, existing.getDocumentId(),
|
existing.getFileVersionId(), existing.getChecksumSha256());
|
}
|
operationLogService.recordRevision(user, tenantId, existing.getDocumentId(),
|
existing.getFileVersionId(), existing.getFileName(), serviceId, sourceId);
|
return existing;
|
}
|
|
DmsDocumentEntity document = documentMapper.selectByTenantAndIdForUpdate(tenantId,
|
form.getDocumentId());
|
if (document == null) {
|
throw DmsPermissionException.of(DmsPermissionError.RESOURCE_NOT_FOUND, "待修订文件不存在");
|
}
|
DmsFileVersionEntity current = fileVersionMapper.selectCurrentEffective(tenantId,
|
form.getDocumentId());
|
if (current == null || !Objects.equals(current.getId(), form.getBaseFileVersionId())) {
|
throw DmsPermissionException.of(DmsPermissionError.REVISION_CONFLICT,
|
"待修订版本已不是现行有效版本,请刷新后重试");
|
}
|
DmsVersionStateAuditRow currentState = versionStateMapper.selectAuditState(
|
tenantId, current.getId());
|
String folderPath = folderMapper.selectPath(tenantId, document.getFolderId());
|
DmsFileAuditSnapshot before = snapshot(document, current, currentState, folderPath);
|
|
long nextVersionNo = fileVersionMapper.selectNextVersionNo(tenantId, form.getDocumentId());
|
try {
|
DmsFileVersionEntity newVersion = insertFileVersion(form, tenantId, user.getUserId(), nextVersionNo,
|
approvedActor == null
|
? inheritedControlFlag(current.getShifouShoukong())
|
: requireApprovedControlFlag(form.getShifouShoukong()));
|
insertRegistry(form, tenantId, serviceId, sourceId);
|
insertFileObject(form, tenantId, user.getUserId(), serviceId, sourceId);
|
pdfGenerationTaskService.enqueueEligible(tenantId, form.getFileVersionId(),
|
form.getFileName(), form.getChecksumSha256());
|
if (workflowPending) {
|
versionStateMapper.insertWorkflowPending(tenantId, form.getDocumentId(),
|
form.getFileVersionId(), SOURCE_PREFIX + form.getBaseFileVersionId() + ":" + sourceId,
|
flowId, taskId, "REVISE", user.getUserId());
|
} else {
|
int superseded = versionStateMapper.supersedeCurrent(tenantId, form.getDocumentId(),
|
form.getBaseFileVersionId(), form.getVersionLabel().trim(), user.getUserId());
|
if (superseded != 1) {
|
throw DmsPermissionException.of(DmsPermissionError.REVISION_CONFLICT,
|
"现行有效版本已发生变化,请刷新后重试");
|
}
|
versionStateMapper.insertRevisionEffective(tenantId, form.getDocumentId(),
|
form.getBaseFileVersionId(), form.getFileVersionId(), sourceId,
|
form.getRevisionReason().trim(), user.getUserId());
|
searchIndexJobService.enqueueContentUpsert(tenantId, form.getDocumentId(),
|
form.getFileVersionId(), form.getChecksumSha256());
|
}
|
DmsVersionStateAuditRow previousAfter = versionStateMapper.selectAuditState(
|
tenantId, current.getId());
|
DmsVersionStateAuditRow newState = versionStateMapper.selectAuditState(
|
tenantId, newVersion.getId());
|
auditAdapter.fileChange(DmsFileAuditEvent.builder()
|
.documentId(form.getDocumentId())
|
.actionCode("UPDATE")
|
.actionLabel(workflowPending ? "暂存待审批 DMS 修订" : "修订 DMS 文件")
|
.before(before)
|
.after(snapshot(document, newVersion, newState, folderPath))
|
.previousVersionAfter(versionSnapshot(current, previousAfter,
|
workflowPending ? "EFFECTIVE" : "SUPERSEDED"))
|
.reason(form.getRevisionReason().trim())
|
.sourceServiceId(serviceId)
|
.sourceId(sourceId)
|
.relatedSigns(DmsAuditRequestSignResolver.resolve(form.getAuditSignParameters()))
|
.build());
|
} catch (DuplicateKeyException exception) {
|
throw conflict("修订登记的资源 ID、source_id、版本号、版本标签或对象键已被占用", exception);
|
}
|
|
operationLogService.recordRevision(user, tenantId, form.getDocumentId(),
|
form.getFileVersionId(), form.getFileName(), serviceId, sourceId);
|
return fileObjectMapper.selectUploadBySource(tenantId, serviceId, sourceId);
|
}
|
|
private DmsFileVersionEntity insertFileVersion(DmsFileRevisionRegisterForm form, String tenantId,
|
String userId, long versionNo, String shifouShoukong) {
|
DmsFileVersionEntity version = new DmsFileVersionEntity();
|
version.setId(form.getFileVersionId());
|
version.setTenantId(tenantId);
|
version.setDocumentId(form.getDocumentId());
|
version.setVersionNo(versionNo);
|
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());
|
return version;
|
}
|
|
private void insertRegistry(DmsFileRevisionRegisterForm form, String tenantId,
|
String serviceId, String sourceId) {
|
DmsPermissionResourceEntity registry = new DmsPermissionResourceEntity();
|
registry.setId(form.getFileVersionId());
|
registry.setTenantId(tenantId);
|
registry.setResourceType(DmsResourceType.FILE_VERSION.name());
|
registry.setSourceType("SYSTEM");
|
registry.setSourceId(SOURCE_PREFIX + sourceId + ":version");
|
registry.setRegisteredBy(serviceId);
|
resourceMapper.insert(registry);
|
}
|
|
private void insertFileObject(DmsFileRevisionRegisterForm 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 validateRetry(DmsFileUploadVO existing, DmsFileRevisionRegisterForm form,
|
String shifouShoukong) {
|
if (!Objects.equals(existing.getDocumentId(), form.getDocumentId())
|
|| !Objects.equals(existing.getBaseFileVersionId(), form.getBaseFileVersionId())
|
|| !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)
|
|| !Objects.equals(existing.getRevisionReason(), form.getRevisionReason().trim())) {
|
throw DmsPermissionException.of(DmsPermissionError.IDEMPOTENCY_CONFLICT,
|
"source_id 对应的修订内容与本次请求不一致");
|
}
|
}
|
|
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 retryControlFlag(String tenantId, DmsFileRevisionRegisterForm form, boolean approved) {
|
if (approved) return requireApprovedControlFlag(form.getShifouShoukong());
|
DmsFileVersionEntity base = fileVersionMapper.selectByTenantAndId(tenantId,
|
form.getBaseFileVersionId());
|
if (base == null || !Objects.equals(base.getDocumentId(), form.getDocumentId())) {
|
throw DmsPermissionException.of(DmsPermissionError.REVISION_CONFLICT,
|
"修订基准版本不存在或不属于目标文件");
|
}
|
return inheritedControlFlag(base.getShifouShoukong());
|
}
|
|
private String inheritedControlFlag(String inherited) {
|
return "no".equals(inherited) ? "no" : "yes";
|
}
|
|
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 DmsFileAuditSnapshot snapshot(DmsDocumentEntity document, DmsFileVersionEntity version,
|
DmsVersionStateAuditRow state, String folderPath) {
|
return DmsFileAuditSnapshot.builder()
|
.documentId(document.getId())
|
.documentNo(document.getDocumentNo())
|
.documentName(document.getName())
|
.documentType(document.getDocumentType())
|
.folderId(document.getFolderId())
|
.folderPath(folderPath)
|
.responsibleDeptId(document.getResponsibleDeptId())
|
.currentVersion(versionSnapshot(version, state, "EFFECTIVE"))
|
.build();
|
}
|
|
private DmsFileVersionAuditSnapshot versionSnapshot(DmsFileVersionEntity version,
|
DmsVersionStateAuditRow state,
|
String fallbackStatus) {
|
return 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 ? null : state.getStatusReason())
|
.build();
|
}
|
|
private DmsPermissionException conflict(String message, Throwable cause) {
|
DmsPermissionException exception = DmsPermissionException.of(DmsPermissionError.SOURCE_CONFLICT, message);
|
exception.initCause(cause);
|
return exception;
|
}
|
}
|