刘光辉
4 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
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
package jnpf.dmsService;
 
import jnpf.audit.sdk.annotation.AuditLog;
import jnpf.dmsEntity.storage.approval.DmsDisableRequest;
import jnpf.dmsEntity.storage.approval.DmsApprovedBatch;
import jnpf.dmsMapper.DmsApprovedBatchMapper;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import jnpf.dmsMapper.DmsApprovedDisableMapper;
import jnpf.dmsMapper.DmsWenjianZiyuanMapper;
import jnpf.exception.DataException;
import jnpf.flowable.WorkFlowApi;
import jnpf.flowable.entity.TaskEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
 
/** Short local transactions only. Native forms are read, never created or replaced here. */
@Service
@RequiredArgsConstructor
public class DmsApprovedDisableService {
    private final DmsApprovedDisableMapper mapper;
    private final DmsWenjianZiyuanMapper resources;
    private final WorkFlowApi workflow;
    private final DmsApprovedBatchMapper batches;
    @Value("${dms.approved-disable.enabled:false}") private boolean enabled;
 
    @Transactional(rollbackFor = Exception.class)
    @AuditLog(action="DMS_QUEUE_APPROVED_DISABLE", bizCodeExpr="#taskId")
    public DmsApprovedBatch enqueue(String tenantId, String flowId, String taskId, String operation) {
        requireEnabled(); requireOperation(operation);
        TaskEntity task = approved(flowId, taskId);
        List<DmsDisableRequest> rows = new ArrayList<>(mapper.nativeRows(tenantId, flowId, taskId, operation));
        if (rows.isEmpty()) throw new DataException("未找到已批准的原生停用申请明细");
        Set<String> files = new HashSet<>();
        Set<String> applications = new HashSet<>();
        rows.sort(Comparator.comparing(DmsDisableRequest::getSourceRowId));
        for (DmsDisableRequest row : rows) {
            applications.add(row.getApplicationId());
            if (!files.add(row.getFileId()) || row.getFileId()==null || row.getBusinessVersionId()==null
                    || row.getDocumentId()==null || row.getFileVersionId()==null || row.getFolderId()==null)
                throw new DataException("停用明细重复或缺少真实文件/版本关联,请先发布并补全表单");
            row.setId(UUID.randomUUID()); row.setApprovedAt(task.getEndTime());
            // getInfoSubmit only returns workflow identity/status; nativeRows resolves its persisted actor.
            if (row.getActorId()==null || row.getActorId().isBlank()) throw new DataException("批准任务缺少操作人");
            row.setScheduledAt(DmsApprovedFileDates.disableScheduled(row.getRequestedAt(), task.getEndTime()));
        }
        if (applications.size()!=1) throw new DataException("流程任务关联了多个停用申请");
        String auditType = auditType(operation);
        String digest = digest(rows);
        DmsApprovedBatch existing = batches.find(tenantId, taskId, auditType);
        if (existing != null) {
            if (!Objects.equals(existing.getSourceDigest(), digest)
                    || !Objects.equals(existing.getApprovedAt(), task.getEndTime())
                    || !Objects.equals(existing.getFlowId(), flowId))
                throw new DataException("批准后的停用来源已改变,停止自动处理");
            return existing;
        }
        DmsApprovedBatch batch = new DmsApprovedBatch();
        batch.setId(UUID.randomUUID()); batch.setTenantId(tenantId); batch.setFlowId(flowId);
        batch.setTaskId(taskId); batch.setAuditType(auditType); batch.setAuditId(rows.get(0).getApplicationId());
        batch.setSourceDigest(digest); batch.setActorId(rows.get(0).getActorId());
        batch.setApprovedAt(task.getEndTime()); batch.setStatus("WAITING");
        batch.setScheduledAt(rows.stream().map(DmsDisableRequest::getScheduledAt).min(Date::compareTo).orElseThrow());
        batches.insertBatch(batch);
        for (DmsDisableRequest row : rows) {
            row.setBatchId(batch.getId());
            resources.selectByIdForUpdate(tenantId, row.getFileId());
            validateTarget(row);
            mapper.enqueue(row);
        }
        return batch;
    }
 
    // Length prefixes preserve nulls and separators in native IDs; row order is canonical.
    private static String digest(List<DmsDisableRequest> rows) {
        StringBuilder source = new StringBuilder();
        for (DmsDisableRequest row : rows) {
            for (Object value : Arrays.asList(row.getSourceRowId(), row.getApplicationId(), row.getFileId(),
                    row.getBusinessVersionId(), row.getDocumentId(), row.getFileVersionId(), row.getFolderId(),
                    row.getSourceFileId(), row.getSourceVersionId(),
                    row.getRequestedAt() == null ? null : row.getRequestedAt().getTime())) {
                String text = value == null ? null : value.toString();
                source.append(text == null ? -1 : text.length()).append(':');
                if (text != null) source.append(text);
            }
        }
        try { return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(source.toString().getBytes(StandardCharsets.UTF_8))); }
        catch (NoSuchAlgorithmException impossible) { throw new IllegalStateException(impossible); }
    }
 
    static String auditType(String operation) {
        requireOperation(operation);
        return "yichexiao".equals(operation) ? "chexiao_shenqing" : "shixiao_shenqing";
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void refreshProgress(DmsApprovedBatch batch) {
        requireEnabled();
        if (mapper.refreshProgress(batch) != 1) throw new DataException("统一任务进度更新失败或租约已失效");
    }
 
    private void validateTarget(DmsDisableRequest row) {
        if (mapper.validateTarget(row)!=1) throw new DataException("业务文件版本或有效状态已改变,不能登记原停用申请");
        if ("yishixiao".equals(row.getOperation()) && mapper.candidateExists(row)<1)
            throw new DataException("该文件不是已生效新增文件检出的同名旧文件");
    }
    private TaskEntity approved(String flowId,String taskId) {
        TaskEntity task=workflow.getInfoSubmit(taskId);
        if (task==null || !taskId.equals(task.getId()) || !Integer.valueOf(2).equals(task.getStatus())
                || task.getEndTime()==null || (!flowId.equals(task.getFlowId()) && !flowId.equals(task.getTemplateId())))
            throw new DataException("文件停用必须先通过真实工作流审批");
        return task;
    }
    static void requireOperation(String operation) {
        if (!"yichexiao".equals(operation) && !"yishixiao".equals(operation)) throw new DataException("文件停用类型无效");
    }
    private void requireEnabled() {
        if (!enabled) throw new DataException("停用推送尚未启用:需先完成技术迁移和原生流程接入");
    }
}