REQ-3540: 备份
This commit is contained in:
parent
208fe9ab20
commit
cb69eee281
@ -3,7 +3,6 @@ package cn.axzo.nanopart.doc.api.domain;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
import cn.axzo.nanopart.doc.api.enums.FileFormat;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@ -24,12 +23,6 @@ public class IndexNodeAttributes {
|
||||
return new IndexNodeAttributes();
|
||||
}
|
||||
|
||||
public static IndexNodeAttributes fileAttributesForFileFormat(FileFormat fileFormat) {
|
||||
IndexNodeAttributes attributes = IndexNodeAttributes.create();
|
||||
attributes.getOrCreateFileAttributes().setFileFormat(fileFormat);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public DatabaseAttributes getOrCreateDatabaseAttributes() {
|
||||
if (databaseAttributes == null)
|
||||
databaseAttributes = new DatabaseAttributes();
|
||||
|
||||
@ -3,7 +3,6 @@ package cn.axzo.nanopart.doc.api.filetemplate.request;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import cn.axzo.nanopart.doc.api.domain.IndexNodeAttributes;
|
||||
import cn.axzo.nanopart.doc.api.enums.FileFormat;
|
||||
import cn.axzo.nanopart.doc.api.util.DefaultIcons;
|
||||
import lombok.Getter;
|
||||
@ -20,16 +19,11 @@ public class FileTemplateCreateFileRequest extends NodeCreateFileTemplate {
|
||||
* 文件格式. EXCEL, WORD, PDF
|
||||
*/
|
||||
@NotNull(message = "文件格式不能为空")
|
||||
private FileFormat fileFormat;
|
||||
private FileFormat format;
|
||||
|
||||
@Override
|
||||
public String icon() {
|
||||
return DefaultIcons.defaultFileIcon(fileFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexNodeAttributes attributes() {
|
||||
return IndexNodeAttributes.fileAttributesForFileFormat(fileFormat);
|
||||
return DefaultIcons.defaultFileIcon(format);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ package cn.axzo.nanopart.doc.api.filetemplate.request;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import cn.axzo.nanopart.doc.api.domain.IndexNodeAttributes;
|
||||
import cn.axzo.nanopart.doc.api.enums.FileFormat;
|
||||
import cn.axzo.nanopart.doc.api.util.DefaultIcons;
|
||||
import lombok.Getter;
|
||||
@ -27,16 +26,11 @@ public class FileTemplateUploadFileRequest extends NodeCreateFileTemplate {
|
||||
* 文件格式. EXCEL, WORD, PDF
|
||||
*/
|
||||
@NotNull(message = "文件格式不能为空")
|
||||
private FileFormat fileFormat;
|
||||
private FileFormat format;
|
||||
|
||||
@Override
|
||||
public String icon() {
|
||||
return DefaultIcons.defaultFileIcon(fileFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexNodeAttributes attributes() {
|
||||
return IndexNodeAttributes.fileAttributesForFileFormat(fileFormat);
|
||||
return DefaultIcons.defaultFileIcon(format);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
|
||||
package cn.axzo.nanopart.doc.api.util;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
/**
|
||||
* @author yanglin
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class IdBuilder {
|
||||
|
||||
private final List<Object> buf = new ArrayList<>();
|
||||
private final boolean appendAbsentValue;
|
||||
|
||||
public static IdBuilder idbuilder() {
|
||||
return new IdBuilder(false);
|
||||
}
|
||||
|
||||
public static IdBuilder idbuilder(boolean appendAbsentValue) {
|
||||
return new IdBuilder(appendAbsentValue);
|
||||
}
|
||||
|
||||
public IdBuilder append(Object value) {
|
||||
if (isAbsentValue(value) && !appendAbsentValue)
|
||||
return this;
|
||||
buf.add(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static boolean isAbsentValue(Object value) {
|
||||
if (value == null)
|
||||
return true;
|
||||
if (value instanceof String)
|
||||
return StringUtils.isBlank((String) value);
|
||||
return false;
|
||||
}
|
||||
|
||||
public String build() {
|
||||
return buf.stream().map(String::valueOf).collect(joining(":"));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
|
||||
package cn.axzo.nanopart.doc.dao;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
|
||||
import cn.axzo.nanopart.doc.api.util.BizAssertions;
|
||||
import cn.axzo.nanopart.doc.entity.DocLock;
|
||||
import cn.axzo.nanopart.doc.mapper.DocLockMapper;
|
||||
|
||||
/**
|
||||
* @author yanglin
|
||||
*/
|
||||
@Repository
|
||||
public class DocLockDao extends ServiceImpl<DocLockMapper, DocLock> {
|
||||
|
||||
public void lockAndReleaseOnCommit(String key) {
|
||||
BizAssertions.assertTrue(TransactionSynchronizationManager.isActualTransactionActive(), "必须和事务搭配使用");
|
||||
DocLock lock = lambdaQuery() //
|
||||
.eq(DocLock::getKey, key) //
|
||||
.last("for update") //
|
||||
.one();
|
||||
if (lock == null) {
|
||||
lock = new DocLock();
|
||||
lock.setKey(key);
|
||||
save(lock);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -18,6 +18,19 @@ import cn.axzo.nanopart.doc.mapper.IndexNodeMapper;
|
||||
@Repository
|
||||
public class IndexNodeDao extends ServiceImpl<IndexNodeMapper, IndexNode> {
|
||||
|
||||
public IndexNode getNodeForUpdateOrThrow(String code) {
|
||||
IndexNode node = findNodeForUpdateOrNull(code);
|
||||
BizAssertions.assertNotNull(node, "节点不存在: {}", code);
|
||||
return node;
|
||||
}
|
||||
|
||||
public IndexNode findNodeForUpdateOrNull(String code) {
|
||||
return lambdaQuery() //
|
||||
.eq(IndexNode::getCode, code) //
|
||||
.last("FOR UPDATE") //
|
||||
.one();
|
||||
}
|
||||
|
||||
public IndexNode getNodeOrThrow(String code) {
|
||||
IndexNode node = findNodeOrNull(code);
|
||||
BizAssertions.assertNotNull(node, "节点不存在: {}", code);
|
||||
@ -64,4 +77,4 @@ public class IndexNodeDao extends ServiceImpl<IndexNodeMapper, IndexNode> {
|
||||
.update();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
|
||||
package cn.axzo.nanopart.doc.entity;
|
||||
|
||||
import cn.axzo.pudge.core.persistence.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author yanglin
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@TableName(value = "doc_lock", autoResultMap = true)
|
||||
public class DocLock extends BaseEntity<DocLock> {
|
||||
|
||||
/**
|
||||
* 锁键
|
||||
*/
|
||||
private String key;
|
||||
|
||||
}
|
||||
@ -11,6 +11,7 @@ import cn.axzo.nanopart.doc.api.enums.FileScope;
|
||||
import cn.axzo.nanopart.doc.api.enums.IndexNodeContext;
|
||||
import cn.axzo.nanopart.doc.api.enums.IndexNodeState;
|
||||
import cn.axzo.nanopart.doc.api.enums.IndexNodeType;
|
||||
import cn.axzo.nanopart.doc.api.util.UUIDUtil;
|
||||
import cn.axzo.pokonyan.config.mybatisplus.BaseEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@ -25,6 +26,10 @@ public class IndexNode extends BaseEntity<IndexNode> implements NodeCreate {
|
||||
|
||||
public static final String ROOT_CODE = "";
|
||||
|
||||
public static String newCode() {
|
||||
return UUIDUtil.uuidString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 父节点code
|
||||
*/
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
package cn.axzo.nanopart.doc.file.filetemplate;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import cn.axzo.nanopart.doc.api.enums.FileTemplateState;
|
||||
import cn.axzo.nanopart.doc.api.filetemplate.request.FileTemplateCreateDirRequest;
|
||||
@ -12,7 +13,7 @@ import cn.axzo.nanopart.doc.dao.FileTemplateDao;
|
||||
import cn.axzo.nanopart.doc.entity.FileTemplate;
|
||||
import cn.axzo.nanopart.doc.entity.IndexNode;
|
||||
import cn.axzo.nanopart.doc.file.index.IndexManager;
|
||||
import cn.axzo.nanopart.doc.utils.BizTransactional;
|
||||
import cn.axzo.nanopart.doc.file.index.OssFile;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
@ -24,19 +25,20 @@ public class FileTemplateManager {
|
||||
|
||||
private final IndexManager indexManager;
|
||||
private final FileTemplateDao fileTemplateDao;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
public String createDir(FileTemplateCreateDirRequest request) {
|
||||
return indexManager.createDir(request).getCode();
|
||||
}
|
||||
|
||||
@BizTransactional
|
||||
public String createFile(FileTemplateCreateFileRequest request) {
|
||||
return createFileTemplate(indexManager.createFile(request, request.getFileFormat()));
|
||||
OssFile ossFile = indexManager.uploadEmptyFile(request, request.getFormat());
|
||||
return transactionTemplate.execute(unused -> createFileTemplate(indexManager.createFile(request, ossFile)));
|
||||
}
|
||||
|
||||
@BizTransactional
|
||||
public String uploadFile(FileTemplateUploadFileRequest request) {
|
||||
return createFileTemplate(indexManager.uploadFile(request, request.getFileFormat(), request.getFileBase64()));
|
||||
OssFile ossFile = indexManager.uploadFile(request, request.getFormat(), request.getFileBase64());
|
||||
return transactionTemplate.execute(unused -> createFileTemplate(indexManager.createFile(request, ossFile)));
|
||||
}
|
||||
|
||||
private String createFileTemplate(IndexNode fileNode) {
|
||||
@ -63,4 +65,4 @@ public class FileTemplateManager {
|
||||
.set(FileTemplate::getState, FileTemplateState.PUBLISHED) //
|
||||
.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ package cn.axzo.nanopart.doc.file.index;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import cn.axzo.nanopart.doc.api.domain.NodeCreate;
|
||||
import cn.axzo.nanopart.doc.api.enums.FileFormat;
|
||||
@ -41,30 +42,43 @@ public class IndexManager {
|
||||
return indexSupport.createNode(node, IndexNodeType.DIRECTORY, IndexNodeState.VALID);
|
||||
}
|
||||
|
||||
@BizTransactional
|
||||
public IndexNode createFile(NodeCreate node, FileFormat format) {
|
||||
/**
|
||||
* 不能在事务中使用
|
||||
*/
|
||||
public OssFile uploadEmptyFile(NodeCreate node, FileFormat format) {
|
||||
BizAssertions.assertTrue(format.creatable(), "无法创建: {}", format.readableName());
|
||||
BizAssertions.assertFalse(TransactionSynchronizationManager.isActualTransactionActive(), "不能在事务中使用");
|
||||
indexSupport.ensureChildNameNotUsed(node);
|
||||
IndexNode fileNode = createFileNode(node, format);
|
||||
String emptyOssFileKey = format == FileFormat.WORD //
|
||||
? docProps.getEmptyWordOssFileKey() //
|
||||
: docProps.getEmptyExcelFileKey();
|
||||
return updateFileKey(fileNode, ossClient.copy(emptyOssFileKey, fileNode.getCode(), format));
|
||||
String code = IndexNode.newCode();
|
||||
return new OssFile(format, code, ossClient.copy(emptyOssFileKey, code, format));
|
||||
}
|
||||
|
||||
/**
|
||||
* 不能在事务中使用
|
||||
*/
|
||||
public OssFile uploadFile(NodeCreate node, FileFormat format, String fileBase64) {
|
||||
BizAssertions.assertFalse(TransactionSynchronizationManager.isActualTransactionActive(), "不能在事务中使用");
|
||||
indexSupport.ensureChildNameNotUsed(node);
|
||||
String code = IndexNode.newCode();
|
||||
return new OssFile(format, code, ossClient.upload(fileBase64, code, format));
|
||||
}
|
||||
|
||||
@BizTransactional
|
||||
public IndexNode uploadFile(NodeCreate node, FileFormat format, String fileBase64) {
|
||||
indexSupport.ensureChildNameNotUsed(node);
|
||||
IndexNode fileNode = createFileNode(node, format);
|
||||
return updateFileKey(fileNode, ossClient.upload(fileBase64, fileNode.getCode(), format));
|
||||
}
|
||||
|
||||
private IndexNode createFileNode(NodeCreate node, FileFormat format) {
|
||||
BizAssertions.assertTrue(format.creatable(), "无法创建: {}", format.readableName());
|
||||
return indexSupport.createNode(node, IndexNodeType.FILE, IndexNodeState.VALID);
|
||||
}
|
||||
|
||||
private IndexNode updateFileKey(IndexNode fileNode, String fileKey) {
|
||||
fileNode.getOrCreateAttributes().getOrCreateFileAttributes().setOssFileKey(fileKey);
|
||||
public IndexNode createFile(NodeCreate node, OssFile ossFile) {
|
||||
indexSupport.lockParentAndReleaseOnCommit(node);
|
||||
try {
|
||||
indexSupport.ensureChildNameNotUsed(node);
|
||||
}
|
||||
catch (NameUsedException e) {
|
||||
ossClient.delete(ossFile.ossFileKey());
|
||||
throw e;
|
||||
}
|
||||
IndexNode fileNode = indexSupport.createNode(ossFile.code(), node, IndexNodeType.FILE, IndexNodeState.VALID);
|
||||
fileNode.getOrCreateAttributes().getOrCreateFileAttributes().setOssFileKey(ossFile.ossFileKey());
|
||||
fileNode.getOrCreateAttributes().getOrCreateFileAttributes().setFileFormat(ossFile.format());
|
||||
indexNodeDao.updateAttributes(fileNode);
|
||||
return indexNodeDao.findNodeOrNull(fileNode.getCode());
|
||||
}
|
||||
@ -72,15 +86,12 @@ public class IndexManager {
|
||||
@BizTransactional
|
||||
public void rename(String code, String newName) {
|
||||
IndexNode indexNode = indexNodeDao.getNodeOrThrow(code);
|
||||
indexSupport.lockParentAndReleaseOnCommit(indexNode);
|
||||
indexSupport.ensureChildNameNotUsed(indexNode, indexNode.getParentCode(), newName);
|
||||
indexNodeDao.rename(code, newName);
|
||||
}
|
||||
|
||||
public IndexNode copy(String copyCode, String destParentCode) {
|
||||
IndexNode copyNode = indexNodeDao.getNodeOrThrow(copyCode);
|
||||
BizAssertions.assertEquals(IndexNodeType.FILE, copyNode.getNodeType(), "只能复制文件");
|
||||
IndexNode parentNode = indexNodeDao.getNodeOrThrow(destParentCode);
|
||||
new NodeCreateCopy(copyNode, destParentCode, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
|
||||
package cn.axzo.nanopart.doc.file.index;
|
||||
|
||||
import static cn.axzo.nanopart.doc.api.util.IdBuilder.idbuilder;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import cn.axzo.nanopart.doc.api.domain.IndexNodeQueryContext;
|
||||
import cn.axzo.nanopart.doc.api.domain.NodeCreate;
|
||||
@ -10,6 +13,7 @@ import cn.axzo.nanopart.doc.api.enums.IndexNodeState;
|
||||
import cn.axzo.nanopart.doc.api.enums.IndexNodeType;
|
||||
import cn.axzo.nanopart.doc.api.util.BizAssertions;
|
||||
import cn.axzo.nanopart.doc.api.util.UUIDUtil;
|
||||
import cn.axzo.nanopart.doc.dao.DocLockDao;
|
||||
import cn.axzo.nanopart.doc.dao.IndexNodeDao;
|
||||
import cn.axzo.nanopart.doc.entity.IndexNode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -22,6 +26,7 @@ import lombok.RequiredArgsConstructor;
|
||||
class IndexSupport {
|
||||
|
||||
private final IndexNodeDao indexNodeDao;
|
||||
private final DocLockDao docLockDao;
|
||||
|
||||
IndexNode createNode(NodeCreate node, IndexNodeType nodeType, IndexNodeState state) {
|
||||
return createNode(UUIDUtil.uuidString(), node, nodeType, state);
|
||||
@ -64,7 +69,28 @@ class IndexSupport {
|
||||
|
||||
void ensureChildNameNotUsed(IndexNodeQueryContext nodeContext, String parentCode, String childName) {
|
||||
IndexNode child = indexNodeDao.findChildByName(nodeContext, parentCode, childName);
|
||||
BizAssertions.assertFalse(child != null, "名称已被使用");
|
||||
if (child != null)
|
||||
throw new NameUsedException("名称已被使用");
|
||||
}
|
||||
|
||||
void lockParentAndReleaseOnCommit(NodeCreate node) {
|
||||
lockParentAndReleaseOnCommit(node, node.parentCode());
|
||||
}
|
||||
|
||||
void lockParentAndReleaseOnCommit(IndexNodeQueryContext nodeContext, String parentCode) {
|
||||
BizAssertions.assertTrue(TransactionSynchronizationManager.isActualTransactionActive(), "必须和事务搭配使用");
|
||||
// 如果父节点存在就锁父节点
|
||||
if (StringUtils.isNotBlank(parentCode)) {
|
||||
indexNodeDao.getNodeForUpdateOrThrow(parentCode);
|
||||
return;
|
||||
}
|
||||
// 如果父节点不存在就锁scope
|
||||
docLockDao.lockAndReleaseOnCommit(idbuilder() //
|
||||
.append("INDEX_NODE") //
|
||||
.append(nodeContext.context()) //
|
||||
.append(nodeContext.scope()) //
|
||||
.append(nodeContext.scopeId()) //
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
package cn.axzo.nanopart.doc.file.index;
|
||||
|
||||
import cn.axzo.basics.common.exception.ServiceException;
|
||||
|
||||
/**
|
||||
* @author yanglin
|
||||
*/
|
||||
public class NameUsedException extends ServiceException {
|
||||
|
||||
public NameUsedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@ -63,4 +63,4 @@ public class NodeCreateCopy implements NodeCreate {
|
||||
return copyNode.getScopeId();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
package cn.axzo.nanopart.doc.file.index;
|
||||
|
||||
import cn.axzo.nanopart.doc.api.enums.FileFormat;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @author yanglin
|
||||
*/
|
||||
@Getter
|
||||
@Accessors(fluent = true)
|
||||
@RequiredArgsConstructor
|
||||
public class OssFile {
|
||||
|
||||
private final FileFormat format;
|
||||
private final String code;
|
||||
private final String ossFileKey;
|
||||
|
||||
}
|
||||
@ -13,7 +13,7 @@ import lombok.RequiredArgsConstructor;
|
||||
@RequiredArgsConstructor
|
||||
public class OssClient {
|
||||
|
||||
public String copy(String srcFileKey, String destFileName, FileFormat format) {
|
||||
public String copy(String srcOssFileKey, String destFileName, FileFormat format) {
|
||||
// TODO(yl): finish this
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@ -23,4 +23,7 @@ public class OssClient {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void delete(String ossFileKey) {
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
|
||||
package cn.axzo.nanopart.doc.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
import cn.axzo.nanopart.doc.entity.DocLock;
|
||||
|
||||
/**
|
||||
* @author yanglin
|
||||
*/
|
||||
@Mapper
|
||||
public interface DocLockMapper extends BaseMapper<DocLock> {
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user