feat(REQ-3769) - 增加模板关联文档克隆、文档列表查询等功能

This commit is contained in:
wangli 2025-04-01 13:48:28 +08:00
parent 77cfdcd0f5
commit f3049d20c2
12 changed files with 261 additions and 9 deletions

View File

@ -161,6 +161,11 @@
<artifactId>org-api</artifactId>
<version>${axzo-dependencies.org.version}</version>
</dependency>
<dependency>
<groupId>cn.axzo.nanopart</groupId>
<artifactId>doc-api</artifactId>
<version>${axzo-dependencies.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -8,6 +8,7 @@ import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelUpdateDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocOrderDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocUpdateDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigQueryDTO;
@ -259,6 +260,17 @@ public interface ProcessModelApi {
@InvokeMode(SYNC)
CommonResponse<BpmPageResult<DocBaseVO>> docPage(@Validated @RequestBody DocSearchDTO dto);
/**
* 获取指定模板的原始文档列表
*
* @param dto
* @return
*/
@Operation(summary = "根据业务 ID 获取模型文档列表,自动适配公共模板和代运营")
@PostMapping(value = "/api/process/model/doc/list")
@InvokeMode(SYNC)
CommonResponse<List<DocBaseVO>> docList(DocQueryDTO dto);
/**
* 添加关联文档
*
@ -279,6 +291,17 @@ public interface ProcessModelApi {
@InvokeMode(SYNC)
CommonResponse<Boolean> updateDoc(@Validated @RequestBody DocUpdateDTO dto);
/**
* 克隆关联文档
*
* @param docId
* @return
*/
@Operation(summary = "克隆关联文档")
@PostMapping(value = "/api/process/model/doc/clone")
@InvokeMode(SYNC)
CommonResponse<Boolean> cloneDoc(@RequestParam("id") Long docId);
/**
* 删除关联文档
*

View File

@ -21,7 +21,11 @@ public enum BpmnModelRespCode implements IModuleRespCode {
MODEL_IS_DISABLE("006", "模型已经被停用"),
MODEL_FILE_TAG_DUPLICATE("007", "模型关联的文档业务标签重复"),
MODEL_FILE_TAG_EXISTS("008", "模型关联的文档业务标签已存在"),
MODEL_FILE_NOT_EXISTS("009", "模型关联的文档不存在或已被删除")
MODEL_FILE_NOT_EXISTS("009", "模型关联的文档不存在或已被删除"),
MODEL_FILE_CLONE_ERROR("010", "克隆文档失败, 原因:【{}】"),
MODEL_FILE_TYPE_CLONE_NOT_SUPPORT("011", "不支持的文档类型克隆"),
MODEL_FILE_CONTENT_DATA_ERROR("012", "文档内容数据异常"),
MODEL_FILE_QUERY_ERROR("013", "文档搜索参数实例 ID 和业务 ID 不能同时为空"),
;
private final String code;

View File

@ -83,7 +83,6 @@ public class DocCreateDTO implements Serializable {
* 业务标签
*/
@ApiModelProperty(value = "业务标签")
@NotBlank(message = "业务标签不能为空")
private String tag;
/**

View File

@ -0,0 +1,35 @@
package cn.axzo.workflow.common.model.request.bpmn.model.doc;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 流程关联文档搜索入参模型
*
* @author wangli
* @since 2025-03-31 09:46
*/
@ApiModel("流程关联文档搜索入参模型")
@Data
public class DocQueryDTO {
/**
* 流程实例 ID
*/
@ApiModelProperty(value = "流程实例 ID")
private String processInstanceId;
/**
* 流程定义 KEY业务 ID
*/
@ApiModelProperty(value = "流程定义 KEY业务 ID")
private String processDefinitionKey;
/**
* 租户 ID对应工作台 ID
*/
@ApiModelProperty(value = "租户 ID对应工作台 ID")
private String tenantId;
}

View File

@ -1,6 +1,7 @@
package cn.axzo.workflow.core.repository.entity;
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@ -36,6 +37,7 @@ public class ExtAxModelDoc extends BaseEntity<ExtAxModelDoc> {
* hp 对应 docContent ID
* pdf 对应 oss 中的 fileKey
*/
@TableField(updateStrategy = FieldStrategy.NEVER)
private String fileRelationId;
/**

View File

@ -16,4 +16,6 @@ public interface ExtAxDocContentService {
ExtAxDocContent updateContent(String content, FileTypeEnum fileType, String docId);
ExtAxDocContent deleteContent(String docId);
String getContent(Long docId);
}

View File

@ -2,11 +2,14 @@ package cn.axzo.workflow.core.service;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocOrderDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.model.doc.DocBaseVO;
import java.util.List;
/**
* 模型关联文档
*
@ -23,6 +26,16 @@ public interface ExtAxModelDocService {
*/
BpmPageResult<DocBaseVO> docPage(DocSearchDTO dto);
List<DocBaseVO> docList(DocQueryDTO dto);
/**
* 获取指定文档
*
* @param docId
* @return
*/
DocBaseVO get(Long docId);
/**
* 模型文档创建
*
@ -39,6 +52,14 @@ public interface ExtAxModelDocService {
*/
Boolean updateDoc(DocUpdateDTO dto);
/**
* 克隆文档
*
* @param docId
* @return
*/
Boolean cloneDoc(Long docId);
/**
* 模型文档删除
*

View File

@ -1,6 +1,7 @@
package cn.axzo.workflow.core.service.impl;
import cn.axzo.workflow.common.enums.FileTypeEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.repository.entity.ExtAxDocContent;
import cn.axzo.workflow.core.repository.mapper.ExtAxDocContentMapper;
import cn.axzo.workflow.core.service.ExtAxDocContentService;
@ -9,6 +10,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Objects;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_CONTENT_DATA_ERROR;
/**
* 文档模板内容
@ -49,4 +53,13 @@ public class ExtAxDocContentServiceImpl implements ExtAxDocContentService {
extAxDocContentMapper.deleteById(entity);
return entity;
}
@Override
public String getContent(Long docId) {
ExtAxDocContent content = extAxDocContentMapper.selectOne(ExtAxDocContent::getFileId, docId);
if (Objects.isNull(content)) {
throw new WorkflowEngineException(MODEL_FILE_CONTENT_DATA_ERROR);
}
return content.getContent();
}
}

View File

@ -6,6 +6,7 @@ import cn.axzo.workflow.common.enums.OrderEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocOrderDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
@ -18,18 +19,27 @@ import cn.axzo.workflow.core.service.ExtAxModelDocService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_TAG_DUPLICATE;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_TAG_EXISTS;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_TYPE_CLONE_NOT_SUPPORT;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID;
/**
* 模型关联的文档
@ -40,7 +50,10 @@ import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_TAG_EXIS
@Service
@Slf4j
public class ExtAxModelDocServiceImpl implements ExtAxModelDocService {
@Resource
private RuntimeService runtimeService;
@Resource
private RepositoryService repositoryService;
@Resource
private ExtAxModelDocMapper extAxModelDocMapper;
@Resource
@ -56,6 +69,46 @@ public class ExtAxModelDocServiceImpl implements ExtAxModelDocService {
return new BpmPageResult<>(list, page.getTotal());
}
@Override
public List<DocBaseVO> docList(DocQueryDTO dto) {
String tenantId = StringUtils.hasText(dto.getTenantId()) ? dto.getTenantId() : NO_TENANT_ID;
ProcessDefinition processDefinition;
if (StringUtils.hasText(dto.getProcessDefinitionKey())) {
List<ProcessDefinition> definitions = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(dto.getProcessDefinitionKey())
.processDefinitionWithoutTenantId()
.list();
Optional<ProcessDefinition> first = definitions.stream().filter(i -> i.getTenantId().equals(tenantId))
.max(Comparator.comparing(ProcessDefinition::getVersion));
processDefinition = first.orElseGet(() -> definitions.stream().filter(i -> i.getTenantId().equals(NO_TENANT_ID)).max(Comparator.comparing(ProcessDefinition::getVersion)).get());
} else {
ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult();
if (Objects.isNull(instance)) {
throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
}
processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(instance.getProcessDefinitionId()).singleResult();
}
ExtAxModelDoc query = new ExtAxModelDoc();
query.setModelKey(processDefinition.getKey());
query.setStatus(true);
query.setTenantId(processDefinition.getTenantId());
List<ExtAxModelDoc> docs = extAxModelDocMapper.selectList(buildQueryWrapper(query));
return BeanMapper.copyList(docs, DocBaseVO.class, (s, t) -> {
t.setFileType(FileTypeEnum.valueOfType(s.getFileType()));
});
}
@Override
public DocBaseVO get(Long docId) {
ExtAxModelDoc doc = extAxModelDocMapper.selectById(docId);
if (Objects.isNull(doc)) {
throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS);
}
return BeanMapper.copyBean(doc, DocBaseVO.class, (s, t) -> {
t.setFileType(FileTypeEnum.valueOfType(s.getFileType()));
});
}
private LambdaQueryWrapper<ExtAxModelDoc> buildQueryWrapper(ExtAxModelDoc entity) {
return new LambdaQueryWrapper<ExtAxModelDoc>()
.eq(StringUtils.hasText(entity.getModelId()), ExtAxModelDoc::getModelId, entity.getModelId())
@ -80,12 +133,14 @@ public class ExtAxModelDocServiceImpl implements ExtAxModelDocService {
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean createDoc(DocCreateDTO dto) {
ExtAxModelDoc old = new ExtAxModelDoc();
old.setModelId(dto.getModelId());
old.setTag(dto.getTag());
Integer count = extAxModelDocMapper.selectCount(buildQueryWrapper(old));
if (count > 0) {
throw new WorkflowEngineException(MODEL_FILE_TAG_DUPLICATE);
if (StringUtils.hasText(dto.getTag())) {
ExtAxModelDoc old = new ExtAxModelDoc();
old.setModelId(dto.getModelId());
old.setTag(dto.getTag());
Integer count = extAxModelDocMapper.selectCount(buildQueryWrapper(old));
if (count > 0) {
throw new WorkflowEngineException(MODEL_FILE_TAG_DUPLICATE);
}
}
ExtAxModelDoc entity = BeanMapper.copyBean(dto, ExtAxModelDoc.class);
entity.setFileType(dto.getFileType().name());
@ -121,6 +176,31 @@ public class ExtAxModelDocServiceImpl implements ExtAxModelDocService {
return extAxModelDocMapper.updateById(entity) > 0;
}
/**
* 该功能只能处理 hiprint 类型
*
* @param docId
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean cloneDoc(Long docId) {
ExtAxModelDoc origin = extAxModelDocMapper.selectById(docId);
if (Objects.isNull(origin)) {
throw new WorkflowEngineException(MODEL_FILE_NOT_EXISTS);
}
if (Objects.equals(FileTypeEnum.HIPRINT, FileTypeEnum.valueOfType(origin.getFileType()))) {
DocCreateDTO newDoc = BeanMapper.copyBean(origin, DocCreateDTO.class);
// 强制清空业务标签
newDoc.setTag("");
newDoc.setContent(extAxDocContentService.getContent(Long.valueOf(origin.getFileRelationId())));
createDoc(newDoc);
return true;
} else {
throw new WorkflowEngineException(MODEL_FILE_TYPE_CLONE_NOT_SUPPORT);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteDoc(Long docId) {

View File

@ -150,6 +150,10 @@
<groupId>cn.axzo.org</groupId>
<artifactId>org-api</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.nanopart</groupId>
<artifactId>doc-api</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -1,12 +1,20 @@
package cn.axzo.workflow.server.controller.web.bpmn;
import cn.axzo.basics.common.BeanMapper;
import cn.axzo.nanopart.doc.api.anonymous.DocAnonymousDatabaseApi;
import cn.axzo.nanopart.doc.api.index.request.CopyNodeRequest;
import cn.axzo.oss.http.api.ServerFileServiceApi;
import cn.axzo.oss.http.model.copyobject.ServerFileBatchCopyObjectRequest;
import cn.axzo.oss.http.model.copyobject.ServerFileBatchCopyObjectResponse;
import cn.axzo.workflow.client.feign.bpmn.ProcessModelApi;
import cn.axzo.workflow.common.enums.FileTypeEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelUpdateDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocOrderDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.model.doc.DocUpdateDTO;
import cn.axzo.workflow.common.model.request.bpmn.print.PrintTemplateConfigQueryDTO;
@ -29,6 +37,8 @@ import cn.axzo.workflow.server.common.annotation.RepeatSubmit;
import cn.azxo.framework.common.model.CommonResponse;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
@ -46,9 +56,12 @@ import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_CLONE_ERROR;
import static cn.axzo.workflow.common.code.BpmnModelRespCode.MODEL_FILE_QUERY_ERROR;
import static cn.axzo.workflow.common.code.OtherRespCode.ILLEGAL_PARAM_ERROR;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_TENANT_ID;
import static cn.azxo.framework.common.model.CommonResponse.success;
@ -75,6 +88,10 @@ public class BpmnProcessModelController implements ProcessModelApi {
private BpmnProcessInstanceService bpmnProcessInstanceService;
@Resource
private ExtAxModelDocService modelDocService;
@Resource
private DocAnonymousDatabaseApi docApi;
@Resource
private ServerFileServiceApi fileServiceApi;
/**
* 获取流程模型的查询结果
@ -386,6 +403,16 @@ public class BpmnProcessModelController implements ProcessModelApi {
return success(modelDocService.docPage(dto));
}
@Override
@Operation(summary = "根据业务 ID 获取模型文档列表,自动适配公共模板和代运营")
@PostMapping(value = "/doc/list")
public CommonResponse<List<DocBaseVO>> docList(DocQueryDTO dto) {
if (!StringUtils.hasText(dto.getProcessInstanceId()) && !StringUtils.hasText(dto.getProcessDefinitionKey())) {
throw new WorkflowEngineException(MODEL_FILE_QUERY_ERROR);
}
return success(modelDocService.docList(dto));
}
@Override
@Operation(summary = "添加关联文档")
@PutMapping(value = "/doc/create")
@ -400,6 +427,43 @@ public class BpmnProcessModelController implements ProcessModelApi {
return success(modelDocService.updateDoc(dto));
}
@Override
@Operation(summary = "克隆关联文档")
@PostMapping(value = "/doc/clone")
public CommonResponse<Boolean> cloneDoc(Long docId) {
DocBaseVO originDoc = modelDocService.get(docId);
ArrayList<FileTypeEnum> wpsSupported = Lists.newArrayList(FileTypeEnum.WORD, FileTypeEnum.EXCEL);
if (wpsSupported.contains(originDoc.getFileType())) {
// WPS 文档 clone
CopyNodeRequest copy = new CopyNodeRequest();
copy.setCode(originDoc.getFileRelationId());
CommonResponse<String> response = docApi.copy(copy);
if (Objects.isNull(response) || !Objects.equals(response.getCode(), 200)) {
throw new WorkflowEngineException(MODEL_FILE_CLONE_ERROR, Objects.isNull(response) ? "网络异常" : response.getMsg());
}
DocCreateDTO newDoc = BeanMapper.copyBean(originDoc, DocCreateDTO.class);
newDoc.setFileRelationId(response.getData());
newDoc.setTag("");
return success(modelDocService.createDoc(newDoc));
}
if (Objects.equals(originDoc.getFileType(), FileTypeEnum.PDF)) {
// PDF 文档 clone
CommonResponse<ServerFileBatchCopyObjectResponse> response = fileServiceApi.batchCopyObject(ServerFileBatchCopyObjectRequest.builder()
.copyObjects(Sets.newHashSet(ServerFileBatchCopyObjectRequest.ServerFileCopyObjectRequest.builder()
.srcFileKey(originDoc.getFileRelationId())
.build()))
.build());
if (Objects.isNull(response) || Objects.equals(response.getCode(), 200)) {
throw new WorkflowEngineException(MODEL_FILE_CLONE_ERROR, Objects.isNull(response) ? "网络异常" : response.getMsg());
}
DocCreateDTO newDoc = BeanMapper.copyBean(originDoc, DocCreateDTO.class);
newDoc.setTag("");
newDoc.setFileRelationId(response.getData().getResponses().get(0).getSrcFileKey());
return success(modelDocService.createDoc(newDoc));
}
return success(modelDocService.cloneDoc(docId));
}
@Override
@Operation(summary = "删除指定文档")
@DeleteMapping(value = "/doc/delete")