刘光辉
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package jnpf.limsService.impl;
 
import cn.hutool.core.util.IdUtil;
import jnpf.base.ActionResult;
import jnpf.base.ActionResultCode;
import jnpf.base.UserInfo;
import jnpf.eln.RecordFreezeApi;
import jnpf.eln.RecordFreezeCheckBatch;
import jnpf.eln.RecordFreezeCheckRequest;
import jnpf.eln.RecordFreezeCheckResult;
import jnpf.eln.RecordFreezeIssue;
import jnpf.eln.RecordFreezeReleaseRequest;
import jnpf.exception.DataException;
import jnpf.limsEntity.JianyanStatus;
import jnpf.limsEntity.LimsJianyanXiangEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.StringUtils;
 
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
/**
 * LIMS 的唯一 ELN 提交门禁。先完成本地权限/状态预检,再向 ELN 申请冻结;
 * 任何一项未就绪均在本地 UPDATE 前抛出,保证 Controller 事务零写入。
 */
@Slf4j
@Component
public class LimsElnRecordFreezeGate {
    private final RecordFreezeApi recordFreezeApi;
 
    public LimsElnRecordFreezeGate(RecordFreezeApi recordFreezeApi) {
        this.recordFreezeApi = recordFreezeApi;
    }
 
    public Map<String, SubmissionValues> prepareAndReleaseAfterCompletion(String tenantId, UserInfo user,
                                                                            List<String> testWorkItemIds,
                                                                            Map<String, LimsJianyanXiangEntity> rows) {
        String actualTenantId = StringUtils.hasText(tenantId) ? tenantId.trim() : "0";
        List<String> ids = uniqueIds(testWorkItemIds);
        List<String> localIssues = new ArrayList<>();
        if (ids.size() != testWorkItemIds.size()) localIssues.add("提交检验项存在重复或无效 id");
        localIssues.addAll(precheckLocal(ids, rows, user == null ? null : user.getUserId()));
        RecordFreezeCheckRequest request = new RecordFreezeCheckRequest();
        request.setTenantId(actualTenantId);
        request.setUserId(user.getUserId());
        request.setOperationCode(IdUtil.getSnowflakeNextIdStr());
        request.setTestWorkItemIds(ids);
        RecordFreezeCheckBatch batch;
        try {
            batch = preparedBatch(request);
        } catch (DataException e) {
            releaseBestEffort(actualTenantId, request.getOperationCode(), ids);
            throw e;
        } catch (RuntimeException e) {
            // 响应丢失时无法确认权威复算是否已提交;释放确定性 fence 并拒绝本次提交,
            // 避免同操作码自动重试产生重复计算日志和记录版本递增。
            releaseBestEffort(actualTenantId, request.getOperationCode(), ids);
            throw new DataException("ELN 冻结校验服务调用失败,已拒绝提交");
        }
        registerRelease(actualTenantId, request.getOperationCode(), ids);
        return validateBatch(ids, batch, localIssues);
    }
 
    private static List<String> precheckLocal(List<String> ids, Map<String, LimsJianyanXiangEntity> rows, String userId) {
        List<String> issues = new ArrayList<>();
        for (String id : ids) {
            LimsJianyanXiangEntity row = rows.get(id);
            if (row == null) {
                issues.add(id + ": 检验项不存在");
                continue;
            }
            String status = row.getJianyanZhuangtai();
            if (!JianyanStatus.PENDING_TEST.getCode().equals(status)
                    && !JianyanStatus.CANCELLED.getCode().equals(status)
                    && !JianyanStatus.AUDIT_REJECTED.getCode().equals(status)) {
                issues.add(id + ": 检验项状态不允许提交");
            }
            boolean external = "yes".equalsIgnoreCase(row.getShifouWaijian());
            String expectedUser = external ? row.getWaijianLururen() : row.getJianchaRen();
            if (!StringUtils.hasText(expectedUser) || !expectedUser.equals(userId)) {
                issues.add(id + ": 当前用户不是检验结果录入负责人");
            }
        }
        return issues;
    }
 
