package jnpf.bizcommon.onlyoffice.service.impl;
|
|
import cn.hutool.crypto.digest.DigestUtil;
|
import cn.hutool.jwt.JWTUtil;
|
import jnpf.base.ActionResult;
|
import jnpf.base.ActionResultCode;
|
import jnpf.base.model.uploadedit.UploadEditLease;
|
import jnpf.base.model.uploadedit.UploadEditSaveCommand;
|
import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeCallbackParam;
|
import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeSessionEntity;
|
import jnpf.exception.DataException;
|
import jnpf.onlinedev.UploadEditApi;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Component;
|
import java.util.Map;
|
import java.util.Objects;
|
|
@Component
|
@RequiredArgsConstructor
|
public class UploadEditSessionSupport {
|
private final UploadEditApi api;
|
private final UploadEditStorage storage;
|
|
public static boolean matches(OnlyOfficeSessionEntity session) {
|
return session != null && UploadEditSceneConfigBuilder.SCENE.equals(session.getBizScene())
|
&& UploadEditSceneConfigBuilder.MODULE.equals(session.getBizModule());
|
}
|
|
public void requireStatusAccess(String tenantId, String ownerId, String leaseId) {
|
if (!Boolean.TRUE.equals(require(api.canReadStatus(tenantId, ownerId, leaseId)))) {
|
throw new DataException("编辑租约已失效或不可访问");
|
}
|
}
|
|
public UploadEditLease lease(String tenantId, String ownerId, String leaseId) {
|
UploadEditLease lease = require(api.lease(tenantId, ownerId, leaseId));
|
if (lease == null || lease.getFile() == null) throw new DataException("附件编辑租约不可用");
|
return lease;
|
}
|
|
public void save(OnlyOfficeSessionEntity session, OnlyOfficeCallbackParam callback, byte[] candidate) {
|
// 回调到达时重新检查当前流程权限,不能复用打开时的权限结论。
|
UploadEditLease lease = lease(session.getTenantId(), session.getLockUserId(), session.getFileId());
|
// 普通未保存附件维持原行为;只有服务端核验的DMS修订来源可另存临时副本。
|
if (!lease.isTracked() && !lease.isRevisionTemporary()) return;
|
String operationId = DigestUtil.sha256Hex(callback.getKey() + "\n" + callback.getStatus() + "\n" + callback.getUrl());
|
if (operationId.equals(lease.getLastOperationId())) return;
|
Map<String, Object> file = storage.save(session.getTenantId(), session.getFileId(), savedFileName(session.getFileName(), callback.getFiletype()), candidate);
|
UploadEditSaveCommand command = new UploadEditSaveCommand();
|
command.setTenantId(session.getTenantId());
|
command.setOwnerId(lease.getOwnerId());
|
command.setLeaseId(lease.getLeaseId());
|
command.setOperationId(operationId);
|
command.setFile(file);
|
if (!Boolean.TRUE.equals(require(api.save(command)))) throw new DataException("附件已替换、放弃或采用,本次编辑产物不再生效");
|
}
|
|
static String savedFileName(String sourceName, String outputType) {
|
boolean sourceDoc = sourceName != null && sourceName.toLowerCase(java.util.Locale.ROOT).endsWith(".doc");
|
// DOC 可能由 Document Server 转成 DOCX;缺少实际输出类型时不能冒用源后缀。
|
if (outputType == null || outputType.isBlank()) {
|
if (sourceDoc) throw new DataException("DOC 编辑产物缺少输出格式,无法安全保存");
|
return sourceName;
|
}
|
String extension = outputType.toLowerCase(java.util.Locale.ROOT);
|
if ((!"docx".equals(extension) && !(sourceDoc && "doc".equals(extension))) || sourceName == null) {
|
throw new DataException("附件编辑产物格式不受支持");
|
}
|
if (sourceName.toLowerCase(java.util.Locale.ROOT).endsWith("." + extension)) return sourceName;
|
return sourceName.substring(0, sourceName.lastIndexOf('.')) + "." + extension;
|
}
|
|
/** 已经过签名验证;进一步绑定请求体,拒绝拿编辑器配置 JWT 冒充保存回调。 */
|
public static void requireBoundCallback(OnlyOfficeCallbackParam callback, String authorization) {
|
String token = authorization == null || authorization.isBlank() ? callback.getToken()
|
: authorization.startsWith("Bearer ") ? authorization.substring(7).trim() : authorization.trim();
|
Object payload = JWTUtil.parseToken(token).getPayload("payload");
|
Map<?, ?> claims = payload instanceof Map ? (Map<?, ?>) payload : JWTUtil.parseToken(token).getPayloads();
|
if (!Objects.equals(callback.getKey(), claims.get("key"))
|
|| !(claims.get("status") instanceof Number) || !Objects.equals(callback.getStatus(), ((Number) claims.get("status")).intValue())
|
|| !Objects.equals(callback.getUrl(), claims.get("url"))
|
|| !Objects.equals(callback.getFiletype(), claims.get("filetype"))) {
|
throw new DataException("保存回调与已签名内容不一致");
|
}
|
}
|
|
private static <T> T require(ActionResult<T> result) {
|
if (result == null || !ActionResultCode.SUCCESS.getCode().equals(result.getCode())) {
|
throw new DataException(result == null ? "附件编辑服务不可用" : result.getMsg());
|
}
|
return result.getData();
|
}
|
}
|