package jnpf.bizcommon.onlyoffice.service.impl;
|
|
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.crypto.digest.DigestUtil;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeSessionEntity;
|
import jnpf.bizcommon.onlyoffice.mapper.OnlyOfficeSessionMapper;
|
import jnpf.exception.DataException;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.StringUtils;
|
|
import java.util.UUID;
|
|
/** 在 OnlyOffice 会话状态机内为 ELN 模板发布提供互斥 fence。 */
|
@Service
|
@RequiredArgsConstructor
|
public class OnlyOfficeTemplatePublishFenceService {
|
|
static final String ELN_TEMPLATE_BIZ_MODULE = "eln.template";
|
|
private final OnlyOfficeSessionMapper sessionMapper;
|
|
public String acquire(String tenantId, String templateId, String fileId, String fileType) {
|
requireIdentity(tenantId, templateId, fileId, fileType);
|
String token = UUID.randomUUID().toString();
|
OnlyOfficeSessionEntity existing = find(tenantId, fileId, fileType);
|
if (existing != null) {
|
acquireExisting(existing, tenantId, templateId, fileId, fileType, token);
|
return token;
|
}
|
|
OnlyOfficeSessionEntity placeholder = new OnlyOfficeSessionEntity();
|
placeholder.setId(IdUtil.getSnowflakeNextIdStr());
|
placeholder.setTenantId(tenantId);
|
placeholder.setFileId(fileId);
|
placeholder.setFileType(fileType);
|
placeholder.setFileName(fileId);
|
placeholder.setDocumentType("word");
|
placeholder.setBizModule(ELN_TEMPLATE_BIZ_MODULE);
|
placeholder.setBizDataId(templateId);
|
placeholder.setVersionNo(1);
|
placeholder.setVersion(1);
|
placeholder.setSessionStatus(OnlyOfficeSessionEntity.SessionStatus.PUBLISHING);
|
placeholder.setPublishToken(token);
|
placeholder.setDocKey(DigestUtil.md5Hex(fileId + "_1"));
|
try {
|
if (sessionMapper.insert(placeholder) == 1) {
|
return token;
|
}
|
} catch (DuplicateKeyException ignored) {
|
// 编辑器首次打开与发布同时插入时,由文件业务唯一键裁决,再重读走同一 CAS。
|
}
|
|
existing = find(tenantId, fileId, fileType);
|
if (existing == null) {
|
throw busy();
|
}
|
acquireExisting(existing, tenantId, templateId, fileId, fileType, token);
|
return token;
|
}
|
|
public boolean release(String tenantId, String templateId, String fileId,
|
String fileType, String token) {
|
requireIdentity(tenantId, templateId, fileId, fileType);
|
if (!StringUtils.hasText(token)) {
|
throw new DataException("发布 fence token 不能为空");
|
}
|
return sessionMapper.update(null, identityUpdate(
|
tenantId, templateId, fileId, fileType)
|
.eq("session_status", OnlyOfficeSessionEntity.SessionStatus.PUBLISHING)
|
.eq("publish_token", token)
|
.set("session_status", OnlyOfficeSessionEntity.SessionStatus.IDLE)
|
.set("publish_token", null)) == 1;
|
}
|
|
private void acquireExisting(OnlyOfficeSessionEntity existing, String tenantId,
|
String templateId, String fileId, String fileType,
|
String token) {
|
if (!ELN_TEMPLATE_BIZ_MODULE.equals(existing.getBizModule())
|
|| !templateId.equals(existing.getBizDataId())
|
|| !Integer.valueOf(OnlyOfficeSessionEntity.SessionStatus.IDLE)
|
.equals(existing.getSessionStatus())) {
|
throw busy();
|
}
|
int nextVersion = existing.getVersion() == null ? 1 : existing.getVersion() + 1;
|
UpdateWrapper<OnlyOfficeSessionEntity> update = identityUpdate(
|
tenantId, templateId, fileId, fileType)
|
.eq("f_id", existing.getId())
|
.eq("doc_key", existing.getDocKey())
|
.eq("version_no", existing.getVersionNo())
|
.eq("session_status", OnlyOfficeSessionEntity.SessionStatus.IDLE)
|
.set("session_status", OnlyOfficeSessionEntity.SessionStatus.PUBLISHING)
|
.set("publish_token", token)
|
.set("lock_user_id", null)
|
.set("lock_heartbeat", null)
|
.set("f_version", nextVersion);
|
if (existing.getVersion() == null) {
|
update.isNull("f_version");
|
} else {
|
update.eq("f_version", existing.getVersion());
|
}
|
if (sessionMapper.update(null, update) != 1) {
|
throw busy();
|
}
|
}
|
|
private OnlyOfficeSessionEntity find(String tenantId, String fileId, String fileType) {
|
return sessionMapper.selectOne(Wrappers.<OnlyOfficeSessionEntity>lambdaQuery()
|
.eq(OnlyOfficeSessionEntity::getTenantId, tenantId)
|
.eq(OnlyOfficeSessionEntity::getFileId, fileId)
|
.eq(OnlyOfficeSessionEntity::getFileType, fileType)
|
.last("limit 1"));
|
}
|
|
private UpdateWrapper<OnlyOfficeSessionEntity> identityUpdate(
|
String tenantId, String templateId, String fileId, String fileType) {
|
return Wrappers.<OnlyOfficeSessionEntity>update()
|
.eq("f_tenant_id", tenantId)
|
.eq("file_id", fileId)
|
.eq("file_type", fileType)
|
.eq("biz_module", ELN_TEMPLATE_BIZ_MODULE)
|
.eq("biz_data_id", templateId)
|
.and(active -> active.isNull("f_delete_mark").or().eq("f_delete_mark", 0));
|
}
|
|
private static void requireIdentity(
|
String tenantId, String templateId, String fileId, String fileType) {
|
if (!StringUtils.hasText(tenantId) || !StringUtils.hasText(templateId)
|
|| !StringUtils.hasText(fileId) || !StringUtils.hasText(fileType)) {
|
throw new DataException("模板发布 fence 身份参数不完整");
|
}
|
}
|
|
private static DataException busy() {
|
return new DataException("模板文档正在编辑、保存或发布中,请稍后重试");
|
}
|
}
|