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;
|
}
|
}
|