    private static Map<String, SubmissionValues> validateBatch(List<String> requestedIds,
                                                                 RecordFreezeCheckBatch batch,
                                                                 List<String> localIssues) {
        if (batch == null || batch.getItems() == null) throw new DataException("ELN 冻结校验响应无效,已拒绝提交");
        Map<String, RecordFreezeCheckResult> byId = new HashMap<>();
        List<String> issues = new ArrayList<>(localIssues);
        for (RecordFreezeCheckResult item : batch.getItems()) {
            if (item == null || !StringUtils.hasText(item.getTestWorkItemId()) || byId.put(item.getTestWorkItemId(), item) != null) {
                throw new DataException("ELN 冻结校验响应包含重复或无效检验项,已拒绝提交");
            }
        }
        if (byId.size() != requestedIds.size() || !byId.keySet().containsAll(requestedIds)) {
            throw new DataException("ELN 冻结校验响应缺少检验项,已拒绝提交");
        }
        for (String id : requestedIds) {
            RecordFreezeCheckResult item = byId.get(id);
            if (RecordFreezeCheckResult.NOT_APPLICABLE.equals(item.getStatus())) continue;
            if (!RecordFreezeCheckResult.READY.equals(item.getStatus()) || !StringUtils.hasText(item.getReleaseToken())) {
                if (item.getDiagnostics() == null || item.getDiagnostics().isEmpty()) {
                    issues.add(id + ": ELN 记录未准备就绪");
                } else {
                    for (RecordFreezeIssue issue : item.getDiagnostics()) {
                        issues.add(id + ": " + (issue == null || !StringUtils.hasText(issue.getMessage())
                                ? "ELN 记录未准备就绪" : issue.getMessage()));
                    }
                }
            }
        }
        if (!issues.isEmpty()) throw new DataException("ELN 可冻结校验失败:" + String.join(";", issues));
        Map<String, SubmissionValues> values = new HashMap<>();
        for (String id : requestedIds) {
            RecordFreezeCheckResult item = byId.get(id);
            if (RecordFreezeCheckResult.NOT_APPLICABLE.equals(item.getStatus())) continue;
            if (!StringUtils.hasText(item.getTestResult()) || !StringUtils.hasText(item.getTestConclusion())) {
                throw new DataException("ELN 可冻结校验响应缺少已校验的检验结果或结论,已拒绝提交");
            }
            values.put(id, new SubmissionValues(item.getTestResult().trim(),
                    item.getTestConclusion().trim()));
        }
        return values;
    }
 
    private void registerRelease(String tenantId, String operationCode, List<String> testWorkItemIds) {
        RecordFreezeReleaseRequest request = new RecordFreezeReleaseRequest();
        request.setTenantId(tenantId);
        request.setOperationCode(operationCode);
        request.setTestWorkItemIds(testWorkItemIds);
        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
            release(request);
            throw new DataException("LIMS 提交事务未激活,已释放 ELN 冻结");
        }
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCompletion(int status) {
                release(request);
            }
        });
    }
 
    private void release(RecordFreezeReleaseRequest request) {
        try {
            ActionResult<Boolean> response = recordFreezeApi.release(request);
            if (response == null || !ActionResultCode.SUCCESS.getCode().equals(response.getCode())
                    || !Boolean.TRUE.equals(response.getData())) {
                log.error("[submit-result] ELN 冻结释放失败 operation={}", request.getOperationCode());
            }
        } catch (RuntimeException e) {
            log.error("[submit-result] ELN 冻结释放异常 operation={}", request.getOperationCode(), e);
        }
    }
 
    private RecordFreezeCheckBatch preparedBatch(RecordFreezeCheckRequest request) {
        ActionResult<RecordFreezeCheckBatch> response = recordFreezeApi.prepare(request);
        if (response == null) throw new IllegalStateException("ELN 冻结校验服务无响应");
        if (!ActionResultCode.SUCCESS.getCode().equals(response.getCode()) || response.getData() == null) {
            throw new DataException(StringUtils.hasText(response.getMsg())
                    ? response.getMsg() : "ELN 冻结校验服务不可用,已拒绝提交");
        }
        return response.getData();
    }
 
    private void releaseBestEffort(String tenantId, String operationCode, List<String> testWorkItemIds) {
        RecordFreezeReleaseRequest request = new RecordFreezeReleaseRequest();
        request.setTenantId(tenantId);
        request.setOperationCode(operationCode);
        request.setTestWorkItemIds(testWorkItemIds);
        release(request);
    }
 
    private static List<String> uniqueIds(Collection<String> ids) {
        if (ids == null) return Collections.emptyList();
        Set<String> unique = new LinkedHashSet<>();
        for (String id : ids) if (StringUtils.hasText(id)) unique.add(id.trim());
        return new ArrayList<>(unique);
    }
 
    /** 仅 READY 的 ELN 项携带;LIMS 无需了解 ELN 字段目录。 */
    public static final class SubmissionValues {
        private final String testResult;
        private final String testConclusion;
 
        private SubmissionValues(String testResult, String testConclusion) {
            this.testResult = testResult;
            this.testConclusion = testConclusion;
        }
 
        public String getTestResult() {
            return testResult;
        }
 
        public String getTestConclusion() {
            return testConclusion;
        }
    }
}