Merge branch 'master' into feature/REQ-3846

# Conflicts:
#	doc/doc-api/src/main/java/cn/axzo/nanopart/doc/api/anonymous/DocAnonymousDatabaseApi.java
#	doc/doc-server/src/main/java/cn/axzo/nanopart/doc/dao/IndexNodeDao.java
#	doc/doc-server/src/main/java/cn/axzo/nanopart/doc/file/anonymous/DocAnonymousDatabaseApiController.java
This commit is contained in:
xudawei 2025-04-28 10:41:54 +08:00
commit c33deb72eb
35 changed files with 498 additions and 138 deletions

View File

@ -12,7 +12,11 @@ import org.springframework.web.bind.annotation.RequestBody;
import cn.axzo.nanopart.doc.api.anonymous.request.AnonymousCreateDirRequest;
import cn.axzo.nanopart.doc.api.anonymous.request.AnonymousCreateFileRequest;
import cn.axzo.nanopart.doc.api.anonymous.request.AnonymousUploadFileRequest;
import cn.axzo.nanopart.doc.api.domain.IndexNodeInfo;
import cn.axzo.nanopart.doc.api.index.request.CopyNodeRequest;
import cn.axzo.nanopart.doc.api.index.request.DeleteNodeRequest;
import cn.axzo.nanopart.doc.api.index.request.GetNodeInfoRequest;
import cn.axzo.nanopart.doc.api.index.request.RenameNodeRequest;
import cn.azxo.framework.common.model.CommonResponse;
import java.util.Map;
@ -39,6 +43,14 @@ public interface DocAnonymousDatabaseApi {
@PostMapping("/api/anonymous/createFile")
CommonResponse<String> createFile(@RequestBody @Valid AnonymousCreateFileRequest request);
/**
* 创建新文件
*
* @return 文件编码, 需要由业务存储
*/
@PostMapping("/api/anonymous/createFile2")
CommonResponse<IndexNodeInfo> createFile2(@RequestBody @Valid AnonymousCreateFileRequest request);
/**
* 上传文件
*
@ -47,6 +59,14 @@ public interface DocAnonymousDatabaseApi {
@PostMapping("/api/anonymous/uploadFile")
CommonResponse<String> uploadFile(@RequestBody @Valid AnonymousUploadFileRequest request);
/**
* 上传文件
*
* @return 文件编码, 需要由业务存储
*/
@PostMapping("/api/anonymous/uploadFile2")
CommonResponse<IndexNodeInfo> uploadFile2(@RequestBody @Valid AnonymousUploadFileRequest request);
/**
* 拷贝节点 (文件)
*
@ -55,6 +75,24 @@ public interface DocAnonymousDatabaseApi {
@PostMapping("/api/anonymous/copy")
CommonResponse<String> copy(@RequestBody @Valid CopyNodeRequest request);
/**
* 删除节点 (文件)
*/
@PostMapping("/api/anonymous/delete")
CommonResponse<Void> delete(@RequestBody @Valid DeleteNodeRequest request);
/**
* 重命名节点 (文件)
*/
@PostMapping("/api/anonymous/rename")
CommonResponse<Void> rename(@RequestBody @Valid RenameNodeRequest request);
/**
* 获取节点信息
*/
@PostMapping("/api/anonymous/getNodeInfo")
CommonResponse<IndexNodeInfo> getNodeInfo(@RequestBody @Valid GetNodeInfoRequest request);
/**
* 批量获取节点信息集合
*/

View File

@ -38,6 +38,7 @@ abstract class NodeCreateAnonymous implements NodeCreate, IndexNodeScope {
/**
* 业务编码
*/
@NotBlank(message = "bizCode不能为空")
private String bizCode;
/**

View File

@ -89,6 +89,11 @@ public class IndexNodeInfo implements NodeValue, ValueContainer<IndexNodeInfo> {
*/
private String icon;
/**
* 文件的大小(bytes), 只有文件才存这个字段
*/
private Integer size;
/**
* 状态. VALID: 有效的
*/

View File

@ -4,7 +4,6 @@ package cn.axzo.nanopart.doc.api.domain;
import com.alibaba.fastjson.JSON;
import cn.axzo.nanopart.doc.api.enums.FileFormat;
import cn.axzo.nanopart.doc.api.util.BizAssertions;
import lombok.Getter;
import lombok.Setter;
@ -41,13 +40,6 @@ public class OssFile {
return ossFile;
}
public void validate() {
BizAssertions.assertNotNull(format, "format不能为空");
BizAssertions.assertTrue(size > 0, "size必须大于0");
BizAssertions.assertNotBlank(ossFileKey, "ossFileKey不能为空");
BizAssertions.assertNotBlank(extension, "后缀不能为空");
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -26,6 +26,10 @@ public enum DatabaseScope {
private final Boolean childNameDuplicatable;
private final Boolean limitChildrenCount;
public boolean isFeeAware() {
return databaseType != DatabaseType.NONE;
}
public Integer getWorkspaceType() {
if (this == ENT_DATABASE)
return 1;

View File

@ -17,7 +17,7 @@ public enum FileFormat {
EXCEL(true, "Excel", "xlsx"),
WORD(true, "Word", "docx"),
PDF(false, "PDF", "pdf"),
PPT(false, "PPT", "pptx");
PPT(true, "PPT", "pptx");
private final boolean creatable;
private final String readableName;

View File

@ -0,0 +1,25 @@
package cn.axzo.nanopart.doc.api.file;
import javax.validation.Valid;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import cn.axzo.nanopart.doc.api.domain.IndexNodeInfo;
import cn.axzo.nanopart.doc.api.index.request.GetNodeInfoRequest;
import cn.azxo.framework.common.model.CommonResponse;
/**
* @author yanglin
*/
@FeignClient(name = "nanopart", url = "${axzo.service.nanopart:http://nanopart:8080}")
public interface IndexNodeApi {
/**
* 获取节点信息
*/
@PostMapping("/api/indexNode/getNodeInfo")
CommonResponse<IndexNodeInfo> getNodeInfo(@RequestBody @Valid GetNodeInfoRequest request);
}

View File

@ -1,9 +1,11 @@
package cn.axzo.nanopart.doc.api.filedb.response;
import cn.axzo.nanopart.doc.api.enums.DatabaseType;
import java.math.BigDecimal;
import com.alibaba.fastjson.JSON;
import cn.axzo.nanopart.doc.api.enums.DatabaseType;
import cn.axzo.nanopart.doc.api.enums.FileDatabaseState;
import cn.axzo.nanopart.doc.api.templatedb.domain.TemplateDatabaseInfo;
import lombok.Getter;
@ -79,7 +81,7 @@ public class FileDatabaseInfoResponse {
/**
* 已使用的容量, 单位GB
*/
private Integer usedCapacity;
private BigDecimal usedCapacity;
/**
* 资料库信息

View File

@ -0,0 +1,21 @@
package cn.axzo.nanopart.doc.api.index.request;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
/**
* @author yanglin
*/
@Setter
@Getter
public class GetNodeInfoRequest {
/**
* 节点编码
*/
@NotBlank(message = "节点编码不能为空")
private String code;
}

View File

@ -1,6 +1,7 @@
package cn.axzo.nanopart.doc.dao;
import java.math.BigDecimal;
import java.util.Date;
import org.springframework.stereotype.Repository;
@ -19,9 +20,10 @@ import cn.axzo.nanopart.doc.mapper.FileDatabaseMapper;
@Repository
public class FileDatabaseDao extends ServiceImpl<FileDatabaseMapper, FileDatabase> {
public FileDatabase findForUpdateOrNull(DatabaseScope scope, String scopeUniqueCode) {
public FileDatabase findForUpdateOrNull(DatabaseScope scope, String templateDatabaseCode, String scopeUniqueCode) {
return lambdaQuery() //
.eq(FileDatabase::getScope, scope) //
.eq(FileDatabase::getTemplateDatabaseCode, templateDatabaseCode) //
.eq(FileDatabase::getScopeUniqueCode, scopeUniqueCode) //
.last("FOR UPDATE") //
.one();
@ -61,10 +63,11 @@ public class FileDatabaseDao extends ServiceImpl<FileDatabaseMapper, FileDatabas
.remove();
}
public void updateUsedCapacity(String code, int usedFileSize) {
public void updateUsedCapacity(String code, int usedFileSizeKb, BigDecimal usedFileSizeGb) {
lambdaUpdate() //
.eq(FileDatabase::getCode, code) //
.set(FileDatabase::getUsedCapacity, usedFileSize) //
.set(FileDatabase::getUsedCapacity, usedFileSizeGb) //
.set(FileDatabase::getUsedCapacityKb, usedFileSizeKb) //
.update();
}
@ -82,4 +85,13 @@ public class FileDatabaseDao extends ServiceImpl<FileDatabaseMapper, FileDatabas
.update();
}
public void expire() {
lambdaUpdate() //
.eq(FileDatabase::getState, FileDatabaseState.ACTIVATED) //
.isNotNull(FileDatabase::getExpireDate)
.le(FileDatabase::getExpireDate, new Date()) //
.set(FileDatabase::getState, FileDatabaseState.EXPIRED)
.update();
}
}

View File

@ -1,6 +1,7 @@
package cn.axzo.nanopart.doc.dao;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@ -16,6 +17,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.axzo.nanopart.doc.api.domain.IndexNodeParentScope;
import cn.axzo.nanopart.doc.api.domain.IndexNodeScope;
import cn.axzo.nanopart.doc.api.enums.DatabaseScope;
import cn.axzo.nanopart.doc.api.enums.IndexNodeState;
import cn.axzo.nanopart.doc.api.enums.IndexNodeType;
import cn.axzo.nanopart.doc.api.util.BizAssertions;
@ -103,10 +105,11 @@ public class IndexNodeDao extends ServiceImpl<IndexNodeMapper, IndexNode> {
.update();
}
public List<IndexNode> collectValidSubtreeNodes(IndexNode indexNode) {
public List<IndexNode> collectValidSubtreeNodes(IndexNode indexNode, IndexNodeType... nodeTypes) {
return lambdaQuery() //
.likeRight(IndexNode::getPath, indexNode.getPath()) //
.eq(IndexNode::getState, IndexNodeState.VALID) //
.in(nodeTypes.length > 0, IndexNode::getNodeType, Arrays.asList(nodeTypes)) //
.list();
}

View File

@ -3,6 +3,7 @@ package cn.axzo.nanopart.doc.dao;
import java.util.List;
import cn.axzo.nanopart.doc.api.util.BizAssertions;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Repository;
@ -18,6 +19,12 @@ import cn.axzo.nanopart.doc.mapper.TemplateDatabaseMapper;
@Repository
public class TemplateDatabaseDao extends ServiceImpl<TemplateDatabaseMapper, TemplateDatabase> {
public TemplateDatabase getOrThrow(String code) {
TemplateDatabase templateDb = findOrNull(code);
BizAssertions.assertNotNull(templateDb, "找不到资料库: {}", code);
return templateDb;
}
public TemplateDatabase findOrNull(String code) {
return lambdaQuery().eq(TemplateDatabase::getCode, code).one();
}

View File

@ -1,6 +1,7 @@
package cn.axzo.nanopart.doc.entity;
import java.math.BigDecimal;
import java.util.Date;
import com.alibaba.fastjson.JSON;
@ -76,7 +77,12 @@ public class FileDatabase extends BaseEntity<FileDatabase> implements IndexNodeS
/**
* 已使用的容量, 单位GB
*/
private Integer usedCapacity;
private BigDecimal usedCapacity;
/**
* 已使用的容量, 单位kb
*/
private Integer usedCapacityKb;
public boolean isActivated() {
return state == FileDatabaseState.ACTIVATED;

View File

@ -1,6 +1,8 @@
package cn.axzo.nanopart.doc.entity;
import cn.axzo.nanopart.doc.api.enums.FileFeeType;
import cn.axzo.nanopart.doc.api.enums.FileTemplateFeeType;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
@ -56,6 +58,10 @@ public class TemplateDatabase extends BaseEntity<TemplateDatabase> implements In
@TableField(typeHandler = FastjsonTypeHandler.class)
private DatabaseAccessConfig accessConfig;
public FileTemplateFeeType getFeeType() {
return feeConfig == null ? null : feeConfig.getFeeType();
}
@Override
public String indexNodeCode() {
return code;

View File

@ -25,7 +25,6 @@ public class BizCodeGenerator {
.append(create.nodeScope().scope() == DatabaseScope.ENT_DATABASE ? "ent" : "pro") //
.append(nodeType == IndexNodeType.DATABASE ? "space" : "folder") //
.append(create.nodeScope().context() == IndexNodeContext.TEMPLATE_DATABASE ? "oms" : workspaceId) //
.append("") //
.build();
create.setBizCode(bizCode);
}

View File

@ -19,12 +19,17 @@ import org.apache.commons.collections4.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import cn.axzo.basics.common.BeanMapper;
import cn.axzo.nanopart.doc.api.anonymous.DocAnonymousDatabaseApi;
import cn.axzo.nanopart.doc.api.anonymous.request.AnonymousCreateDirRequest;
import cn.axzo.nanopart.doc.api.anonymous.request.AnonymousCreateFileRequest;
import cn.axzo.nanopart.doc.api.anonymous.request.AnonymousUploadFileRequest;
import cn.axzo.nanopart.doc.api.domain.IndexNodeInfo;
import cn.axzo.nanopart.doc.api.domain.OssFile;
import cn.axzo.nanopart.doc.api.index.request.CopyNodeRequest;
import cn.axzo.nanopart.doc.api.index.request.DeleteNodeRequest;
import cn.axzo.nanopart.doc.api.index.request.GetNodeInfoRequest;
import cn.axzo.nanopart.doc.api.index.request.RenameNodeRequest;
import cn.axzo.nanopart.doc.entity.IndexNode;
import cn.axzo.nanopart.doc.file.index.IndexManager;
import cn.axzo.nanopart.doc.utils.AsyncUtils;
@ -57,13 +62,26 @@ public class DocAnonymousDatabaseApiController implements DocAnonymousDatabaseAp
return CommonResponse.success(indexManager.createFile(request, ossFile).getCode());
}
@Override
public CommonResponse<IndexNodeInfo> createFile2(AnonymousCreateFileRequest request) {
OssFile ossFile = indexManager.prepareEmptyOssFile(request, request.getFormat());
IndexNode fileNode = indexManager.createFile(request, ossFile);
return CommonResponse.success(BeanMapper.map(fileNode, IndexNodeInfo.class));
}
@Override
public CommonResponse<String> uploadFile(AnonymousUploadFileRequest request) {
log.info("upload file request:{}", request);
request.getOssFile().validate();
return CommonResponse.success(indexManager.uploadFile(request, request.getOssFile()).getCode());
}
@Override
public CommonResponse<IndexNodeInfo> uploadFile2(AnonymousUploadFileRequest request) {
log.info("upload file2 request:{}", request);
IndexNode fileNode = indexManager.uploadFile(request, request.getOssFile());
return CommonResponse.success(BeanMapper.map(fileNode, IndexNodeInfo.class));
}
@Override
public CommonResponse<String> copy(CopyNodeRequest request) {
log.info("copy request:{}", request);
@ -71,6 +89,26 @@ public class DocAnonymousDatabaseApiController implements DocAnonymousDatabaseAp
return CommonResponse.success(asyncUtils.getOrTimeout(future, 60, "克隆").getCode());
}
@Override
public CommonResponse<Void> delete(DeleteNodeRequest request) {
log.info("delete request:{}", request);
indexManager.delete(request.getCode());
return CommonResponse.success();
}
@Override
public CommonResponse<Void> rename(RenameNodeRequest request) {
log.info("rename request:{}", request);
indexManager.rename(request.getCode(), request.getNewName());
return CommonResponse.success();
}
@Override
public CommonResponse<IndexNodeInfo> getNodeInfo(GetNodeInfoRequest request) {
IndexNode indexNode = indexManager.getOrThrow(request.getCode());
return CommonResponse.success(BeanMapper.copyBean(indexNode, IndexNodeInfo.class));
}
/**
* 批量获取节点信息集合
*/

View File

@ -1,17 +1,10 @@
package cn.axzo.nanopart.doc.file.filedb;
import java.util.Date;
import org.springframework.stereotype.Component;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import cn.axzo.nanopart.doc.api.enums.FileDatabaseState;
import cn.axzo.nanopart.doc.dao.FileDatabaseDao;
import cn.axzo.nanopart.doc.entity.FileDatabase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* @author yanglin
@ -21,14 +14,11 @@ import lombok.RequiredArgsConstructor;
@SuppressWarnings({ "unused", "used by job" })
public class FileDatabaseExpireJob {
private final FileDatabaseDao fileDatabaseDao;
private final FileDatabaseService fileDatabaseService;
@XxlJob("fileDatabaseExpireJob")
public ReturnT<String> exec(String paramStr) {
fileDatabaseDao.lambdaUpdate() //
.eq(FileDatabase::getState, FileDatabaseState.ACTIVATED) //
.le(FileDatabase::getExpireDate, new Date()) //
.update();
fileDatabaseService.expire();
return ReturnT.SUCCESS;
}

View File

@ -3,6 +3,8 @@ package cn.axzo.nanopart.doc.file.filedb;
import static java.util.stream.Collectors.toList;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
@ -26,10 +28,13 @@ import cn.axzo.apollo.workspace.api.workspace.res.SimpleWorkspaceRes;
import cn.axzo.apollo.workspace.common.enums.WorkspaceTypeEnum;
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.DatabaseFeeConfig;
import cn.axzo.nanopart.doc.api.domain.IndexNodeScope;
import cn.axzo.nanopart.doc.api.enums.DatabaseScope;
import cn.axzo.nanopart.doc.api.enums.DatabaseType;
import cn.axzo.nanopart.doc.api.enums.FileDatabaseState;
import cn.axzo.nanopart.doc.api.enums.FileTemplateFeeType;
import cn.axzo.nanopart.doc.api.enums.IndexNodeContext;
import cn.axzo.nanopart.doc.api.enums.IndexNodeType;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseAbortRequest;
import cn.axzo.nanopart.doc.api.filedb.request.FileDatabaseActiveRequest;
@ -88,26 +93,26 @@ public class FileDatabaseService {
private FileDatabase addWorkspace0(FileDatabaseAddWorkspaceRequest request) {
String scopeUniqueCode = String.valueOf(request.getWorkspaceId());
TemplateDatabase templateDatabase = templateDatabaseQueryService.getOrThrow(request.getTemplateDatabaseCode());
FileDatabase savedDb = fileDatabaseDao.findForUpdateOrNull(templateDatabase.getScope(), scopeUniqueCode);
TemplateDatabase templateDb = templateDatabaseQueryService.getOrThrow(request.getTemplateDatabaseCode());
FileDatabase savedDb = fileDatabaseDao.findForUpdateOrNull(templateDb.getScope(), templateDb.getCode(),
scopeUniqueCode);
if (savedDb != null)
throw new FileDatabaseExistsException("无法重复创建, 请刷新页面查询已创建的记录");
SimpleWorkspaceRes workspace = RpcExternalUtil.rpcApolloProcessor(
() -> workspaceApi.getOne(request.getWorkspaceId()), "查询租户", request.getWorkspaceId());
BizAssertions.assertNotNull(workspace, "找不到对应的租户");
BizAssertions.assertEquals(workspace.getType(), templateDatabase.getScope().getWorkspaceType(),
"请求租户类型和资料库的租户类型不匹配");
BizAssertions.assertEquals(workspace.getType(), templateDb.getScope().getWorkspaceType(), "请求租户类型和资料库的租户类型不匹配");
FileDatabase db = new FileDatabase();
db.setCode(UUIDUtil.uuidString());
db.setTemplateDatabaseCode(templateDatabase.getCode());
db.setScope(templateDatabase.getScope());
db.setTemplateDatabaseCode(templateDb.getCode());
db.setScope(templateDb.getScope());
db.setScopeUniqueCode(scopeUniqueCode);
db.setWorkspaceId(request.getWorkspaceId());
db.setState(FileDatabaseState.ACTIVATING);
if (request.getExpiredDateMs() != null)
if (templateDb.getFeeType() == FileTemplateFeeType.ANNUAL && request.getExpiredDateMs() != null)
db.setExpireDate(new Date(request.getExpiredDateMs()));
db.setAllowedCapacity(request.getAllowedCapacity());
db.setUsedCapacity(0);
db.setUsedCapacity(BigDecimal.ZERO);
db.setCreateAt(new Date());
db.setUpdateAt(new Date());
fileDatabaseDao.save(db);
@ -140,7 +145,8 @@ public class FileDatabaseService {
public WalkingDecision visit(ValueNode<IndexNode> node) {
WalkingDecision walkingDecision = super.visit(node);
IndexNode indexNode = node.getValue();
BizCodeGenerator.gen(indexNode, indexNode.getNodeType(), db.getWorkspaceId());
if (indexNode.isDatabase() || indexNode.isDirectory())
BizCodeGenerator.gen(indexNode, indexNode.getNodeType(), db.getWorkspaceId());
return walkingDecision;
}
});
@ -167,14 +173,16 @@ public class FileDatabaseService {
@BizTransactional
public void fileSizeChanged(String indexNodeCode) {
IndexNode indexNode = indexManager.findOrNull(indexNodeCode);
if (indexNode == null || !indexNode.isFile())
if (indexNode == null || !indexNode.isFile() || indexNode.context() != IndexNodeContext.FILE_DATABASE)
return;
FileDatabase db = fileDatabaseDao.findForUpdateOrNull(indexNode.scopeCode());
if (db == null)
return;
int usedFileSize = indexQueryService.getUsedFileSize(db);
fileDatabaseDao.updateUsedCapacity(db.getCode(), usedFileSize);
if (db.isActivated() && db.getAllowedCapacity() <= usedFileSize)
int sizeByte = indexQueryService.getUsedFileSize(db);
BigDecimal sizeGb = BigDecimal.valueOf(sizeByte) //
.divide(BigDecimal.valueOf(1024 * 1024 * 1024), 2, RoundingMode.HALF_UP);
fileDatabaseDao.updateUsedCapacity(db.getCode(), sizeByte, sizeGb);
if (db.isActivated() && isCapacityExhausted(db.getAllowedCapacity(), sizeGb))
fileDatabaseDao.updateState(db.getCode(), FileDatabaseState.CAPACITY_EXHAUSTED);
}
@ -184,21 +192,31 @@ public class FileDatabaseService {
FileDatabase db = fileDatabaseDao.getForUpdateOrThrow(request.getCode());
updateCapacity(request) //
.set(FileDatabase::getState, //
request.getAllowedCapacity() <= db.getUsedCapacity() //
isCapacityExhausted(request.getAllowedCapacity(), db.getUsedCapacity()) //
? FileDatabaseState.CAPACITY_EXHAUSTED //
: FileDatabaseState.ACTIVATED) //
.update();
}
private boolean isCapacityExhausted(Integer allowedCapacity, BigDecimal usedCapacity) {
return BigDecimal.valueOf(allowedCapacity).compareTo(usedCapacity) <= 0;
}
@BizTransactional
public void expandCapacity(FileDatabaseExpandCapacityRequest request) {
docLogDao.logRequest("expandCapacity", request.getCode(), request);
FileDatabase db = fileDatabaseDao.getForUpdateOrThrow(request.getCode());
BizAssertions.assertTrue(db.isActivated(), "只有激活状态的资料库才能扩容");
DatabaseFeeConfig feeConfig = templateDatabaseQueryService.getOrThrow(
db.getTemplateDatabaseCode()).getFeeConfig();
if (feeConfig != null)
BizAssertions.assertTrue(feeConfig.getExtendable(), "资料库不支持扩容");
updateCapacity(request).update();
}
private LambdaUpdateChainWrapper<FileDatabase> updateCapacity(FileDatabaseActiveRequest request) {
FileDatabase db = fileDatabaseDao.getOrThrow(request.getCode());
TemplateDatabase templateDb = templateDatabaseQueryService.getOrThrow(db.getTemplateDatabaseCode());
Date expireDate = null;
if (request.getExpiredDateMs() != null && request.getExpiredDateMs() > 0)
expireDate = new Date(request.getExpiredDateMs());
@ -206,7 +224,8 @@ public class FileDatabaseService {
.eq(FileDatabase::getCode, request.getCode()) //
.set(FileDatabase::getAllowedCapacity, request.getAllowedCapacity()) //
.set(FileDatabase::getPurchaseDate, new Date()) //
.set(expireDate != null, FileDatabase::getExpireDate, expireDate);
.set(templateDb.getFeeType() == FileTemplateFeeType.ANNUAL && expireDate != null,
FileDatabase::getExpireDate, expireDate);
}
@BizTransactional
@ -294,4 +313,8 @@ public class FileDatabaseService {
.collect(toList());
return Page.toPage(request.getPage(), request.getPageSize(), page.getTotal(), records);
}
public void expire() {
this.fileDatabaseDao.expire();
}
}

View File

@ -67,7 +67,6 @@ public class FileTemplateApiController implements FileTemplateApi {
@Override
public CommonResponse<String> uploadFile(FileTemplateUploadFileRequest request) {
log.info("upload file request:{}", request);
request.getOssFile().validate();
return CommonResponse.success(fileTemplateManager.uploadFile(request));
}

View File

@ -1,13 +1,16 @@
package cn.axzo.nanopart.doc.file.filetemplate;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
import cn.axzo.nanopart.doc.api.domain.OssFile;
import cn.axzo.nanopart.doc.api.enums.FileTemplateState;
import cn.axzo.nanopart.doc.api.enums.IndexNodeContext;
import cn.axzo.nanopart.doc.api.enums.IndexNodeType;
import cn.axzo.nanopart.doc.api.filetemplate.request.FileTemplateCreateDirRequest;
import cn.axzo.nanopart.doc.api.filetemplate.request.FileTemplateCreateFileRequest;
@ -18,6 +21,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.file.index.SubtreeCopyEventTransactional;
import cn.axzo.nanopart.doc.file.index.domain.IndexNodes;
import cn.axzo.nanopart.doc.utils.BizTransactional;
import lombok.RequiredArgsConstructor;
@ -27,11 +31,11 @@ import lombok.RequiredArgsConstructor;
*/
@Component
@RequiredArgsConstructor
public class FileTemplateManager {
public class FileTemplateManager implements ApplicationListener<SubtreeCopyEventTransactional> {
private final IndexManager indexManager;
private final FileTemplateDao fileTemplateDao;
private final TransactionTemplate transactionTemplate;
private final TransactionTemplate transaction;
public String createDir(FileTemplateCreateDirRequest request) {
return indexManager.createDir(request).getCode();
@ -39,7 +43,7 @@ public class FileTemplateManager {
public String createFile(FileTemplateCreateFileRequest request) {
OssFile ossFile = indexManager.prepareEmptyOssFile(request, request.getFormat());
return transactionTemplate.execute(unused -> createFileTemplate(indexManager.createFile(request, ossFile)));
return transaction.execute(unused -> createFileTemplate(indexManager.createFile(request, ossFile)));
}
@BizTransactional
@ -48,11 +52,16 @@ public class FileTemplateManager {
}
private String createFileTemplate(IndexNode fileNode) {
FileTemplate fileTemplate = buildFileTemplate(fileNode);
fileTemplateDao.save(fileTemplate);
return fileNode.getCode();
}
private static FileTemplate buildFileTemplate(IndexNode fileNode) {
FileTemplate fileTemplate = new FileTemplate();
fileTemplate.setCode(fileNode.getCode());
fileTemplate.setState(FileTemplateState.UNPUBLISH);
fileTemplateDao.save(fileTemplate);
return fileNode.getCode();
return fileTemplate;
}
@BizTransactional
@ -81,4 +90,16 @@ public class FileTemplateManager {
.set(request.getState() != null, FileTemplate::getState, request.getState()) //
.update();
}
@Override
public void onApplicationEvent(SubtreeCopyEventTransactional event) {
if (event.getCopiedRoot().getContext() != IndexNodeContext.FILE_TEMPLATE)
return;
ArrayList<FileTemplate> fileTemplates = new ArrayList<>();
for (IndexNode fileNode : indexManager.collectValidSubtreeNodes(event.getCopiedRoot(), IndexNodeType.FILE))
fileTemplates.add(buildFileTemplate(fileNode));
if (!fileTemplates.isEmpty())
fileTemplateDao.saveBatch(fileTemplates);
}
}

View File

@ -8,6 +8,7 @@ import java.util.concurrent.Future;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
@ -52,16 +53,17 @@ public class IndexManager {
private final FileBroadcaster fileBroadcaster;
private final TransactionTemplate transaction;
private final DocLogDao docLogDao;
private final ApplicationContext applicationContext;
@BizTransactional
public IndexNode createDatabase(NodeCreate create) {
ensureChildNameNotUsed(create, IndexNodeType.DATABASE, true);
indexSupport.ensureChildNameNotUsed(create, IndexNodeType.DATABASE, true);
return indexSupport.createNode(create, IndexNodeType.DATABASE);
}
@BizTransactional
public IndexNode createDir(NodeCreate create) {
ensureChildNameNotUsed(create, IndexNodeType.DIRECTORY, true);
indexSupport.ensureChildNameNotUsed(create, IndexNodeType.DIRECTORY, true);
return indexSupport.createNode(create, IndexNodeType.DIRECTORY);
}
@ -69,7 +71,7 @@ public class IndexManager {
BizAssertions.assertTrue(format.creatable(), "无法创建: {}", format.readableName());
BizAssertions.assertFalse(TransactionSynchronizationManager.isActualTransactionActive(), "不能在事务中使用");
// check without lock
ensureChildNameNotUsed(create, IndexNodeType.FILE, false);
indexSupport.ensureChildNameNotUsed(create, IndexNodeType.FILE, false);
String fullFileName = String.format("%s.%s", create.name(), format.createFileExtension());
String emptyOssFileKey = format == FileFormat.WORD //
? docProps.getCreateFileOssFileKeyWord() //
@ -83,9 +85,10 @@ public class IndexManager {
@BizTransactional
public IndexNode uploadFile(NodeCreate node, OssFile ossFile) {
BizAssertions.assertNotNull(ossFile.getFormat(), "format不能为空");
BizAssertions.assertTrue(ossFile.getSize() > 0, "size必须大于0");
BizAssertions.assertNotBlank(ossFile.getOssFileKey(), "oosFileKey不能为空");
BizAssertions.assertNotBlank(ossFile.getExtension(), "extension不能为空");
if (node.nodeScope().scope().isFeeAware())
BizAssertions.assertTrue(ossFile.getSize() >= 0, "size必须大于等于0");
IndexNode fileNode = createFile(node, ossFile);
updateFileSize(fileNode, ossFile.getSize());
return fileNode;
@ -93,7 +96,7 @@ public class IndexManager {
@BizTransactional
public void updateFileSize(IndexNode fileNode, int size) {
if (fileNode == null || !fileNode.isFile())
if (fileNode == null || !fileNode.isFile() || size < 0)
return;
indexNodeDao.updateFileSize(fileNode.getCode(), size);
fileBroadcaster.fireFileSizeChanged(fileNode.getCode());
@ -105,7 +108,7 @@ public class IndexManager {
return transaction.execute(unused -> {
try {
// check with lock
ensureChildNameNotUsed(create, IndexNodeType.FILE, true);
indexSupport.ensureChildNameNotUsed(create, IndexNodeType.FILE, true);
}
catch (NameUsedException e) {
deleteOssFile.set(true);
@ -128,14 +131,6 @@ public class IndexManager {
}
}
private void ensureChildNameNotUsed(NodeCreate create, IndexNodeType nodeType, boolean withLock) {
if (indexSupport.childrenNameDuplicatable(create))
return;
if (withLock)
indexSupport.lockParentAndReleaseOnCommit(create);
indexSupport.ensureChildNameNotUsed(create, nodeType, create.name());
}
@BizTransactional
public void rename(String code, String newName) {
IndexNode indexNode = getOrThrow(code);
@ -158,7 +153,7 @@ public class IndexManager {
public List<IndexNode> delete(String code) {
IndexNode indexNode = getOrThrow(code);
docLogDao.log("indexNode:delete", code);
List<IndexNode> subtree = indexNodeDao.collectValidSubtreeNodes(indexNode);
List<IndexNode> subtree = collectValidSubtreeNodes(indexNode);
indexNodeDao.stateDeleteSubtree(indexNode);
return subtree;
}
@ -176,7 +171,7 @@ public class IndexManager {
IndexNode srcNode = getOrThrow(srcCode);
BizAssertions.assertTrue(indexNodeDao.validSubtreeFileCount(srcNode) <= docProps.getIndexNodeMaxCopyFileSize(),
"拷贝文件数超过限制: {}", docProps.getIndexNodeMaxCopyFileSize());
IndexNode destParentNode = determineDestParentNode(srcNode, destParentCode, "克隆");
IndexNode destParentNode = determineDestParentNode(srcNode, destParentCode, Op.COPY);
return async(() -> {
// don't inline in transaction
CopyFileVisitor copyNodeVisitor = new CopyFileVisitor(copySubtreeOssFiles(srcNode));
@ -193,7 +188,7 @@ public class IndexManager {
*/
public Future<IndexNode> asyncMove(String srcCode, @Nullable String destParentCode) {
IndexNode srcNode = getOrThrow(srcCode);
IndexNode destParentNode = determineDestParentNode(srcNode, destParentCode, "移动");
IndexNode destParentNode = determineDestParentNode(srcNode, destParentCode, Op.MOVE);
return async(() -> transaction.execute(unused -> {
docLogDao.log("indexNode:asyncMove", srcCode, "srcCode", srcCode, "destParentCode", destParentCode);
if (docProps.isLockSubtreeWhenMove())
@ -204,18 +199,20 @@ public class IndexManager {
}));
}
private IndexNode determineDestParentNode(IndexNode srcNode, String destParentCode, String op) {
private IndexNode determineDestParentNode(IndexNode srcNode, String destParentCode, Op op) {
IndexNode parent = StringUtils.isBlank(destParentCode) ? null : getOrThrow(destParentCode);
if (parent == null)
return null;
BizAssertions.assertFalse(srcNode.isDirectory() && parent.isFile(), "不能{}文件夹到文件下", op);
BizAssertions.assertFalse(srcNode.isSameNodeWith(parent), "不能{}到同一节点下", op);
BizAssertions.assertFalse(srcNode.isParentOf(parent), "不能{}到子节点下", op);
if (op == Op.MOVE)
BizAssertions.assertFalse(srcNode.isSameNodeWith(parent), "不能{}到同一节点下", op);
List<IndexNode> subtreeNodes = indexSupport.collectValidSubtreeAsValueRoot(srcNode);
int subtreeMaxDepth = IndexNodeTreeUtils.build(subtreeNodes).getChildren().stream() //
int subtreeMaxDepth = IndexNodeTreeUtils.build(subtreeNodes).getValueNodes().stream() //
.mapToInt(Node::getLevel) //
.max() //
.orElse(0);
// 这里不加1, 因为root node其实不是真实的值节点, 不用算在内
indexSupport.validateDepthWhenAddChild(parent.path().depth() + subtreeMaxDepth);
return parent;
}
@ -226,7 +223,9 @@ public class IndexManager {
RootNode<IndexNode> srcRoot = TreeUtils.transform(subtreeNodes, IndexNode.class, true);
srcRoot.walkDown(copyNodeVisitor);
indexNodeDao.saveBatch(TreeUtils.collectValues(srcRoot));
return connectNodes(srcRoot, destParent);
IndexNode copiedRoot = connectNodes(srcRoot, destParent);
applicationContext.publishEvent(new SubtreeCopyEventTransactional(this, src, copiedRoot));
return copiedRoot;
}
/**
@ -244,6 +243,10 @@ public class IndexManager {
return indexNodeDao.findOrNull(rootNode.getCode());
}
public List<IndexNode> collectValidSubtreeNodes(IndexNode indexNode, IndexNodeType... nodeTypes) {
return indexNodeDao.collectValidSubtreeNodes(indexNode, nodeTypes);
}
public IndexNode findOrNull(String code) {
if (StringUtils.isBlank(code))
return null;
@ -280,4 +283,14 @@ public class IndexManager {
});
}
@RequiredArgsConstructor
private enum Op {
MOVE("移动"), COPY("克隆");
private final String name;
@Override
public String toString() {
return name;
}
}
}

View File

@ -0,0 +1,31 @@
package cn.axzo.nanopart.doc.file.index;
import org.springframework.web.bind.annotation.RestController;
import cn.axzo.basics.common.BeanMapper;
import cn.axzo.nanopart.doc.api.domain.IndexNodeInfo;
import cn.axzo.nanopart.doc.api.file.IndexNodeApi;
import cn.axzo.nanopart.doc.api.index.request.GetNodeInfoRequest;
import cn.axzo.nanopart.doc.entity.IndexNode;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* @author yanglin
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class IndexNodeApiController implements IndexNodeApi {
private final IndexManager indexManager;
@Override
public CommonResponse<IndexNodeInfo> getNodeInfo(GetNodeInfoRequest request) {
IndexNode indexNode = indexManager.getOrThrow(request.getCode());
return CommonResponse.success(BeanMapper.copyBean(indexNode, IndexNodeInfo.class));
}
}

View File

@ -100,8 +100,8 @@ public class IndexSupport {
"创建失败, 子节点数量不能超过 {}", docProps.getIndexNodeMaxChildrenSize());
}
public void validateDepthWhenAddChild(int pendingDepth) {
BizAssertions.assertTrue(pendingDepth >= docProps.getIndexNodeMaxDepth(), //
public void validateDepthWhenAddChild(int targetDepth) {
BizAssertions.assertTrue(targetDepth <= docProps.getIndexNodeMaxDepth(), //
"节点深度超过限制{}, 无法再创建新节点", docProps.getIndexNodeMaxDepth());
}
@ -134,6 +134,14 @@ public class IndexSupport {
indexNodeDao.rename(rename.getCode(), newName);
}
void ensureChildNameNotUsed(NodeCreate create, IndexNodeType nodeType, boolean withLock) {
if (childrenNameDuplicatable(create))
return;
if (withLock)
lockParentAndReleaseOnCommit(create);
ensureChildNameNotUsed(create, nodeType, create.name());
}
void ensureChildNameNotUsed(IndexNodeParentScope parentScope, IndexNodeType nodeType, String childName) {
IndexNode child = indexNodeDao.findValidChildByName(parentScope, nodeType, childName);
// don't use BizAssertions.assertNull

View File

@ -0,0 +1,24 @@
package cn.axzo.nanopart.doc.file.index;
import org.springframework.context.ApplicationEvent;
import cn.axzo.nanopart.doc.entity.IndexNode;
import lombok.Getter;
/**
* @author yanglin
*/
@Getter
public class SubtreeCopyEventTransactional extends ApplicationEvent {
private final IndexNode srcNode;
private final IndexNode copiedRoot;
public SubtreeCopyEventTransactional(Object source, IndexNode srcNode, IndexNode copiedRoot) {
super(source);
this.srcNode = srcNode;
this.copiedRoot = copiedRoot;
}
}

View File

@ -29,6 +29,12 @@ public class IndexNodes {
.collect(toList());
}
public List<IndexNode> collectNodes(IndexNodeType nodeType) {
return indexNodes.stream() //
.filter(node -> node.getNodeType() == nodeType) //
.collect(toList());
}
public IndexNode findOrNull(String code) {
return indexNodes.stream() //
.filter(node -> node.getCode().equals(code)) //

View File

@ -78,7 +78,6 @@ public class TemplateDatabaseApiController implements TemplateDatabaseApi {
@Override
public CommonResponse<String> uploadFile(TemplateDatabaseUploadFileRequest request) {
log.info("upload file request:{}", request);
request.getOssFile().validate();
return CommonResponse.success(templateDatabaseManager.updateFile(request));
}

View File

@ -4,11 +4,13 @@ package cn.axzo.nanopart.doc.file.templatedb;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import cn.axzo.nanopart.doc.api.domain.OssFile;
import cn.axzo.nanopart.doc.api.enums.IndexNodeContext;
import cn.axzo.nanopart.doc.api.enums.IndexNodeType;
import cn.axzo.nanopart.doc.api.index.request.DeleteNodeRequest;
import cn.axzo.nanopart.doc.api.templatedb.reqeust.NodeCreateTemplateDatabase;
@ -23,6 +25,7 @@ import cn.axzo.nanopart.doc.entity.IndexNode;
import cn.axzo.nanopart.doc.entity.TemplateDatabase;
import cn.axzo.nanopart.doc.file.BizCodeGenerator;
import cn.axzo.nanopart.doc.file.index.IndexManager;
import cn.axzo.nanopart.doc.file.index.SubtreeCopyEventTransactional;
import cn.axzo.nanopart.doc.file.index.domain.IndexNodes;
import cn.axzo.nanopart.doc.utils.BizTransactional;
import lombok.RequiredArgsConstructor;
@ -32,7 +35,7 @@ import lombok.RequiredArgsConstructor;
*/
@Component
@RequiredArgsConstructor
public class TemplateDatabaseManager {
public class TemplateDatabaseManager implements ApplicationListener<SubtreeCopyEventTransactional> {
private final IndexManager indexManager;
private final TemplateDatabaseDao templateDatabaseDao;
@ -110,4 +113,19 @@ public class TemplateDatabaseManager {
.update();
}
@Override
public void onApplicationEvent(SubtreeCopyEventTransactional event) {
if (event.getCopiedRoot().getContext() != IndexNodeContext.TEMPLATE_DATABASE)
return;
if (event.getCopiedRoot().getNodeType() != IndexNodeType.DATABASE)
return;
TemplateDatabase templateDb = templateDatabaseDao.getOrThrow(event.getSrcNode().getCode());
TemplateDatabase db = new TemplateDatabase();
db.setCode(event.getCopiedRoot().getCode());
db.setScope(event.getCopiedRoot().getScope());
db.setCoopType(templateDb.getCoopType());
db.setCreateAt(new Date());
db.setUpdateAt(new Date());
templateDatabaseDao.save(db);
}
}

View File

@ -35,7 +35,7 @@ public class AsyncUtils {
return result;
}
catch (CannotAcquireLockException e) {
String message = String.format("%s等待超时, 可能正在移动节点, 请稍后刷新页面重试", op);
String message = String.format("%s等待超时, 请稍后重试", op);
log.warn(message, e);
throw fail(message);
}

View File

@ -1,8 +1,18 @@
package cn.axzo.nanopart.doc.wps.wpsbase;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.google.common.collect.Lists;
import cn.axzo.basics.common.util.AssertUtil;
import cn.axzo.basics.profiles.dto.basic.PersonProfileDto;
import cn.axzo.nanopart.doc.api.enums.WpsErrorCodeEnum;
import cn.axzo.nanopart.doc.api.wps.exception.WpsException;
import cn.axzo.nanopart.doc.api.wps.request.WpsPermissionRequest;
import cn.axzo.nanopart.doc.api.wps.request.WpsRenameRequest;
import cn.axzo.nanopart.doc.api.wps.request.WpsUsersRequest;
@ -12,8 +22,9 @@ import cn.axzo.nanopart.doc.api.wps.response.WpsPermissionResponse;
import cn.axzo.nanopart.doc.api.wps.response.WpsRenameResponse;
import cn.axzo.nanopart.doc.api.wps.response.WpsUsersResponse;
import cn.axzo.nanopart.doc.config.DocPermissionProps;
import cn.axzo.nanopart.doc.dao.IndexNodeDao;
import cn.axzo.nanopart.doc.entity.IndexNode;
import cn.axzo.nanopart.doc.file.index.IndexManager;
import cn.axzo.nanopart.doc.file.index.domain.NameUsedException;
import cn.axzo.nanopart.doc.integration.DocOssGateway;
import cn.axzo.nanopart.doc.integration.DocUserProfileGateway;
import cn.axzo.nanopart.doc.wps.support.WpsAssertUtil;
@ -23,14 +34,7 @@ import cn.axzo.oss.http.model.ApiSignUrlDownloadResponse;
import cn.axzo.oss.http.model.file.UpdateFileInfoRequest;
import cn.axzo.oss.http.model.file.UpdateFileInfoResponse;
import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* wps对接基础信息
@ -41,7 +45,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class WpsBaseManager {
private final IndexNodeDao indexNodeDao;
private final IndexManager indexManager;
private final DocOssGateway docOssGateway;
private final DocUserProfileGateway docUserProfileGateway;
@ -101,7 +105,7 @@ public class WpsBaseManager {
* 暂时全部开放权限REQ-35401期只是OMS没有文件权限功能待REQ-3540的2期建立权限后再对接
*/
public WpsPermissionResponse permission(WpsPermissionRequest request) {
IndexNode indexNode = indexNodeDao.findOrNull(request.getDocCode());
IndexNode indexNode = indexManager.findOrNull(request.getDocCode());
if (StringUtils.isNotBlank(indexNode.getOrCreateFileAttributes().getFileExtension())
&& indexNode.getOrCreateFileAttributes().getFileExtension().equalsIgnoreCase("pdf")) {
@ -152,13 +156,11 @@ public class WpsBaseManager {
|| StringUtils.isBlank(node.getAttributes().getFileAttributes().getOssFileKey())) {
return WpsRenameResponse.builder().updateFlag(false).build();
}
//TODO: 文件名重复
//try {
// 调用IndexManager.rename();
//} catch (NameUsedException e) {
// // 处理文件已被使用的情况
//}
indexNodeDao.rename(request.getDocCode(), request.getName());
try {
indexManager.rename(node.getCode(), request.getName());
} catch (NameUsedException e) {
throw new WpsException(WpsErrorCodeEnum.FILE_NAME_CONFLICT);
}
UpdateFileInfoResponse response = this.docOssGateway.updateFileInfo(UpdateFileInfoRequest.builder().fileKey(node.getAttributes().getFileAttributes().getOssFileKey()).build());
return WpsRenameResponse.builder().updateFlag(response.isUpdateFlag()).build();
}

View File

@ -62,40 +62,41 @@ public class GuideTenantProcedureStatusServiceImpl extends ServiceImpl<GuideTena
return Collections.emptyList();
}
// 将租户操作步骤数量与模板操作步骤数量进行对比如果模板操作步骤数量大于租户操作步骤数量则根据模板为租户初始化新步骤点
if (templateList.size() > procedureStatusList.size()) {
// 将该租户未初始化的步骤点进行初始化
List<Long> procedureStatusIdList = procedureStatusList.stream().map(GuideTenantProcedureStatus::getProcedureId).collect(Collectors.toList());
respList.addAll(templateList.stream()
.filter(t -> !procedureStatusIdList.contains(t.getId()))
.map(t -> {
GuideTenantProcedureStatusCreateReq createReq = new GuideTenantProcedureStatusCreateReq();
createReq.setWorkspaceId(req.getWorkspaceId());
createReq.setCategoryCode(t.getCategoryCode());
createReq.setProcedureId(t.getId());
createReq.setProcedureName(t.getName());
createReq.setStatus(GuideProcedureStatus.TODO);
create(createReq);
return BeanUtil.copyProperties(createReq, GuideTenantProcedureStatusListResp.class);
})
.collect(Collectors.toList()));
} else if (templateList.size() < procedureStatusList.size()) {
log.warn("操作步骤模板:{},租户&分类:{},租户操作步骤状态:{}", JSONUtil.toJsonStr(templateList), JSONUtil.toJsonStr(req), JSONUtil.toJsonStr(procedureStatusList));
throw new ServiceException("数据异常,操作步骤不存在,请联系管理人员");
// 将租户操作步骤数量与模板操作步骤数量进行对比是否需要根据模板为租户初始化新步骤点
// 将该租户未初始化的步骤点进行初始化
List<Long> procedureStatusIdList = procedureStatusList.stream().map(GuideTenantProcedureStatus::getProcedureId).distinct().collect(Collectors.toList());
List<GuideTenantProcedureStatusListResp> newInitProcedures = templateList.stream()
.filter(t -> !procedureStatusIdList.contains(t.getId()))
.map(t -> {
GuideTenantProcedureStatusCreateReq createReq = new GuideTenantProcedureStatusCreateReq();
createReq.setWorkspaceId(req.getWorkspaceId());
createReq.setCategoryCode(t.getCategoryCode());
createReq.setProcedureId(t.getId());
createReq.setProcedureName(t.getName());
createReq.setStatus(GuideProcedureStatus.TODO);
create(createReq);
return BeanUtil.copyProperties(createReq, GuideTenantProcedureStatusListResp.class);
})
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(newInitProcedures)) {
respList.addAll(newInitProcedures);
}
// 对租户操作步骤的权限码和跳转链接进行补充
Map<Long, GuideProcedureTemplateResp> templateMap = templateList.stream().collect(Collectors.toMap(GuideProcedureTemplateResp::getId, Function.identity(), (v1, v2) -> v1));
respList.forEach(r -> {
GuideProcedureTemplateResp t = templateMap.get(r.getProcedureId());
r.setDescription(t.getDescription());
r.setIsMust(t.getIsMust());
r.setAuthCode(t.getAuthCode());
r.setJumpUrl(t.getJumpUrl());
r.setJumpReport(t.getJumpReport());
r.setSort(t.getSort());
});
return respList;
return respList.stream()
.collect(Collectors.toMap(r -> r.getWorkspaceId() + "_" + r.getProcedureId(), Function.identity(), (v1, v2) -> v1))
.values().stream()
.peek(r -> {
GuideProcedureTemplateResp t = templateMap.get(r.getProcedureId());
r.setDescription(t.getDescription());
r.setIsMust(t.getIsMust());
r.setAuthCode(t.getAuthCode());
r.setJumpUrl(t.getJumpUrl());
r.setJumpReport(t.getJumpReport());
r.setSort(t.getSort());
})
.collect(Collectors.toList());
}
@Override
@ -127,12 +128,15 @@ public class GuideTenantProcedureStatusServiceImpl extends ServiceImpl<GuideTena
log.info("步骤已完成无需重复操作workspaceId: {}, procedureId{}", req.getWorkspaceId(), req.getProcedureId());
return;
}
tenantProcedure.setStatus(req.getStatus());
updateById(tenantProcedure);
lambdaUpdate()
.eq(GuideTenantProcedureStatus::getWorkspaceId, req.getWorkspaceId())
.eq(GuideTenantProcedureStatus::getProcedureId, req.getProcedureId())
.set(GuideTenantProcedureStatus::getStatus, req.getStatus())
.update();
}
@Override
public Long create(GuideTenantProcedureStatusCreateReq req) {
public synchronized Long create(GuideTenantProcedureStatusCreateReq req) {
// 若操作所属分类或操作名称为空则从对应模板获取
if (Objects.isNull(req.getCategoryCode()) || CharSequenceUtil.isBlank(req.getProcedureName())) {
GuideProcedureTemplateResp byProcedureId = procedureTemplateService.getByProcedureId(req.getProcedureId());

View File

@ -4,6 +4,7 @@ import cn.axzo.framework.domain.web.result.ApiPageResult;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.framework.domain.web.result.PageData;
import cn.axzo.nanopart.visa.api.changerecord.ChangeRecordApi;
import cn.axzo.nanopart.visa.api.enums.VisaConfirmBizTypeEnum;
import cn.axzo.nanopart.visa.api.enums.VisaStatusEnum;
import cn.axzo.nanopart.visa.api.request.BizActivityAssigneeDecisionReq;
import cn.axzo.nanopart.visa.api.request.ChangeRecordButtonOperationReq;
@ -30,6 +31,7 @@ import cn.axzo.nanopart.visa.server.service.ChangeRecordBillService;
import cn.axzo.nanopart.visa.server.service.ChangeRecordConfirmService;
import cn.axzo.nanopart.visa.server.service.ChangeRecordService;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
@ -147,7 +149,7 @@ public class ChangeRecordController implements ChangeRecordApi {
@Override
public ApiResult<List<VisaChangeApproveCreateReq.ApprovePersonInfo>> getVisaAllConfirm(FetchVisaAllConfirmReq req) {
return ApiResult.ok(changeRecordConfirmService.listAllConfirmByVisaId(req.getVisaId()));
return ApiResult.ok(changeRecordConfirmService.listAllConfirmByVisaId(req.getVisaId(), Lists.newArrayList(VisaConfirmBizTypeEnum.CONFIRM)));
}
@Override

View File

@ -46,6 +46,8 @@ public class VisaConfirmDto {
*/
private VisaConfirmBizTypeEnum bizType;
private List<VisaConfirmBizTypeEnum> bizTypes;
/**
* 确认人
*/

View File

@ -1,11 +1,14 @@
package cn.axzo.nanopart.visa.server.mq.listener.workflow.process;
import cn.axzo.framework.jackson.utility.JSON;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventProducer;
import cn.axzo.msg.center.api.MessageAPIV3;
import cn.axzo.msg.center.api.request.v3.MessageSendReqV3;
import cn.axzo.msg.center.api.response.v3.MessageSendRespV3;
import cn.axzo.msg.center.service.dto.PersonV3DTO;
import cn.axzo.nanopart.visa.api.enums.VisaConfirmBizTypeEnum;
import cn.axzo.nanopart.visa.api.enums.VisaStampStatusEnum;
import cn.axzo.nanopart.visa.api.enums.VisaStatusEnum;
import cn.axzo.nanopart.visa.api.enums.VisaTypeEnum;
@ -18,14 +21,17 @@ import cn.axzo.nanopart.visa.server.mq.listener.workflow.BasicLogSupport;
import cn.axzo.nanopart.visa.server.mq.producer.VisaChangeLogPayload;
import cn.axzo.nanopart.visa.server.rpc.VisaOrganizationalNodeUserGateway;
import cn.axzo.nanopart.visa.server.service.ChangeRecordBillService;
import cn.axzo.nanopart.visa.server.service.ChangeRecordConfirmService;
import cn.axzo.nanopart.visa.server.service.ChangeRecordRelationService;
import cn.axzo.nanopart.visa.server.service.ChangeRecordService;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.response.mq.ProcessInstanceDTO;
import cn.axzo.workflow.starter.handler.ProcessInstanceEventHandler;
import cn.azxo.framework.common.model.CommonResponse;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.groovy.util.Maps;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@ -35,6 +41,7 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static cn.axzo.nanopart.visa.api.constant.VisaConstant.FORM_FIELD_TOPIC;
import static cn.axzo.nanopart.visa.api.constant.VisaConstant.FORM_FIELD_WORKSPACE_NAME;
@ -54,11 +61,13 @@ import static cn.axzo.nanopart.visa.api.enums.VisaTypeEnum.TECHNOLOGY_APPROVED;
* @author wangli
* @since 2025-01-17 11:35
*/
@Slf4j
@Component
public class ProcessInstanceOfVisaAllEventHandler extends BasicLogSupport implements ProcessInstanceEventHandler {
protected final ChangeRecordService changeRecordService;
protected final ChangeRecordRelationService changeRecordRelationService;
protected final ChangeRecordBillService changeRecordBillService;
protected final ChangeRecordConfirmService changeRecordConfirmService;
protected final MessageAPIV3 noticeApi;
public final static List<String> SUPPORTED_DEFINITION_KEYS = Lists.newArrayList(
DESIGN_CHANGE.getProcessDefinitionKey(),
@ -72,11 +81,14 @@ public class ProcessInstanceOfVisaAllEventHandler extends BasicLogSupport implem
ChangeRecordService changeRecordService,
ChangeRecordRelationService changeRecordRelationService,
ChangeRecordBillService changeRecordBillService,
MessageAPIV3 noticeApi, RefreshableConfiguration refreshableConfiguration) {
ChangeRecordConfirmService changeRecordConfirmService,
MessageAPIV3 noticeApi,
RefreshableConfiguration refreshableConfiguration) {
super(eventProducer, visaOrganizationalNodeUserGateway);
this.changeRecordService = changeRecordService;
this.changeRecordRelationService = changeRecordRelationService;
this.changeRecordBillService = changeRecordBillService;
this.changeRecordConfirmService = changeRecordConfirmService;
this.noticeApi = noticeApi;
this.refreshableConfiguration = refreshableConfiguration;
}
@ -105,6 +117,10 @@ public class ProcessInstanceOfVisaAllEventHandler extends BasicLogSupport implem
public void onCompleted(ProcessInstanceDTO dto) {
String visaTypeDesc = parseVisaType(dto);
log.info("send complete notice");
sendCompleteNotice(dto);
log.info("send complete notice success");
updateChangeRecordApprovalStatus(dto, BpmnProcessInstanceResultEnum.APPROVED);
changeRecordService.lambdaUpdate()
@ -112,15 +128,23 @@ public class ProcessInstanceOfVisaAllEventHandler extends BasicLogSupport implem
.set(ChangeRecord::getStampStatus, VisaStampStatusEnum.UNPRINTED.name())
.update();
}
private void sendCompleteNotice(ProcessInstanceDTO dto) {
Map<String, Object> variables = dto.getVariables();
BpmnTaskDelegateAssigner initiator = dto.getInitiator();
MessageSendReqV3 completeNotice = new MessageSendReqV3();
completeNotice.setSender(PersonV3DTO.builder().build());
completeNotice.setReceivers(Lists.newArrayList(PersonV3DTO.builder().build()));
completeNotice.setReceivers(queryConfirm(dto));
completeNotice.setBizEventMappingCode(refreshableConfiguration.getProcessInstanceCompleteMsgEventCode());
completeNotice.setBizCode(dto.getBusinessKey());
String visaType = (String) variables.getOrDefault(WORKFLOW_VAR_VISA_TYPE_KEY, "");
String VisaTypeDesc = StringUtils.hasText(visaType) ? VisaTypeEnum.valueOf(visaType).getDesc() : "";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
completeNotice.setBizExtParams(new JSONObject(Maps.of(
"initiatorName", initiator.getAssignerName(),
"topic", variables.getOrDefault(FORM_FIELD_TOPIC, ""),
"visaTypeDesc", VisaTypeDesc,
"workspaceName", variables.getOrDefault(FORM_FIELD_WORKSPACE_NAME, ""),
"time", sdf.format(new Date())
)));
@ -129,7 +153,22 @@ public class ProcessInstanceOfVisaAllEventHandler extends BasicLogSupport implem
"ouId", variables.getOrDefault(IM_GROUP_BIZ_INFO_INITIATOR_OU_ID, ""),
"workspaceId", variables.getOrDefault(IM_GROUP_BIZ_INFO_INITIATOR_WORKSPACE_ID, "")
)));
noticeApi.send(completeNotice);
CommonResponse<MessageSendRespV3> response = noticeApi.send(completeNotice);
if (Objects.nonNull(response) && response.getCode() == 200) {
log.info("send complete notice result: {}", JSON.toJSONString(response.getData()));
} else {
log.error("send complete notice failed: {}", Objects.isNull(response) ? "response is null" : JSON.toJSONString(response.getMsg()));
}
}
private List<PersonV3DTO> queryConfirm(ProcessInstanceDTO dto) {
Long visaId = Long.valueOf(dto.getBusinessKey());
return changeRecordConfirmService.listAllConfirmByVisaId(visaId, Lists.newArrayList(VisaConfirmBizTypeEnum.CREATE, VisaConfirmBizTypeEnum.CONFIRM))
.stream().map(e -> PersonV3DTO.builder()
.id(e.getPersonId())
.name(e.getRealName())
.imReceiveModel(new PersonV3DTO.ReceiveModel(e.getOuId(), e.getWorkspaceId()))
.build()).collect(Collectors.toList());
}
/**
@ -160,6 +199,10 @@ public class ProcessInstanceOfVisaAllEventHandler extends BasicLogSupport implem
*/
@Override
public void onRejected(ProcessInstanceDTO dto) {
log.info("send reject notice");
sendRejectNotice(dto);
log.info("send reject notice success");
BpmnTaskDelegateAssigner lastOperationAssigner = dto.getLastOperationAssigner();
ChangeRecordLog log = ChangeRecordLog.builder()
.visaId(Long.valueOf(dto.getBusinessKey()))
@ -171,14 +214,23 @@ public class ProcessInstanceOfVisaAllEventHandler extends BasicLogSupport implem
updateChangeRecordApprovalStatus(dto, BpmnProcessInstanceResultEnum.REJECTED);
}
private void sendRejectNotice(ProcessInstanceDTO dto) {
Map<String, Object> variables = dto.getVariables();
BpmnTaskDelegateAssigner initiator = dto.getInitiator();
MessageSendReqV3 rejectNotice = new MessageSendReqV3();
rejectNotice.setSender(PersonV3DTO.builder().build());
rejectNotice.setReceivers(Lists.newArrayList(PersonV3DTO.builder().build()));
rejectNotice.setBizEventMappingCode(refreshableConfiguration.getProcessInstanceCompleteMsgEventCode());
rejectNotice.setReceivers(queryConfirm(dto));
rejectNotice.setBizEventMappingCode(refreshableConfiguration.getProcessInstanceRejectMsgEventCode());
rejectNotice.setBizCode(dto.getBusinessKey());
String visaType = (String) variables.getOrDefault(WORKFLOW_VAR_VISA_TYPE_KEY, "");
String VisaTypeDesc = StringUtils.hasText(visaType) ? VisaTypeEnum.valueOf(visaType).getDesc() : "";
rejectNotice.setBizExtParams(new JSONObject(Maps.of(
"initiatorName", initiator.getAssignerName(),
"topic", variables.getOrDefault(FORM_FIELD_TOPIC, ""),
"visaTypeDesc", VisaTypeDesc,
"workspaceName", variables.getOrDefault(FORM_FIELD_WORKSPACE_NAME, ""),
"reason", dto.getReason()
)));
@ -187,7 +239,13 @@ public class ProcessInstanceOfVisaAllEventHandler extends BasicLogSupport implem
"ouId", variables.getOrDefault(IM_GROUP_BIZ_INFO_INITIATOR_OU_ID, ""),
"workspaceId", variables.getOrDefault(IM_GROUP_BIZ_INFO_INITIATOR_WORKSPACE_ID, "")
)));
noticeApi.send(rejectNotice);
CommonResponse<MessageSendRespV3> response = noticeApi.send(rejectNotice);
if (Objects.nonNull(response) && response.getCode() == 200) {
log.info("send reject notice result: {}", JSON.toJSONString(response.getData()));
} else {
log.error("send reject notice failed: {}", Objects.isNull(response) ? "response is null" : JSON.toJSONString(response.getMsg()));
}
}
@Override

View File

@ -76,7 +76,7 @@ public interface ChangeRecordConfirmService {
* @param visaId
* @return
*/
List<VisaChangeApproveCreateReq.ApprovePersonInfo> listAllConfirmByVisaId(Long visaId);
List<VisaChangeApproveCreateReq.ApprovePersonInfo> listAllConfirmByVisaId(Long visaId, List<VisaConfirmBizTypeEnum> bizTypes);
/**
* 构建公司的MaporgIdAndNameMap,key:ouId,value:ouName

View File

@ -418,6 +418,7 @@ public class ChangeRecordConfirmServiceImpl extends ServiceImpl<ChangeRecordConf
.in(CollectionUtils.isNotEmpty(dto.getProjectIds()), ChangeRecordConfirm::getProjectId, dto.getProjectIds())
.eq(Objects.nonNull(dto.getType()), ChangeRecordConfirm::getType, dto.getType())
.eq(Objects.nonNull(dto.getBizType()), ChangeRecordConfirm::getBizType, dto.getBizType())
.in(CollectionUtils.isNotEmpty(dto.getBizTypes()), ChangeRecordConfirm::getBizType, dto.getBizTypes())
.in(CollectionUtils.isNotEmpty(dto.getVisaTypes()), ChangeRecordConfirm::getVisaType, dto.getVisaTypes())
.eq(ChangeRecordConfirm::getIsDelete, 0);
}
@ -450,10 +451,10 @@ public class ChangeRecordConfirmServiceImpl extends ServiceImpl<ChangeRecordConf
}
@Override
public List<VisaChangeApproveCreateReq.ApprovePersonInfo> listAllConfirmByVisaId(Long visaId) {
public List<VisaChangeApproveCreateReq.ApprovePersonInfo> listAllConfirmByVisaId(Long visaId, List<VisaConfirmBizTypeEnum> bizTypes) {
VisaConfirmDto dto = VisaConfirmDto.builder()
.visaId(visaId)
.bizType(VisaConfirmBizTypeEnum.CONFIRM).build();
.bizTypes(bizTypes).build();
List<ChangeRecordConfirm> list = this.findByCondition(dto);
if (CollectionUtils.isEmpty(list)) {
return Lists.newArrayList();