Merge remote-tracking branch 'origin/feature/REQ-3540' into feature/REQ-3540

This commit is contained in:
xudawei 2025-03-18 11:01:27 +08:00
commit 4c0bd16765
45 changed files with 610 additions and 185 deletions

View File

@ -14,6 +14,6 @@ public class DirectoryAttributes {
/**
* 是否为资料库文件夹
*/
private boolean isTemplateDatabaseDir;
private Boolean isTemplateDatabaseDir;
}

View File

@ -2,9 +2,9 @@
package cn.axzo.nanopart.doc.api.domain;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
@ -18,7 +18,7 @@ public class IndexNodeAttributes {
/**
* 是否为自定义的图标
*/
private boolean customIcon;
private Boolean customIcon;
/**
* 数据库属性

View File

@ -4,6 +4,7 @@ package cn.axzo.nanopart.doc.api.domain;
import java.util.ArrayList;
import java.util.List;
import cn.axzo.nanopart.doc.api.enums.IndexNodeContext;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
@ -56,6 +57,11 @@ public class IndexNodeInfo implements NodeValue, ValueContainer<IndexNodeInfo> {
*/
private IndexNodeType nodeType;
/**
* 上下文. NONE: 无上下文, SYSTEM: 系统内置, FILE_TEMPLATE: 文件模版, TEMPLATE_DATABASE: 模版数据库, FILE_DATABASE: 数据库
*/
private IndexNodeContext context;
/**
* 作用范围. NONE: , ENT_DATABASE: 企业数据库, PROJECT_DATABASE: 项目数据库, PERSONAL_DATABASE: 个人数据库
*/

View File

@ -14,4 +14,11 @@ public interface IndexNodeParentScope {
return "";
}
IndexNodeParentScope TEMPLATE_DATABASE_ROOT = new IndexNodeParentScope() {
@Override
public IndexNodeScope nodeScope() {
return IndexNodeScope.TEMPLATE_DATABASE;
}
};
}

View File

@ -11,5 +11,7 @@ public enum FileDatabaseState {
// 已开通
ACTIVATED,
// 已到期
EXPIRED
EXPIRED,
// 容量已满
CAPACITY_EXHAUSTED
}

View File

