刘光辉
8 小时以前 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
package jnpf.base.service;
 
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.digest.DigestUtil;
import jnpf.base.UserInfo;
import jnpf.base.model.uploadedit.*;
import jnpf.exception.DataException;
import jnpf.util.JsonUtil;
import jnpf.util.TicketUtil;
import jnpf.util.UserProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
 
/** 无稳定业务 ID 时仅保存短期打开凭据;回调不写内容、不替换表单附件、不提供恢复。 */
@Service
@RequiredArgsConstructor
public class UploadEditTemporaryService {
    private final UploadEditAccess access;
    private final DmsAuditTemporaryEditService revisionService;
    private final StringRedisTemplate redis;
 
    public void prepareRevisionSave(jnpf.base.model.flow.DataModel model) {
        revisionService.prepareSave(model);
    }
 
    public Map<String, Object> begin(UploadEditCommand command) {
        if (command.getFiles() != null && command.getFiles().size() == 1
                && command.getFiles().get(0).containsKey("revisionEditReceipt")) return revisionService.begin(command);
        UploadEditContext context = command.getContext();
        if (context == null || (context.getRecordId() != null && !context.getRecordId().isEmpty()
                && (context.getParentField() == null || context.getParentField().isEmpty() || (context.getRowId() != null && !context.getRowId().isEmpty())))) {
            throw new DataException("有跟踪 ID 的附件应使用工作稿编辑");
        }
        UserInfo user = UserProvider.getUser();
        access.authorizeTemporary(context, user.getUserId());
        if (command.getFiles() == null || command.getFiles().size() != 1) throw new DataException("请选择一个新上传的文件");
        Map<String, Object> requested = command.getFiles().get(0);
        String proofJson = requested.get("uploadReceipt") instanceof String ? TicketUtil.parseTicket((String) requested.get("uploadReceipt")) : null;
        Map<?, ?> proof = proofJson == null ? Collections.emptyMap() : JsonUtil.getJsonToBean(proofJson, Map.class);
        String tenant = UploadEditDraftService.tenant(user);
        if (!"upload-edit-source".equals(proof.get("purpose")) || !tenant.equals(proof.get("tenantId"))
                || !user.getUserId().equals(proof.get("ownerId")) || !Objects.equals(command.getFileId(), proof.get("fileId"))
                || !jnpf.base.util.UploadEditStateMachine.supportsEditing(proof.get("name"))) {
            throw new DataException("临时编辑仅支持本次新上传且归属可核验的 DOC、DOCX,请重新上传");
        }
        Map<String, Object> file = new LinkedHashMap<>();
        for (String key : Arrays.asList("fileId", "name", "fileSize", "fileExtension", "url", "contentSavedAt")) file.put(key, proof.get(key));
        jnpf.base.util.UploadTemplateSource.inherit(file, proof);
        if (access.isDmsAuditTemporary(context)) return revisionService.beginUpload(command, file);
        UploadEditLease lease = new UploadEditLease();
        lease.setLeaseId(IdUtil.simpleUUID()); lease.setDraftId(lease.getLeaseId());
        lease.setTenantId(tenant); lease.setOwnerId(user.getUserId()); lease.setTracked(false); lease.setFile(file);
        Map<String, Object> state = new LinkedHashMap<>();
        state.put("lease", lease); state.put("context", context);
        redis.opsForValue().set(key(tenant, lease.getLeaseId()), JsonUtil.getObjectToString(state), 4, TimeUnit.HOURS);
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("leaseId", lease.getLeaseId()); result.put("draftId", lease.getDraftId());
        result.put("source", file); result.put("files", Collections.singletonList(file)); result.put("revision", 0);
        result.put("canDiscard", false);
        return result;
    }
 
    /** 状态轮询不授予文件访问权;保留 Redis 到期/清理和租约归属检查。 */
    public boolean canReadStatus(String tenant, String owner, String leaseId) {
        if (revisionService.canReadStatus(tenant, owner, leaseId)) return true;
        String json = redis.opsForValue().get(key(tenant, leaseId));
        if (json == null) return false;
        Map<?, ?> state = JsonUtil.getJsonToBean(json, Map.class);
        UploadEditLease lease = JsonUtil.getJsonToBean(state.get("lease"), UploadEditLease.class);
        return lease != null && Objects.equals(tenant, lease.getTenantId()) && Objects.equals(owner, lease.getOwnerId())
                && Objects.equals(leaseId, lease.getLeaseId());
    }
 
    public UploadEditLease find(String tenant, String owner, String leaseId) {
        UploadEditLease revision = revisionService.find(tenant, owner, leaseId);
        if (revision != null) return revision;
        String json = redis.opsForValue().get(key(tenant, leaseId));
        if (json == null) return null;
        Map<?, ?> state = JsonUtil.getJsonToBean(json, Map.class);
        UploadEditLease lease = JsonUtil.getJsonToBean(state.get("lease"), UploadEditLease.class);
        if (!owner.equals(lease.getOwnerId())) throw new DataException("临时编辑会话不可访问");
        UploadEditContext context = JsonUtil.getJsonToBean(state.get("context"), UploadEditContext.class);
        access.authorizeTemporary(context, owner);
        lease.setAllowDownload(access.allowDownload(context));
        lease.setQianpiyeAnnotatorEnabled(access.qianpiyeAnnotatorEnabled(context));
        return lease;
    }
 
    /** 清理接口仅撤销本人的临时会话。无 ID 回调不落文件,因此无需删除源文件或历史文件。 */
    public void cleanup(String leaseId) {
        UserInfo user = UserProvider.getUser();
        String tenant = UploadEditDraftService.tenant(user);
        revisionService.cleanup(tenant, user.getUserId(), leaseId);
        String key = key(tenant, leaseId);
        String json = redis.opsForValue().get(key);
        if (json == null) return;
        Map<?, ?> state = JsonUtil.getJsonToBean(json, Map.class);
        UploadEditLease lease = JsonUtil.getJsonToBean(state.get("lease"), UploadEditLease.class);
        if (!user.getUserId().equals(lease.getOwnerId())) throw new DataException("临时编辑会话不可访问");
        redis.delete(key);
    }
    public boolean saveRevision(UploadEditSaveCommand command) { return revisionService.save(command); }
    public Map<String, Object> revisionResult(String leaseId) { return revisionService.result(leaseId); }
    private String key(String tenant, String leaseId) {
        if (tenant == null || leaseId == null || !leaseId.matches("[a-f0-9]{32}")) throw new DataException("临时编辑标识无效");
        return "upload-edit:temporary:" + DigestUtil.sha256Hex(tenant) + ":" + leaseId;
    }
}