@ -3,6 +3,7 @@ package cn.axzo.nanopart.doc.api.filedb;
import javax.validation.Valid;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseRenewRequest;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -40,6 +41,12 @@ public interface FileDatabaseApi {
@PostMapping("/api/fileDatabase/expandCapacity")
CommonResponse<Void> expandCapacity(@RequestBody @Valid FileDatabaseExpandCapacityRequest request);
/**
* 续费
*/
@PostMapping("/api/fileDatabase/renew")
CommonResponse<Void> renew(@RequestBody @Valid FileDatabaseRenewRequest request);
/**
* 移除
*/

View File

@ -4,6 +4,7 @@ package cn.axzo.nanopart.doc.api.filedb.request;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import cn.axzo.nanopart.doc.api.util.BizAssertions;
import lombok.Getter;
import lombok.Setter;
@ -31,4 +32,8 @@ public class FileDatabaseActiveRequest {
*/
@NotNull(message = "expiredDateMs不能为空")
private Long expiredDateMs;
public void checkExpiredDate() {
BizAssertions.assertTrue(expiredDateMs > System.currentTimeMillis(), "expiredDateMs不能小于当前时间");
}
}

View File

@ -0,0 +1,8 @@
package cn.axzo.nanopart.doc.api.filedb.request;
/**
* @author yanglin
*/
public class FileDatabaseRenewRequest extends FileDatabaseActiveRequest {
}

View File

@ -1,8 +1,8 @@
package cn.axzo.nanopart.doc.api.filetemplate.request;
import cn.axzo.nanopart.doc.api.util.DefaultIcons;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
@ -13,11 +13,6 @@ import lombok.Setter;
@Getter
public class FileTemplateCreateDirRequest extends NodeCreateFileTemplate {
@Override
public String icon() {
return DefaultIcons.DIR;
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -6,7 +6,6 @@ import javax.validation.constraints.NotNull;
import com.alibaba.fastjson.JSON;
import cn.axzo.nanopart.doc.api.enums.FileFormat;
import cn.axzo.nanopart.doc.api.util.DefaultIcons;
import lombok.Getter;
import lombok.Setter;
@ -23,11 +22,6 @@ public class FileTemplateCreateFileRequest extends NodeCreateFileTemplate {
@NotNull(message = "文件格式不能为空")
private FileFormat format;
@Override
public String icon() {
return DefaultIcons.defaultFileIcon(format);
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -5,6 +5,8 @@ import java.util.List;
import javax.validation.Valid;
import cn.axzo.nanopart.doc.api.templatedb.domain.DatabaseInfo;
import cn.axzo.nanopart.doc.api.templatedb.reqeust.TemplateDatabaseSearchRequest;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -97,6 +99,12 @@ public interface TemplateDatabaseApi {
@PostMapping("/api/templateDatabase/search")
CommonResponse<Page<IndexNodeInfo>> search(@RequestBody @Valid IndexNodeSearchRequest request);
/**
* 搜索资料库
*/
@PostMapping("/api/templateDatabase/searchDatabase")
CommonResponse<Page<DatabaseInfo>> searchDatabase(@RequestBody @Valid TemplateDatabaseSearchRequest request);
/**
* 项企资料库: 获取资料库或文件夹信息
*/

View File

@ -0,0 +1,36 @@
package cn.axzo.nanopart.doc.api.templatedb.domain;
import cn.axzo.nanopart.doc.api.domain.DatabaseFeeConfig;
import cn.axzo.nanopart.doc.api.enums.DatabaseType;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class DatabaseInfo {
/**
* 资料库名称
*/
private String name;
/**
* 资料库编译
*/
private String code;
/**
* 类型. CREATED_BY_SYSTEM: 平台资料库, CREATED_BY_USER_FOR_ENT: 专属资料库
*/
private DatabaseType databaseType;
/**
* 费用配置
*/
private DatabaseFeeConfig feeConfig;
}

View File

@ -7,7 +7,6 @@ import org.apache.commons.lang3.StringUtils;
import cn.axzo.nanopart.doc.api.enums.CooperationType;
import cn.axzo.nanopart.doc.api.enums.DatabaseScope;
import cn.axzo.nanopart.doc.api.util.DefaultIcons;
import lombok.Getter;
import lombok.Setter;
@ -31,7 +30,7 @@ public class TemplateDatabaseCreateDatabaseRequest extends TemplateDatabaseCreat
private CooperationType coopType;
public String icon() {
return StringUtils.isBlank(getIcon()) ? DefaultIcons.DATABASE : getIcon();
return StringUtils.isBlank(getIcon()) ? "" : getIcon();
}
}

View File

@ -6,7 +6,6 @@ import javax.validation.constraints.NotBlank;
import org.apache.commons.lang3.StringUtils;
import cn.axzo.nanopart.doc.api.domain.IndexNodeAttributes;
import cn.axzo.nanopart.doc.api.util.DefaultIcons;
import lombok.Getter;
import lombok.Setter;
@ -39,7 +38,7 @@ public class TemplateDatabaseCreateDirRequest extends NodeCreateTemplateDatabase
private boolean customIcon;
public String icon() {
return StringUtils.isBlank(icon) ? DefaultIcons.DIR : icon;
return StringUtils.isBlank(icon) ? "" : icon;
}
public String bizCode() {
@ -53,7 +52,7 @@ public class TemplateDatabaseCreateDirRequest extends NodeCreateTemplateDatabase
@Override
public IndexNodeAttributes attributes() {
IndexNodeAttributes attributes = IndexNodeAttributes.create();
attributes.getDirectoryAttributes().setTemplateDatabaseDir(true);
attributes.getOrCreateDirectoryAttributes().setIsTemplateDatabaseDir(true);
attributes.setCustomIcon(customIcon);
return attributes;
}

View File

@ -4,7 +4,6 @@ package cn.axzo.nanopart.doc.api.templatedb.reqeust;
import javax.validation.constraints.NotNull;
import cn.axzo.nanopart.doc.api.enums.FileFormat;
import cn.axzo.nanopart.doc.api.util.DefaultIcons;
import lombok.Getter;
import lombok.Setter;
@ -21,9 +20,4 @@ public class TemplateDatabaseCreateFileRequest extends NodeCreateTemplateDatabas
@NotNull(message = "文件格式不能为空")
private FileFormat format;
@Override
public String icon() {
return DefaultIcons.defaultFileIcon(format);
}
}

View File

@ -0,0 +1,20 @@
package cn.axzo.nanopart.doc.api.templatedb.reqeust;
import cn.axzo.basics.common.page.PageRequest;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class TemplateDatabaseSearchRequest extends PageRequest {
/**
* 搜索的名称
*/
private String name;
}

View File

@ -128,7 +128,7 @@ public class BizAssertions {
if (response == null || response.getCode() != HttpStatus.HTTP_OK) {
ServiceException e = new ServiceException(
messageOrTemplateMessage(response == null ? null : response.getMsg(), message, args));
log.warn("remote call response with error", e);
log.warn("remote call response with error, response={}", JSON.toJSONString(response), e);
throw e;
}
return response.getData();

View File

@ -1,34 +0,0 @@
package cn.axzo.nanopart.doc.api.util;
import static cn.axzo.nanopart.doc.api.util.BizAssertions.fail;
import cn.axzo.nanopart.doc.api.enums.FileFormat;
/**
* @author yanglin
*/
public class DefaultIcons {
// TODO(yl): WHAT
public static final String DIR = "default_dir_icon";
// TODO(yl): WHAT
public static final String DATABASE = "default_database_icon";
// TODO(yl): WHAT
public static final String FILE_EXCEL = "default_excel_icon";
// TODO(yl): WHAT
public static final String FILE_WORD = "default_word_icon";
// TODO(yl): WHAT
public static final String FILE_PDF = "default_pdf_icon";
public static String defaultFileIcon(FileFormat fileFormat) {
if (fileFormat == FileFormat.EXCEL)
return DefaultIcons.FILE_EXCEL;
if (fileFormat == FileFormat.WORD)
return DefaultIcons.FILE_WORD;
if (fileFormat == FileFormat.PDF)
return DefaultIcons.FILE_PDF;
throw fail("不支持的文件格式: {}", fileFormat);
}
}

View File

@ -1,13 +1,11 @@
package cn.axzo.nanopart.doc.config;
import static cn.axzo.nanopart.doc.api.util.BizAssertions.fail;
import cn.axzo.nanopart.doc.api.enums.FileFormat;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import cn.axzo.nanopart.doc.api.enums.FileFormat;
import lombok.Getter;
import lombok.Setter;
@ -28,15 +26,21 @@ public class DocProps {
private int indexNodeMaxDepth = 10;
private int indexNodeMaxCopyFileSize = 1000;
private String ossAppCode = "ent-workspace-doc";
private int asyncTimeoutSeconds = 5;
private String defaultIconDir = "default_dir_icon";
private String defaultIconExcel = "default_excel_icon";
private String defaultIconWord = "default_word_icon";
private String defaultIconPdf = "default_pdf_icon";
private int asyncTimeoutSeconds = 10;
private String defaultIconDatabase = "https://axzo-obs-public.obs.cn-north-4.myhuaweicloud.com:443/doc/doc/image-30.png";
private String defaultIconDir = "https://axzo-obs-public.obs.cn-north-4.myhuaweicloud.com:443/doc/doc/image-30.png";
private String defaultIconExcel = "https://axzo-obs-public.obs.cn-north-4.myhuaweicloud.com:443/doc/doc/excel.png";
private String defaultIconWord = "https://axzo-obs-public.obs.cn-north-4.myhuaweicloud.com:443/doc/doc/word.png";
private String defaultIconPdf = "https://axzo-obs-public.obs.cn-north-4.myhuaweicloud.com:443/doc/doc/pdf.png";
public String createFileOssFileKey(FileFormat format) {
return format == FileFormat.WORD //
? getCreateFileWordOssFileKey() //
: getCreateFileExcelFileKey();
if (format == FileFormat.WORD)
return createFileWordOssFileKey;
if (format == FileFormat.EXCEL)
return createFileExcelFileKey;
if (format == FileFormat.PDF)
return createFilePptFileKey;
return "";
}
}

View File

@ -1,12 +1,12 @@
package cn.axzo.nanopart.doc.dao;
import cn.axzo.nanopart.doc.api.enums.FileDatabaseState;
import org.springframework.stereotype.Repository;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.axzo.nanopart.doc.api.enums.DatabaseScope;
import cn.axzo.nanopart.doc.api.enums.FileDatabaseState;
import cn.axzo.nanopart.doc.api.util.BizAssertions;
import cn.axzo.nanopart.doc.entity.FileDatabase;
import cn.axzo.nanopart.doc.mapper.FileDatabaseMapper;
@ -35,11 +35,22 @@ public class FileDatabaseDao extends ServiceImpl<FileDatabaseMapper, FileDatabas
}
public FileDatabase getOrThrow(String code) {
FileDatabase db = lambdaQuery() //
FileDatabase db = findOrNull(code);
BizAssertions.assertNotNull(db, "找不到资料库: {}", code);
return db;
}
public FileDatabase findOrNull(String code) {
return lambdaQuery() //
.eq(FileDatabase::getCode, code) //
.one();
BizAssertions.assertNotNull(code, "找不到资料库: {}", code);
return db;
}
public FileDatabase findForUpdateOrNull(String code) {
return lambdaQuery() //
.eq(FileDatabase::getCode, code) //
.last("FOR UPDATE") //
.one();
}
public void deleteByCode(String code) {
@ -48,10 +59,18 @@ public class FileDatabaseDao extends ServiceImpl<FileDatabaseMapper, FileDatabas
.remove();
}
public void setActivated(String code) {
public void updateUsedCapacity(String code, int usedFileSize) {
lambdaUpdate() //
.eq(FileDatabase::getCode, code) //
.set(FileDatabase::getState, FileDatabaseState.ACTIVATED) //
.set(FileDatabase::getUsedCapacity, usedFileSize) //
.update();
}
public void updateState(String code, FileDatabaseState state) {
lambdaUpdate() //
.eq(FileDatabase::getCode, code) //
.set(FileDatabase::getState, state) //
.update();
}
}

View File

@ -68,12 +68,12 @@ public class IndexNodeDao extends ServiceImpl<IndexNodeMapper, IndexNode> {
.one();
}
public List<IndexNode> findValidChildrenNameRightLike(IndexNodeParentScope parentNode, IndexNodeType nodeType,
String name) {
public List<IndexNode> findValidChildrenNameRightLike(IndexNodeParentScope parentNode, IndexNode node) {
return parentOrScopeRootQuery(parentNode) //
.likeRight(IndexNode::getName, name) //
.eq(IndexNode::getNodeType, nodeType) //
.likeRight(IndexNode::getName, node.getName()) //
.eq(IndexNode::getNodeType, node.getNodeType()) //
.eq(IndexNode::getState, IndexNodeState.VALID) //
.select(IndexNode::getId, IndexNode::getName, IndexNode::getParentId, IndexNode::getCode) //
.list();
}
@ -136,11 +136,12 @@ public class IndexNodeDao extends ServiceImpl<IndexNodeMapper, IndexNode> {
public void updateFileSize(String code, long size) {
lambdaUpdate() //
.eq(IndexNode::getCode, code) //
.eq(IndexNode::getNodeType, IndexNodeType.FILE) //
.set(IndexNode::getSize, size) //
.update();
}
private LambdaQueryChainWrapper<IndexNode> parentOrScopeRootQuery(IndexNodeParentScope parentScope) {
public LambdaQueryChainWrapper<IndexNode> parentOrScopeRootQuery(IndexNodeParentScope parentScope) {
if (StringUtils.isBlank(parentScope.parentCode()))
return scopeQuery(parentScope.nodeScope()).eq(IndexNode::getParentCode, "");
return lambdaQuery().eq(IndexNode::getParentCode, parentScope.parentCode());

View File

@ -3,6 +3,7 @@ package cn.axzo.nanopart.doc.entity;
import java.util.Date;
import cn.axzo.nanopart.doc.entity.domain.IndexNodeCodeProvider;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.TableName;
@ -20,7 +21,7 @@ import lombok.Setter;
@Setter
@Getter
@TableName(value = "doc_file_database", autoResultMap = true)
public class FileDatabase extends BaseEntity<FileDatabase> implements IndexNodeScope {
public class FileDatabase extends BaseEntity<FileDatabase> implements IndexNodeScope, IndexNodeCodeProvider {
/**
* 编码, 和index_node中对应的节点的scope一致
@ -76,6 +77,10 @@ public class FileDatabase extends BaseEntity<FileDatabase> implements IndexNodeS
return state == FileDatabaseState.ACTIVATED;
}
public boolean isCapacityExhausted() {
return usedCapacity >= allowedCapacity;
}
@Override
public String scopeCode() {
// I'm a scope of the index node
@ -97,4 +102,9 @@ public class FileDatabase extends BaseEntity<FileDatabase> implements IndexNodeS
public String toString() {
return JSON.toJSONString(this);
}
@Override
public String indexNodeCode() {
return templateDatabaseCode;
}
}

View File

@ -0,0 +1,38 @@
package cn.axzo.nanopart.doc.entity;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* @author yanglin
*/
@Slf4j
@MappedTypes({Object.class})
@MappedJdbcTypes(JdbcType.VARCHAR)
@RequiredArgsConstructor
public class IgnorePropsJsonTypeHandler extends AbstractJsonTypeHandler<Object> {
private final Class<?> type;
@Override
protected Object parse(String json) {
if (StringUtils.isBlank(json))
return null;
return JSON.parseObject(json, type);
}
@Override
protected String toJson(Object obj) {
return JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect);
}
}

View File

@ -7,7 +7,6 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import com.fasterxml.jackson.annotation.JsonIgnore;
import cn.axzo.maokai.api.vo.response.tree.NodeValue;
@ -115,7 +114,7 @@ public class IndexNode extends BaseEntity<IndexNode> implements NodeValue, Index
/**
* 属性. 不同node_type的属性可能不一样
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
@TableField(typeHandler = IgnorePropsJsonTypeHandler.class)
private IndexNodeAttributes attributes;
// !! helper field
@ -131,6 +130,12 @@ public class IndexNode extends BaseEntity<IndexNode> implements NodeValue, Index
return attributes;
}
@JsonIgnore
@JSONField(serialize = false, deserialize = false)
public FileAttributes getOrCreateFileAttributes() {
return getOrCreateAttributes().getOrCreateFileAttributes();
}
/**
* 获取Attributes中fileAttribute
*/
@ -152,6 +157,12 @@ public class IndexNode extends BaseEntity<IndexNode> implements NodeValue, Index
return Objects.equals(n1.getId(), n2.getId());
}
public static void setScope(IndexNode indexNode, IndexNodeScope scope) {
indexNode.setContext(scope.context());
indexNode.setScope(scope.scope());
indexNode.setScopeCode(scope.scopeCode());
}
@JsonIgnore
@JSONField(serialize = false, deserialize = false)
public boolean isFile() {

View File

@ -1,6 +1,7 @@
package cn.axzo.nanopart.doc.entity;
import cn.axzo.nanopart.doc.entity.domain.IndexNodeCodeProvider;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableField;
@ -23,7 +24,7 @@ import lombok.Setter;
@Setter
@Getter
@TableName(value = "doc_template_database", autoResultMap = true)
public class TemplateDatabase extends BaseEntity<TemplateDatabase> {
public class TemplateDatabase extends BaseEntity<TemplateDatabase> implements IndexNodeCodeProvider {
/**
* 编码, 和index_node中对应节点的编码保持一致
@ -73,6 +74,11 @@ public class TemplateDatabase extends BaseEntity<TemplateDatabase> {
return accessConfig;
}
@Override
public String indexNodeCode() {
return code;
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -0,0 +1,11 @@
package cn.axzo.nanopart.doc.entity.domain;
/**
* @author yanglin
*/
public interface IndexNodeCodeProvider {
String indexNodeCode();
}

View File

@ -8,6 +8,7 @@ import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseActiveRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseAddWorkspaceRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseExpandCapacityRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseRemoveRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseRenewRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseSearchRequest;
import cn.axzo.nanopart.doc.api.filedb.response.FileDatabaseInfoResponse;
import cn.azxo.framework.common.model.CommonResponse;
@ -27,30 +28,42 @@ public class FileDatabaseApiController implements FileDatabaseApi {
@Override
public CommonResponse<Void> addWorkspace(FileDatabaseAddWorkspaceRequest request) {
log.info("add workspace: {}", request);
fileDatabaseService.addWorkspace(request);
return CommonResponse.success();
}
@Override
public CommonResponse<Void> active(FileDatabaseActiveRequest request) {
log.info("active workspace: {}", request);
fileDatabaseService.active(request);
return CommonResponse.success();
}
@Override
public CommonResponse<Void> expandCapacity(FileDatabaseExpandCapacityRequest request) {
log.info("expand capacity: {}", request);
fileDatabaseService.expandCapacity(request);
return CommonResponse.success();
}
@Override
public CommonResponse<Void> renew(FileDatabaseRenewRequest request) {
log.info("renew workspace: {}", request);
fileDatabaseService.renew(request);
return CommonResponse.success();
}
@Override
public CommonResponse<Void> remove(FileDatabaseRemoveRequest request) {
log.info("remove workspace: {}", request);
fileDatabaseService.remove(request);
return CommonResponse.success();
}
@Override
public CommonResponse<Page<FileDatabaseInfoResponse>> search(FileDatabaseSearchRequest request) {
log.info("search workspace: {}", request);
return CommonResponse.success(fileDatabaseService.search(request));
}

View File

@ -17,6 +17,7 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import cn.axzo.apollo.workspace.api.workspace.WorkspaceApi;
import cn.axzo.apollo.workspace.api.workspace.req.GetSimpleWorkspaceReqV2;
@ -30,18 +31,20 @@ import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseActiveRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseAddWorkspaceRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseExpandCapacityRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseRemoveRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseRenewRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseSearchRequest;
import cn.axzo.nanopart.doc.api.filedb.response.FileDatabaseInfoResponse;
import cn.axzo.nanopart.doc.api.util.BizAssertions;
import cn.axzo.nanopart.doc.api.util.UUIDUtil;
import cn.axzo.nanopart.doc.dao.DocLogDao;
import cn.axzo.nanopart.doc.dao.FileDatabaseDao;
import cn.axzo.nanopart.doc.entity.FileDatabase;
import cn.axzo.nanopart.doc.entity.IndexNode;
import cn.axzo.nanopart.doc.entity.TemplateDatabase;
import cn.axzo.nanopart.doc.file.index.IndexManager;
import cn.axzo.nanopart.doc.file.index.IndexQueryService;
import cn.axzo.nanopart.doc.file.index.copy.CopyFileVisitor;
import cn.axzo.nanopart.doc.file.index.copy.SetScopeProcessor;
import cn.axzo.nanopart.doc.file.index.copy.CopiedOssFiles;
import cn.axzo.nanopart.doc.file.index.copy.SetCopyCopyFileVisitor;
import cn.axzo.nanopart.doc.file.index.domain.IndexNodes;
import cn.axzo.nanopart.doc.file.templatedb.TemplateDatabaseManager;
import cn.axzo.nanopart.doc.utils.AsyncUtils;
@ -64,9 +67,11 @@ public class FileDatabaseService {
private final IndexManager indexManager;
private final TransactionTemplate transaction;
private final AsyncUtils asyncUtils;
private final DocLogDao docLogDao;
@BizTransactional
public void addWorkspace(FileDatabaseAddWorkspaceRequest request) {
docLogDao.logRequest("addWorkspace", request.getWorkspaceId(), request);
String scopeUniqueCode = String.valueOf(request.getWorkspaceId());
TemplateDatabase templateDatabase = templateDatabaseManager.getOrThrow(request.getTemplateDatabaseCode());
FileDatabase savedDb = fileDatabaseDao.findForUpdateOrNull(templateDatabase.getScope(), scopeUniqueCode);
@ -82,40 +87,75 @@ public class FileDatabaseService {
db.setExpireDate(new Date(request.getExpiredDateMs()));
db.setAllowedCapacity(request.getAllowedCapacity());
db.setUsedCapacity(0);
db.setCreateAt(new Date());
db.setUpdateAt(new Date());
fileDatabaseDao.save(db);
}
public void active(FileDatabaseActiveRequest request) {
request.checkExpiredDate();
docLogDao.logRequest("active", request.getCode(), request);
FileDatabase db = fileDatabaseDao.getOrThrow(request.getCode());
if (db.isActivated())
return;
IndexNode template = indexManager.getOrThrow(db.getTemplateDatabaseCode());
Future<?> future = indexManager.async(() -> {
// don't inline in transaction
CopyFileVisitor copyFileVisitor = indexManager.createCopyFileVisitor(template, new SetScopeProcessor(db));
CopiedOssFiles copiedOssFiles = indexManager.copySubtreeOssFiles(template);
transaction.executeWithoutResult(unused -> {
FileDatabase reload = fileDatabaseDao.getForUpdateOrThrow(request.getCode());
if (reload.isActivated())
return;
indexManager.copySubTree(template, null, copyFileVisitor);
fileDatabaseDao.setActivated(reload.getCode());
indexManager.copySubTree(template, null, new SetCopyCopyFileVisitor(copiedOssFiles, db));
fileDatabaseDao.updateState(db.getCode(), FileDatabaseState.ACTIVATED);
});
});
asyncUtils.getOrTimeout(future, "激活等待超时, 激活任务在后台运行, 请稍后刷新查看");
}
@BizTransactional
public void expandCapacity(FileDatabaseExpandCapacityRequest request) {
public void fileSizeChanged(String indexNodeCode) {
IndexNode indexNode = indexManager.findOrNull(indexNodeCode);
if (indexNode == null || !indexNode.isFile())
return;
FileDatabase db = fileDatabaseDao.findForUpdateOrNull(indexNode.scopeCode());
if (db == null)
return;
fileDatabaseDao.updateUsedCapacity(db.getCode(), indexQueryService.getUsedFileSize(db));
db = fileDatabaseDao.findOrNull(db.getCode());
if (db.isActivated() && db.isCapacityExhausted())
fileDatabaseDao.updateState(db.getCode(), FileDatabaseState.CAPACITY_EXHAUSTED);
}
@BizTransactional
public void renew(FileDatabaseRenewRequest request) {
request.checkExpiredDate();
docLogDao.logRequest("renew", request.getCode(), request);
FileDatabase db = fileDatabaseDao.getForUpdateOrThrow(request.getCode());
BizAssertions.assertTrue(db.isActivated(), "只有激活状态的资料库才能扩容");
fileDatabaseDao.lambdaUpdate() //
.eq(FileDatabase::getCode, request.getCode()) //
.set(FileDatabase::getAllowedCapacity, request.getAllowedCapacity()) //
.set(FileDatabase::getExpireDate, new Date(request.getExpiredDateMs())) //
updateCapacity(request) //
.set(FileDatabase::getState,
db.isCapacityExhausted() ? FileDatabaseState.CAPACITY_EXHAUSTED : FileDatabaseState.ACTIVATED) //
.update();
}
@BizTransactional
public void expandCapacity(FileDatabaseExpandCapacityRequest request) {
request.checkExpiredDate();
docLogDao.logRequest("expandCapacity", request.getCode(), request);
FileDatabase db = fileDatabaseDao.getForUpdateOrThrow(request.getCode());
BizAssertions.assertTrue(db.isActivated(), "只有激活状态的资料库才能扩容");
updateCapacity(request).update();
}
private LambdaUpdateChainWrapper<FileDatabase> updateCapacity(FileDatabaseActiveRequest request) {
return fileDatabaseDao.lambdaUpdate() //
.eq(FileDatabase::getCode, request.getCode()) //
.set(FileDatabase::getAllowedCapacity, request.getAllowedCapacity()) //
.set(FileDatabase::getExpireDate, new Date(request.getExpiredDateMs()));
}
public void remove(FileDatabaseRemoveRequest request) {
docLogDao.logRequest("remove", request.getCode(), request);
fileDatabaseDao.deleteByCode(request.getCode());
}
@ -157,9 +197,7 @@ public class FileDatabaseService {
.orderByDesc(FileDatabase::getId)
.page(request.toPage());
// @formatter:on
IndexNodes indexNodes = IndexNodes.wrap(indexQueryService.get(page.getRecords().stream() //
.map(FileDatabase::getTemplateDatabaseCode) //
.collect(toList())));
IndexNodes indexNodes = IndexNodes.wrap(indexQueryService.get(page.getRecords()));
Map<Long, SimpleWorkspaceRes> id2Workspace = new HashMap<>();
List<Long> resultWorkspaceIds = page.getRecords().stream() //
.map(FileDatabase::getWorkspaceId) //
@ -189,5 +227,4 @@ public class FileDatabaseService {
.collect(toList());
return Page.toPage(request.getPage(), request.getPageSize(), page.getTotal(), records);
}
}

View File

@ -47,9 +47,10 @@ import lombok.extern.slf4j.Slf4j;
public class FileTemplateApiController implements FileTemplateApi {
private final FileTemplateManager fileTemplateManager;
private final IndexManager indexManager;;
private final IndexManager indexManager;
private final IndexQueryService indexQueryService;
private final AsyncUtils asyncUtils;
private final IndexNodeTreeUtils indexNodeTreeUtils;
@Override
public CommonResponse<String> createDir(FileTemplateCreateDirRequest request) {
@ -101,7 +102,7 @@ public class FileTemplateApiController implements FileTemplateApi {
public CommonResponse<List<IndexNodeInfo>> getTree() {
List<IndexNode> subtreeNodes = indexQueryService.getTree(IndexNodeScope.FILE_TEMPLATE);
FileTemplates fileTemplates = buildFileTemplates(subtreeNodes);
RootNode<IndexNodeInfo> root = IndexNodeTreeUtils.transform(subtreeNodes);
RootNode<IndexNodeInfo> root = indexNodeTreeUtils.transform(subtreeNodes);
root.walkDown(new ValueNodeRecursiveVisitor<IndexNodeInfo>() {
@Override
public WalkingDecision visit(ValueNode<IndexNodeInfo> node) {
@ -109,7 +110,7 @@ public class FileTemplateApiController implements FileTemplateApi {
return WalkingDecision.CONTINUE;
}
});
return CommonResponse.success(IndexNodeTreeUtils.collectSortedValueRoots(root));
return CommonResponse.success(indexNodeTreeUtils.valueRoots(root));
}
@Override

View File

@ -4,12 +4,9 @@ package cn.axzo.nanopart.doc.file.index;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import cn.axzo.nanopart.doc.api.domain.IndexNodeParentScope;
import cn.axzo.nanopart.doc.api.domain.IndexNodeScope;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@ -19,12 +16,15 @@ import cn.axzo.maokai.api.util.Ref;
import cn.axzo.maokai.api.vo.response.tree.RootNode;
import cn.axzo.maokai.api.vo.response.tree.TreeBuilder;
import cn.axzo.maokai.api.vo.response.tree.TreeUtils;
import cn.axzo.nanopart.doc.api.domain.IndexNodeParentScope;
import cn.axzo.nanopart.doc.api.domain.IndexNodeScope;
import cn.axzo.nanopart.doc.api.domain.NodeCreate;
import cn.axzo.nanopart.doc.api.domain.OssFile;
import cn.axzo.nanopart.doc.api.enums.FileFormat;
import cn.axzo.nanopart.doc.api.enums.IndexNodeType;
import cn.axzo.nanopart.doc.api.util.BizAssertions;
import cn.axzo.nanopart.doc.config.DocProps;
import cn.axzo.nanopart.doc.dao.DocLogDao;
import cn.axzo.nanopart.doc.dao.IndexNodeDao;
import cn.axzo.nanopart.doc.entity.IndexNode;
import cn.axzo.nanopart.doc.file.index.copy.ConnectNodeVisitor;
@ -52,6 +52,7 @@ public class IndexManager {
private final DocProps docProps;
private final FileBroadcaster fileBroadcaster;
private final TransactionTemplate transaction;
private final DocLogDao docLogDao;
@BizTransactional
public IndexNode createDatabase(NodeCreate create) {
@ -85,6 +86,7 @@ public class IndexManager {
BizAssertions.assertNotBlank(ossFile.getExtension(), "extension不能为空");
IndexNode fileNode = createFile(node, ossFile);
indexNodeDao.updateFileSize(fileNode.getCode(), ossFile.getSize());
fileBroadcaster.fireFileSizeChanged(fileNode.getCode());
return fileNode;
}
@ -103,10 +105,10 @@ public class IndexManager {
}
String operatorId = node.operatorId() == null ? "" : node.operatorId() + "";
IndexNode fileNode = indexSupport.createNode(node, IndexNodeType.FILE);
fileNode.getOrCreateAttributes().getOrCreateFileAttributes().setOssFileKey(ossFile.getOssFileKey());
fileNode.getOrCreateAttributes().getOrCreateFileAttributes().setFileFormat(ossFile.getFormat());
fileNode.getOrCreateAttributes().getOrCreateFileAttributes().setFileExtension(ossFile.getExtension());
fileNode.getOrCreateAttributes().getOrCreateFileAttributes().setCreatorId(operatorId);
fileNode.getOrCreateFileAttributes().setOssFileKey(ossFile.getOssFileKey());
fileNode.getOrCreateFileAttributes().setFileFormat(ossFile.getFormat());
fileNode.getOrCreateFileAttributes().setFileExtension(ossFile.getExtension());
fileNode.getOrCreateFileAttributes().setCreatorId(operatorId);
indexNodeDao.updateAttributes(fileNode);
return findOrNull(fileNode.getCode());
});
@ -123,6 +125,7 @@ public class IndexManager {
IndexNode indexNode = getOrThrow(code);
if (indexNode.getName().equals(newName))
return;
docLogDao.log("indexNodeRename", code, "newName", newName);
indexSupport.lockParentAndReleaseOnCommit(indexNode);
indexSupport.ensureChildNameNotUsed(indexNode, indexNode.getNodeType(), newName);
indexNodeDao.rename(code, newName);
@ -136,6 +139,7 @@ public class IndexManager {
@BizTransactional
public List<IndexNode> delete(String code) {
IndexNode indexNode = getOrThrow(code);
docLogDao.log("deleteIndexNode", code);
List<IndexNode> subtree = indexNodeDao.collectValidSubtreeNodes(indexNode);
indexNodeDao.stateDeleteSubtree(indexNode);
return subtree;
@ -151,17 +155,16 @@ public class IndexManager {
IndexNode destParentNode = findOrNull(destParentCode);
return async(() -> {
// don't inline in transaction
CopyFileVisitor copyNodeVisitor = createCopyFileVisitor(srcNode, null);
CopyFileVisitor copyNodeVisitor = new CopyFileVisitor(copySubtreeOssFiles(srcNode));
//noinspection SpringTransactionalMethodCallsInspection
return transaction.execute(unused -> copySubTree(srcNode, destParentNode, copyNodeVisitor));
});
}
public CopyFileVisitor createCopyFileVisitor(IndexNode srcNode, @Nullable Consumer<IndexNode> copyPostProcessor) {
public CopiedOssFiles copySubtreeOssFiles(IndexNode srcNode) {
BizAssertions.assertFalse(TransactionSynchronizationManager.isActualTransactionActive(), "不能在事务中使用");
List<String> ossFileKeys = indexNodeDao.collectValidSubtreeFileOssKeys(srcNode);
CopiedOssFiles copiedOssFiles = new CopiedOssFiles(ossClient.copy(ossFileKeys));
return new CopyFileVisitor(copiedOssFiles, copyPostProcessor);
return new CopiedOssFiles(ossClient.copy(ossFileKeys));
}
/**
@ -174,7 +177,10 @@ public class IndexManager {
BizAssertions.assertFalse(srcNode.isDirectory() && destParentNode.isFile(), "不能移动文件夹到文件下");
return async(() -> {
RootNode<IndexNode> moveRoot = TreeBuilder.build(collectValidSubtreeAsValueRoot(srcNode));
return transaction.execute(unused -> connectNodes(moveRoot, destParentNode));
return transaction.execute(unused -> {
docLogDao.log("indexNodeMove", srcCode, "srcCode", srcCode, "destParentCode", destParentCode);
return connectNodes(moveRoot, destParentNode);
});
});
}
@ -201,11 +207,11 @@ public class IndexManager {
// rebuild parent id, parent code and the most important path
connectRoot.walkDown(new ConnectNodeVisitor(destParent));
indexNodeDao.connectNodes(TreeUtils.collectValues(connectRoot));
IndexNode copiedRoot = connectRoot.getChildren().get(0).<IndexNode> asValueNode().getValue();
IndexNode rootNode = connectRoot.getChildren().get(0).<IndexNode> asValueNode().getValue();
IndexNodeParentScope nameScope = new IndexNodeParentScope() {
@Override
public IndexNodeScope nodeScope() {
return copiedRoot;
return rootNode;
}
@Override
@ -214,9 +220,9 @@ public class IndexManager {
}
};
// resolve the name issue
indexSupport.incrNameIfDuplicate(nameScope, copiedRoot.getNodeType(), copiedRoot);
indexSupport.incrNameIfDuplicate(nameScope, rootNode);
// get the coped root with full props
return indexNodeDao.findOrNull(copiedRoot.getCode());
return indexNodeDao.findOrNull(rootNode.getCode());
}
public IndexNode findOrNull(String code) {

View File

@ -1,21 +1,29 @@
package cn.axzo.nanopart.doc.file.index;
import static java.util.stream.Collectors.toList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import cn.axzo.nanopart.doc.api.domain.IndexNodeParentScope;
import cn.axzo.nanopart.doc.api.domain.IndexNodeScope;
import cn.axzo.nanopart.doc.api.enums.FileFormat;
import cn.axzo.nanopart.doc.api.enums.IndexNodeState;
import cn.axzo.nanopart.doc.api.enums.IndexNodeType;
import cn.axzo.nanopart.doc.api.index.request.IndexNodeSearchRequest;
import cn.axzo.nanopart.doc.config.DocProps;
import cn.axzo.nanopart.doc.dao.IndexNodeDao;
import cn.axzo.nanopart.doc.entity.IndexNode;
import cn.axzo.nanopart.doc.entity.domain.IndexNodeCodeProvider;
import cn.azxo.framework.common.model.Page;
import lombok.RequiredArgsConstructor;
@ -27,11 +35,14 @@ import lombok.RequiredArgsConstructor;
public class IndexQueryService {
private final IndexNodeDao indexNodeDao;
private final DocProps docProps;
public List<IndexNode> getTree(IndexNodeScope nodeScope) {
return indexNodeDao.scopeQuery(nodeScope) //
List<IndexNode> indexNodes = indexNodeDao.scopeQuery(nodeScope) //
.eq(IndexNode::getState, IndexNodeState.VALID) //
.list();
setIcons(indexNodes);
return indexNodes;
}
public Page<IndexNode> search(IndexNodeScope nodeScope, IndexNodeSearchRequest search) {
@ -40,6 +51,7 @@ public class IndexQueryService {
.like(IndexNode::getName, search.getName()) //
.in(CollectionUtils.isNotEmpty(search.getNodeTypes()), IndexNode::getNodeType, search.getNodeTypes()) //
.page(search.toPage());
setIcons(page.getRecords());
return Page.toPage(page.getPages(), page.getSize(), page.getTotal(), page.getRecords());
}
@ -51,10 +63,57 @@ public class IndexQueryService {
.list();
}
public List<IndexNode> get(List<String> codes) {
public List<IndexNode> getNameLike(IndexNodeParentScope parentScope, String name, IndexNodeType... nodeTypes) {
List<IndexNodeType> effectiveNodeTypes = nodeTypes == null ? Collections.emptyList() : Arrays.asList(nodeTypes);
return indexNodeDao.parentOrScopeRootQuery(parentScope) //
.like(IndexNode::getName, name) //
.in(!effectiveNodeTypes.isEmpty(), IndexNode::getNodeType, effectiveNodeTypes) //
.list();
}
public int getUsedFileSize(IndexNodeScope nodeScope) {
IndexNode sum = indexNodeDao.getBaseMapper().selectOne(new QueryWrapper<IndexNode>() //
.select("SUM(size)") //
.lambda() //
.eq(IndexNode::getContext, nodeScope.context()) //
.eq(IndexNode::getScope, nodeScope.scope()) //
.eq(IndexNode::getScopeCode, nodeScope.scopeCode()) //
.eq(IndexNode::getState, IndexNodeState.VALID));
return sum == null ? 0 : sum.getSize();
}
public List<IndexNode> get(List<? extends IndexNodeCodeProvider> nodes) {
if (CollectionUtils.isEmpty(nodes))
return Collections.emptyList();
List<String> codes = nodes.stream() //
.map(IndexNodeCodeProvider::indexNodeCode) //
.filter(StringUtils::isNotBlank) //
.collect(toList());
if (CollectionUtils.isEmpty(codes))
return Collections.emptyList();
return indexNodeDao.lambdaQuery().in(IndexNode::getCode, codes).list();
}
private void setIcons(List<IndexNode> indexNodes) {
for (IndexNode indexNode : indexNodes) {
if (StringUtils.isBlank(indexNode.getIcon()))
indexNode.setIcon(determineDefaultIcon(indexNode));
}
}
private String determineDefaultIcon(IndexNode indexNode) {
if (indexNode.getNodeType() == IndexNodeType.DATABASE)
return docProps.getDefaultIconDatabase();
if (indexNode.getNodeType() == IndexNodeType.DIRECTORY)
return docProps.getDefaultIconDir();
FileFormat fileFormat = indexNode.getOrCreateFileAttributes().getFileFormat();
if (fileFormat == FileFormat.EXCEL)
indexNode.setIcon(docProps.getDefaultIconExcel());
else if (fileFormat == FileFormat.WORD)
indexNode.setIcon(docProps.getDefaultIconWord());
else if (fileFormat == FileFormat.PDF)
indexNode.setIcon(docProps.getDefaultIconPdf());
return "no-icon";
}
}

View File

@ -30,7 +30,6 @@ import cn.axzo.nanopart.doc.dao.DocLockDao;
import cn.axzo.nanopart.doc.dao.IndexNodeDao;
import cn.axzo.nanopart.doc.entity.IndexNode;
import cn.axzo.nanopart.doc.entity.domain.Path;
import cn.axzo.nanopart.doc.file.index.copy.SetScopeProcessor;
import cn.axzo.nanopart.doc.file.index.domain.NameUsedException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -56,7 +55,7 @@ class IndexSupport {
validateBeforeCreateChild(parent, create, nodeType);
}
IndexNode child = new IndexNode();
new SetScopeProcessor(create.nodeScope()).accept(child);
IndexNode.setScope(child, create.nodeScope());
child.setCode(UUIDUtil.uuidString());
child.setParentId(parent == null ? IndexNode.TREE_ROOT_NODE_ID : parent.getId());
child.setParentCode(parent == null ? IndexNode.TREE_ROOT_NOE_CODE : parent.getCode());
@ -82,28 +81,27 @@ class IndexSupport {
}
private void validateBeforeCreateChild(IndexNode parent, NodeCreate child, IndexNodeType nodeType) {
BizAssertions.assertTrue(
indexNodeDao.validChildrenCount(parent.getCode()) < docProps.getIndexNodeMaxChildrenSize(),
"创建失败, 子节点数量不能超过 {}", docProps.getIndexNodeMaxChildrenSize());
BizAssertions.assertTrue(parent.path().depth() < docProps.getIndexNodeMaxDepth(), //
"节点深度超过限制{}, 无法再创建新节点", docProps.getIndexNodeMaxDepth());
if (nodeType == IndexNodeType.DIRECTORY)
BizAssertions.assertFalse(parent.isFile(), "不能在文件下建立文件夹");
BizAssertions.assertEquals(parent.getContext(), child.nodeScope().context(), //
"创建子节点时父子context不匹配: {}", child.parentCode());
BizAssertions.assertEquals(parent.getScope(), child.nodeScope().scope(), //
"创建子节点时父子scope不匹配: {}", child.nodeScope().scope());
BizAssertions.assertEquals(parent.getScopeCode(), child.nodeScope().scopeCode(), //
"创建子节点时父子scopeCode不匹配: {}", child.parentCode());
if (nodeType == IndexNodeType.DIRECTORY)
BizAssertions.assertFalse(parent.isFile(), "不能在文件下建立文件夹");
BizAssertions.assertTrue(
indexNodeDao.validChildrenCount(parent.getCode()) < docProps.getIndexNodeMaxChildrenSize(),
"创建失败, 子节点数量不能超过 {}", docProps.getIndexNodeMaxChildrenSize());
BizAssertions.assertTrue(parent.path().depth() < docProps.getIndexNodeMaxDepth(), //
"节点深度超过限制{}, 无法再创建新节点", docProps.getIndexNodeMaxDepth());
}
void incrNameIfDuplicate(IndexNodeParentScope parentScope, IndexNodeType nodeType, IndexNode rename) {
void incrNameIfDuplicate(IndexNodeParentScope parentScope, IndexNode rename) {
lockParentAndReleaseOnCommit(parentScope);
List<IndexNode> nameLikeNodes = indexNodeDao.findValidChildrenNameRightLike(parentScope, nodeType,
rename.getName());
List<IndexNode> nameLikeNodes = indexNodeDao.findValidChildrenNameRightLike(parentScope, rename);
if (nameLikeNodes.size() == 1 && IndexNode.idEquals(rename, nameLikeNodes.get(0)))
return;
Pattern pattern = Pattern.compile(Pattern.quote(rename.getName()) + "\\((\\d+)\\)");
Pattern pattern = Pattern.compile(Pattern.quote(rename.getName()) + "\\(副本(\\d+)\\)");
int maxSeq = 0;
for (IndexNode indexNode : nameLikeNodes) {
if (indexNode.getId().equals(rename.getId()))
@ -115,8 +113,7 @@ class IndexSupport {
maxSeq = seq;
}
}
maxSeq = maxSeq == 0 ? 1 : maxSeq + 1;
String newName = String.format("%s(%d)", rename.getName(), maxSeq);
String newName = String.format("%s(副本%d)", rename.getName(), maxSeq == 0 ? 1 : maxSeq + 1);
indexNodeDao.rename(rename.getCode(), newName);
}

View File

@ -1,10 +1,6 @@
package cn.axzo.nanopart.doc.file.index.copy;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import cn.axzo.maokai.api.vo.response.tree.ValueNode;
import cn.axzo.maokai.api.vo.response.tree.WalkingDecision;
import cn.axzo.nanopart.doc.api.domain.FileAttributes;
@ -18,8 +14,6 @@ import lombok.RequiredArgsConstructor;
public class CopyFileVisitor extends CopyNodeVisitor {
private final CopiedOssFiles copiedOssFiles;
@Nullable
private final Consumer<IndexNode> copyPostProcessor;
@Override
public WalkingDecision visit(ValueNode<IndexNode> node) {
@ -29,8 +23,6 @@ public class CopyFileVisitor extends CopyNodeVisitor {
String newOssFileKey = copiedOssFiles.getCopyOssFileKey(fileAttributes.getOssFileKey());
fileAttributes.setOssFileKey(newOssFileKey);
}
if (copyPostProcessor != null)
copyPostProcessor.accept(copy);
return super.visit(node);
}

View File

@ -16,6 +16,10 @@ public class CopyNodeVisitor extends ValueNodeRecursiveVisitor<IndexNode> {
public WalkingDecision visit(ValueNode<IndexNode> node) {
IndexNode copy = node.getValue();
copy.setId(0L);
copy.setRootCode("");
copy.setParentId(0L);
copy.setParentCode("");
copy.setPath("");
copy.setCode(UUIDUtil.uuidString());
return WalkingDecision.CONTINUE;
}

View File

@ -0,0 +1,27 @@
package cn.axzo.nanopart.doc.file.index.copy;
import cn.axzo.maokai.api.vo.response.tree.ValueNode;
import cn.axzo.maokai.api.vo.response.tree.WalkingDecision;
import cn.axzo.nanopart.doc.api.domain.IndexNodeScope;
import cn.axzo.nanopart.doc.entity.IndexNode;
/**
* @author yanglin
*/
public class SetCopyCopyFileVisitor extends CopyFileVisitor {
private final IndexNodeScope nodeScope;
public SetCopyCopyFileVisitor(CopiedOssFiles copiedOssFiles, IndexNodeScope nodeScope) {
super(copiedOssFiles);
this.nodeScope = nodeScope;
}
@Override
public WalkingDecision visit(ValueNode<IndexNode> node) {
IndexNode.setScope(node.getValue(), nodeScope);
return super.visit(node);
}
}

View File

@ -1,25 +0,0 @@
package cn.axzo.nanopart.doc.file.index.copy;
import java.util.function.Consumer;
import cn.axzo.nanopart.doc.api.domain.IndexNodeScope;
import cn.axzo.nanopart.doc.entity.IndexNode;
import lombok.RequiredArgsConstructor;
/**
* @author yanglin
*/
@RequiredArgsConstructor
public class SetScopeProcessor implements Consumer<IndexNode> {
private final IndexNodeScope nodeScope;;
@Override
public void accept(IndexNode indexNode) {
indexNode.setContext(nodeScope.context());
indexNode.setScope(nodeScope.scope());
indexNode.setScopeCode(nodeScope.scopeCode());
}
}

View File

@ -6,6 +6,7 @@ import org.springframework.stereotype.Component;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventProducer;
import cn.axzo.nanopart.doc.file.mq.message.DeleteOssFileMessage;
import cn.axzo.nanopart.doc.file.mq.message.FileSizeChangeMessage;
import cn.axzo.nanopart.doc.file.mq.message.RenameIndexNodeOssFileMessage;
import lombok.RequiredArgsConstructor;
@ -43,4 +44,16 @@ public class FileBroadcaster {
.build());
}
public void fireFileSizeChanged(String indexNodeCode) {
FileSizeChangeMessage message = new FileSizeChangeMessage();
message.setIndexNodeCode(indexNodeCode);
eventProducer.send(Event.builder() //
.eventCode(FileInternalEvent.INDEX_NODE_FILE_SIZE_CHANGED.getEventCode()) //
.shardingKey(indexNodeCode) //
.targetId(indexNodeCode) //
.targetType("index-node") //
.data(message) //
.build());
}
}

View File

@ -13,7 +13,9 @@ import lombok.RequiredArgsConstructor;
public enum FileInternalEvent {
INDEX_NODE_RENAME_OSS_FILE("nanopart-doc", "index-node-rename-oss-file", "修改index node oss文件名"),
OSS_FILE_DELETE("nanopart-doc", "oss-file-delete", "删除oss文件"),;
INDEX_NODE_FILE_SIZE_CHANGED("nanopart-doc", "index-node-file-size-changed", "修改index node文件大小发生变化"),
OSS_FILE_DELETE("nanopart-doc", "oss-file-delete", "删除oss文件"),
;
FileInternalEvent(String model, String name, String desc) {
this.eventCode = Event.EventCode.builder().module(model).name(name).build();

View File

@ -0,0 +1,39 @@
package cn.axzo.nanopart.doc.file.mq.filehandler;
import cn.axzo.nanopart.doc.file.filedb.FileDatabaseService;
import cn.axzo.nanopart.doc.file.mq.message.FileSizeChangeMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.nanopart.doc.file.mq.FileInternalEvent;
import lombok.RequiredArgsConstructor;
/**
* @author yanglin
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class UpdateFileDatabaseCapacityHandler implements EventHandler, InitializingBean {
private final EventConsumer eventConsumer;
private final FileDatabaseService fileDatabaseService;
@Override
public void onEvent(Event event, EventConsumer.Context context) {
log.info("receive file change message: {}", event);
FileSizeChangeMessage message = event.normalizedData(FileSizeChangeMessage.class);
fileDatabaseService.fileSizeChanged(message.getIndexNodeCode());
}
@Override
public void afterPropertiesSet() throws Exception {
eventConsumer.registerHandler(FileInternalEvent.INDEX_NODE_FILE_SIZE_CHANGED.getEventCode(), this);
}
}

View File

@ -0,0 +1,15 @@
package cn.axzo.nanopart.doc.file.mq.message;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class FileSizeChangeMessage extends MqMessage {
private String indexNodeCode;
}

View File

@ -4,6 +4,8 @@ package cn.axzo.nanopart.doc.file.templatedb;
import java.util.List;
import java.util.concurrent.Future;
import cn.axzo.nanopart.doc.api.templatedb.domain.DatabaseInfo;
import cn.axzo.nanopart.doc.api.templatedb.reqeust.TemplateDatabaseSearchRequest;
import org.springframework.web.bind.annotation.RestController;
import cn.axzo.basics.common.BeanMapper;
@ -46,6 +48,7 @@ public class TemplateDatabaseApiController implements TemplateDatabaseApi {
private final TemplateDatabaseQueryService templateDatabaseQueryService;
private final IndexQueryService indexQueryService;
private final AsyncUtils asyncUtils;
private final IndexNodeTreeUtils indexNodeTreeUtils;
@Override
public CommonResponse<String> createDatabase(TemplateDatabaseCreateDatabaseRequest request) {
@ -98,8 +101,8 @@ public class TemplateDatabaseApiController implements TemplateDatabaseApi {
@Override
public CommonResponse<List<IndexNodeInfo>> getTree() {
List<IndexNode> subtreeNodes = indexQueryService.getTree(IndexNodeScope.TEMPLATE_DATABASE);
RootNode<IndexNodeInfo> root = IndexNodeTreeUtils.transform(subtreeNodes);
return CommonResponse.success(IndexNodeTreeUtils.collectSortedValueRoots(root));
RootNode<IndexNodeInfo> root = indexNodeTreeUtils.transform(subtreeNodes);
return CommonResponse.success(indexNodeTreeUtils.valueRoots(root));
}
@Override
@ -110,6 +113,11 @@ public class TemplateDatabaseApiController implements TemplateDatabaseApi {
return CommonResponse.success(infoPage);
}
@Override
public CommonResponse<Page<DatabaseInfo>> searchDatabase(TemplateDatabaseSearchRequest request) {
return CommonResponse.success(templateDatabaseQueryService.searchDatabase(request));
}
@Override
public CommonResponse<TemplateDatabaseGetDatabaseOrDirInfoResponse> getDatabaseOrDirInfo(
TemplateDatabaseGetDatabaseOrDirInfoRequest request) {

View File

@ -23,6 +23,8 @@ import cn.axzo.nanopart.doc.file.index.domain.IndexNodes;
import cn.axzo.nanopart.doc.utils.BizTransactional;
import lombok.RequiredArgsConstructor;
import java.util.Date;
/**
* @author yanglin
*/
@ -41,6 +43,8 @@ public class TemplateDatabaseManager {
db.setCode(indexNode.getCode());
db.setScope(request.getScope());
db.setCoopType(request.getCoopType());
db.setCreateAt(new Date());
db.setUpdateAt(new Date());
templateDatabaseDao.save(db);
return indexNode.getCode();
}

View File

@ -1,14 +1,28 @@
package cn.axzo.nanopart.doc.file.templatedb;
import static java.util.stream.Collectors.toList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import cn.axzo.nanopart.doc.api.domain.IndexNodeParentScope;
import cn.axzo.nanopart.doc.api.enums.TemplateDatabaseState;
import cn.axzo.nanopart.doc.api.templatedb.domain.DatabaseInfo;
import cn.axzo.nanopart.doc.api.templatedb.reqeust.TemplateDatabaseGetDatabaseOrDirInfoRequest;
import cn.axzo.nanopart.doc.api.templatedb.reqeust.TemplateDatabaseSearchRequest;
import cn.axzo.nanopart.doc.api.templatedb.response.TemplateDatabaseGetDatabaseOrDirInfoResponse;
import cn.axzo.nanopart.doc.dao.TemplateDatabaseDao;
import cn.axzo.nanopart.doc.entity.IndexNode;
import cn.axzo.nanopart.doc.entity.TemplateDatabase;
import cn.axzo.nanopart.doc.file.index.IndexManager;
import cn.axzo.nanopart.doc.file.index.IndexQueryService;
import cn.axzo.nanopart.doc.file.index.domain.IndexNodes;
import cn.azxo.framework.common.model.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -22,6 +36,7 @@ public class TemplateDatabaseQueryService {
private final IndexManager indexManager;;
private final TemplateDatabaseDao templateDatabaseDao;
private final IndexQueryService indexQueryService;
public TemplateDatabaseGetDatabaseOrDirInfoResponse getDatabaseOrDirInfo(
TemplateDatabaseGetDatabaseOrDirInfoRequest request) {
@ -34,7 +49,7 @@ public class TemplateDatabaseQueryService {
response.getDatabaseOrDirInfo().setBizCode(indexNode.getBizCode());
response.getDatabaseOrDirInfo().setDescription(indexNode.getDescription());
response.getDatabaseOrDirInfo().setIcon(indexNode.getIcon());
response.getDatabaseOrDirInfo().setCustomIcon(indexNode.getOrCreateAttributes().isCustomIcon());
response.getDatabaseOrDirInfo().setCustomIcon(indexNode.getOrCreateAttributes().getCustomIcon());
if (indexNode.isDatabase()) {
TemplateDatabase template = templateDatabaseDao.findOrNull(request.getCode());
if (template != null) {
@ -46,4 +61,32 @@ public class TemplateDatabaseQueryService {
return response;
}
@SuppressWarnings("unchecked")
public Page<DatabaseInfo> searchDatabase(TemplateDatabaseSearchRequest request) {
List<String> codes = null;
if (StringUtils.isNotBlank(request.getName())) {
codes = indexQueryService.getNameLike( //
IndexNodeParentScope.TEMPLATE_DATABASE_ROOT, request.getName()).stream() //
.map(IndexNode::getCode) //
.collect(toList());
if (codes.isEmpty())
return Page.zero();
}
IPage<TemplateDatabase> page = templateDatabaseDao.lambdaQuery() //
.eq(TemplateDatabase::getState, TemplateDatabaseState.VALID) //
.in(codes != null, TemplateDatabase::getCode, codes) //
.page(request.toPage());
IndexNodes indexNodes = IndexNodes.wrap(indexQueryService.get(page.getRecords()));
List<DatabaseInfo> infoList = page.getRecords().stream().map(db -> {
IndexNode indexNode = indexNodes.findOrNull(db.getCode());
DatabaseInfo info = new DatabaseInfo();
info.setName(indexNode == null ? "" : indexNode.getName());
info.setCode(db.getCode());
info.setDatabaseType(db.getScope().getDatabaseType());
info.setFeeConfig(db.getFeeConfig());
return info;
}).collect(toList());
return Page.toPage(request.getPage(), page.getSize(), page.getTotal(), infoList);
}
}

View File

@ -24,6 +24,12 @@ import cn.axzo.oss.http.model.copyobject.ServerFileBatchCopyObjectRequest;
import cn.axzo.oss.http.model.copyobject.ServerFileBatchCopyObjectRequest.ServerFileCopyObjectRequest;
import cn.axzo.oss.http.model.copyobject.ServerFileBatchCopyObjectResponse;
import cn.axzo.oss.http.model.copyobject.ServerFileBatchCopyObjectResponse.ServerFileCopyObjectResponse;
import cn.axzo.oss.http.model.copyobject.ServerFileBatchDeleteObjectRequest;
import cn.axzo.oss.http.model.copyobject.ServerFileBatchDeleteObjectRequest.ServerFileDeleteObjectRequest;
import cn.axzo.oss.http.model.copyobject.ServerFileBatchDeleteObjectResponse;
import cn.axzo.oss.http.model.file.FileRenameRequest;
import cn.axzo.oss.http.model.file.FileRenameResponse;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -52,7 +58,7 @@ public class OssClient {
BizAssertions.assertNotEmpty(copyResponse.getResponses(), "单个拷贝oss文件失败, 返回了空列表结果");
}
finally {
log.info("单个拷贝oss文件, request={}, response={}", JSON.toJSONString(copyRequest),
log.info("oss copy, request={}, response={}", JSON.toJSONString(copyRequest),
JSON.toJSONString(copyResponse));
}
return copyResponse.getResponses().get(0).getTargetFileKey();
@ -83,7 +89,7 @@ public class OssClient {
BizAssertions.assertNotEmpty(copyResponse.getResponses(), "批量拷贝oss文件失败, 返回了空列表结果");
}
finally {
log.info("批量拷贝oss文件, request={}, response={}", JSON.toJSONString(copyRequest),
log.info("oss batch copy, request={}, response={}", JSON.toJSONString(copyRequest),
JSON.toJSONString(copyResponse));
}
return copyResponse.getResponses().stream().collect(
@ -91,11 +97,31 @@ public class OssClient {
}
public void rename(String ossFileKey, String newFullName) {
// TODO(yl): finish this
FileRenameRequest request = new FileRenameRequest();
request.setFileKey(ossFileKey);
request.setNewName(newFullName);
CommonResponse<FileRenameResponse> response = null;
try {
response = serverFileServiceApi.rename(request);
}
finally {
log.info("oss rename, request={}, response={}", JSON.toJSONString(request), JSON.toJSONString(response));
}
}
// TODO(yl): finish this
public void delete(String ossFileKey) {
ServerFileDeleteObjectRequest itemRequest = new ServerFileDeleteObjectRequest();
itemRequest.setFileKey(ossFileKey);
ServerFileBatchDeleteObjectRequest request = new ServerFileBatchDeleteObjectRequest();
request.setAppCode(docProps.getOssAppCode());
request.setDeleteObjects(Sets.newHashSet(itemRequest));
CommonResponse<ServerFileBatchDeleteObjectResponse> response = null;
try {
response = serverFileServiceApi.batchDeleteObject(request);
}
finally {
log.info("oss delete, request={}, response={}", JSON.toJSONString(request), JSON.toJSONString(response));
}
}
}

View File

@ -1,31 +1,49 @@
package cn.axzo.nanopart.doc.utils;
import static java.util.stream.Collectors.toList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import cn.axzo.maokai.api.vo.response.tree.RootNode;
import cn.axzo.maokai.api.vo.response.tree.TreeUtils;
import cn.axzo.maokai.api.vo.response.tree.ValueNode;
import cn.axzo.maokai.api.vo.response.tree.ValueNodeRecursiveVisitor;
import cn.axzo.maokai.api.vo.response.tree.WalkingDecision;
import cn.axzo.nanopart.doc.api.domain.IndexNodeInfo;
import cn.axzo.nanopart.doc.config.DocProps;
import cn.axzo.nanopart.doc.entity.IndexNode;
import static java.util.stream.Collectors.toList;
import lombok.RequiredArgsConstructor;
/**
* @author yanglin
*/
@Component
@RequiredArgsConstructor(onConstructor_ = { @Autowired })
public class IndexNodeTreeUtils {
public static RootNode<IndexNodeInfo> transform(List<IndexNode> subtreeNodes) {
private final DocProps docProps;
public RootNode<IndexNodeInfo> transform(List<IndexNode> subtreeNodes) {
RootNode<IndexNodeInfo> root = TreeUtils.transform(subtreeNodes, IndexNodeInfo.class);
TreeUtils.connectValueChildren(root);
return root;
}
public static List<IndexNodeInfo> collectSortedValueRoots(RootNode<IndexNodeInfo> root) {
public List<IndexNodeInfo> valueRoots(RootNode<IndexNodeInfo> root) {
Comparator<IndexNodeInfo> comparator = Comparator.comparingLong(IndexNodeInfo::getId);
TreeUtils.sortChildren(root, comparator);
root.walkDown(new ValueNodeRecursiveVisitor<IndexNodeInfo>() {
@Override
public WalkingDecision visit(ValueNode<IndexNodeInfo> node) {
node.getValue().setAddMoreChildren(node.getChildren().size() < docProps.getIndexNodeMaxChildrenSize());
return WalkingDecision.CONTINUE;
}
});
return TreeUtils.collectValueRoots(root).stream().sorted(comparator).collect(toList());
}