feat: 归档L03与L09审批发布能力
- 新增统一审批中心与审批管理页面,支持流程配置、审批详情与角色/用户审批对象 - 接入聊天助手、知识库、工作流的发布与删除审批,并补齐发布态校验与快照展示
This commit is contained in:
@@ -80,6 +80,10 @@
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-module-system</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-module-approval</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.java-websocket</groupId>
|
||||
|
||||
@@ -14,25 +14,31 @@ import java.util.*;
|
||||
public class WorkflowTool extends BaseTool {
|
||||
|
||||
private BigInteger workflowId;
|
||||
private String definitionId;
|
||||
|
||||
public WorkflowTool() {
|
||||
}
|
||||
|
||||
public WorkflowTool(Workflow workflow, boolean needEnglishName) {
|
||||
this(workflow, needEnglishName, workflow.getId() == null ? null : workflow.getId().toString());
|
||||
}
|
||||
|
||||
public WorkflowTool(Workflow workflow, boolean needEnglishName, String definitionId) {
|
||||
this.workflowId = workflow.getId();
|
||||
this.definitionId = definitionId;
|
||||
if (needEnglishName) {
|
||||
this.name = workflow.getEnglishName();
|
||||
} else {
|
||||
this.name = workflow.getTitle();
|
||||
}
|
||||
this.description = workflow.getDescription();
|
||||
this.parameters = toParameters(workflow);
|
||||
this.parameters = toParameters(workflow, definitionId);
|
||||
}
|
||||
|
||||
|
||||
static Parameter[] toParameters(Workflow workflow) {
|
||||
static Parameter[] toParameters(Workflow workflow, String definitionId) {
|
||||
ChainExecutor executor = SpringContextUtil.getBean(ChainExecutor.class);
|
||||
ChainDefinition definition = executor.getDefinitionRepository().getChainDefinitionById(workflow.getId().toString());
|
||||
ChainDefinition definition = executor.getDefinitionRepository().getChainDefinitionById(definitionId);
|
||||
List<com.easyagents.flow.core.chain.Parameter> parameterDefs = definition.getStartParameters();
|
||||
if (parameterDefs == null || parameterDefs.isEmpty()) {
|
||||
return new Parameter[0];
|
||||
@@ -131,16 +137,25 @@ public class WorkflowTool extends BaseTool {
|
||||
this.workflowId = workflowId;
|
||||
}
|
||||
|
||||
public String getDefinitionId() {
|
||||
return definitionId;
|
||||
}
|
||||
|
||||
public void setDefinitionId(String definitionId) {
|
||||
this.definitionId = definitionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Map<String, Object> argsMap) {
|
||||
ChainExecutor executor = SpringContextUtil.getBean(ChainExecutor.class);
|
||||
return executor.execute(workflowId.toString(), argsMap);
|
||||
return executor.execute(definitionId, argsMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AiWorkflowFunction{" +
|
||||
"workflowId=" + workflowId +
|
||||
", definitionId='" + definitionId + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
", parameters=" + Arrays.toString(parameters) +
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.easyagents.flow.core.chain.ChainDefinition;
|
||||
import com.easyagents.flow.core.chain.repository.ChainDefinitionRepository;
|
||||
import com.easyagents.flow.core.parser.ChainParser;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.ai.easyagentsflow.support.PublishedWorkflowDefinitionIds;
|
||||
import tech.easyflow.ai.easyagentsflow.service.WorkflowDatacenterContentService;
|
||||
import tech.easyflow.ai.entity.Workflow;
|
||||
import tech.easyflow.ai.service.WorkflowService;
|
||||
@@ -22,10 +23,14 @@ public class ChainDefinitionRepositoryImpl implements ChainDefinitionRepository
|
||||
|
||||
@Override
|
||||
public ChainDefinition getChainDefinitionById(String id) {
|
||||
Workflow workflow = workflowService.getById(id);
|
||||
boolean publishedDefinition = PublishedWorkflowDefinitionIds.isPublished(id);
|
||||
String workflowId = PublishedWorkflowDefinitionIds.unwrap(id);
|
||||
Workflow workflow = publishedDefinition
|
||||
? workflowService.getPublishedById(new java.math.BigInteger(workflowId))
|
||||
: workflowService.getById(workflowId);
|
||||
String json = workflowDatacenterContentService.prepareContent(workflow.getContent());
|
||||
ChainDefinition chainDefinition = chainParser.parse(json);
|
||||
chainDefinition.setId(workflow.getId().toString());
|
||||
chainDefinition.setId(id);
|
||||
chainDefinition.setName(workflow.getEnglishName());
|
||||
chainDefinition.setDescription(workflow.getDescription());
|
||||
return chainDefinition;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package tech.easyflow.ai.easyagentsflow.support;
|
||||
|
||||
/**
|
||||
* 已发布工作流定义 ID 工具。
|
||||
*/
|
||||
public final class PublishedWorkflowDefinitionIds {
|
||||
|
||||
private static final String PREFIX = "published:";
|
||||
|
||||
private PublishedWorkflowDefinitionIds() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建已发布定义 ID。
|
||||
*
|
||||
* @param workflowId 工作流 ID
|
||||
* @return 已发布定义 ID
|
||||
*/
|
||||
public static String published(String workflowId) {
|
||||
return PREFIX + workflowId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为已发布定义 ID。
|
||||
*
|
||||
* @param definitionId 定义 ID
|
||||
* @return 命中已发布定义前缀时返回 true
|
||||
*/
|
||||
public static boolean isPublished(String definitionId) {
|
||||
return definitionId != null && definitionId.startsWith(PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原真实工作流 ID。
|
||||
*
|
||||
* @param definitionId 定义 ID
|
||||
* @return 原始工作流 ID
|
||||
*/
|
||||
public static String unwrap(String definitionId) {
|
||||
if (!isPublished(definitionId)) {
|
||||
return definitionId;
|
||||
}
|
||||
return definitionId.substring(PREFIX.length());
|
||||
}
|
||||
}
|
||||
@@ -19,4 +19,8 @@ public class Workflow extends WorkflowBase implements VisibilityResource {
|
||||
public Tool toFunction(boolean needEnglishName) {
|
||||
return new WorkflowTool(this, needEnglishName);
|
||||
}
|
||||
|
||||
public Tool toFunction(boolean needEnglishName, String definitionId) {
|
||||
return new WorkflowTool(this, needEnglishName, definitionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,36 @@ public class BotBase extends DateEntity implements Serializable {
|
||||
@Column(comment = "修改者ID")
|
||||
private BigInteger modifiedBy;
|
||||
|
||||
/**
|
||||
* 发布状态
|
||||
*/
|
||||
@Column(comment = "发布状态")
|
||||
private String publishStatus;
|
||||
|
||||
/**
|
||||
* 当前审批实例ID
|
||||
*/
|
||||
@Column(comment = "当前审批实例ID")
|
||||
private BigInteger currentApprovalInstanceId;
|
||||
|
||||
/**
|
||||
* 已发布快照
|
||||
*/
|
||||
@Column(typeHandler = FastjsonTypeHandler.class, comment = "已发布快照")
|
||||
private Map<String, Object> publishedSnapshotJson;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
@Column(comment = "发布时间")
|
||||
private Date publishedAt;
|
||||
|
||||
/**
|
||||
* 发布人
|
||||
*/
|
||||
@Column(comment = "发布人")
|
||||
private BigInteger publishedBy;
|
||||
|
||||
public BigInteger getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -239,4 +269,44 @@ public class BotBase extends DateEntity implements Serializable {
|
||||
this.modifiedBy = modifiedBy;
|
||||
}
|
||||
|
||||
public String getPublishStatus() {
|
||||
return publishStatus;
|
||||
}
|
||||
|
||||
public void setPublishStatus(String publishStatus) {
|
||||
this.publishStatus = publishStatus;
|
||||
}
|
||||
|
||||
public BigInteger getCurrentApprovalInstanceId() {
|
||||
return currentApprovalInstanceId;
|
||||
}
|
||||
|
||||
public void setCurrentApprovalInstanceId(BigInteger currentApprovalInstanceId) {
|
||||
this.currentApprovalInstanceId = currentApprovalInstanceId;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPublishedSnapshotJson() {
|
||||
return publishedSnapshotJson;
|
||||
}
|
||||
|
||||
public void setPublishedSnapshotJson(Map<String, Object> publishedSnapshotJson) {
|
||||
this.publishedSnapshotJson = publishedSnapshotJson;
|
||||
}
|
||||
|
||||
public Date getPublishedAt() {
|
||||
return publishedAt;
|
||||
}
|
||||
|
||||
public void setPublishedAt(Date publishedAt) {
|
||||
this.publishedAt = publishedAt;
|
||||
}
|
||||
|
||||
public BigInteger getPublishedBy() {
|
||||
return publishedBy;
|
||||
}
|
||||
|
||||
public void setPublishedBy(BigInteger publishedBy) {
|
||||
this.publishedBy = publishedBy;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -166,6 +166,36 @@ public class DocumentCollectionBase extends DateEntity implements Serializable {
|
||||
@Column(comment = "可见范围")
|
||||
private String visibilityScope;
|
||||
|
||||
/**
|
||||
* 发布状态
|
||||
*/
|
||||
@Column(comment = "发布状态")
|
||||
private String publishStatus;
|
||||
|
||||
/**
|
||||
* 当前审批实例ID
|
||||
*/
|
||||
@Column(comment = "当前审批实例ID")
|
||||
private BigInteger currentApprovalInstanceId;
|
||||
|
||||
/**
|
||||
* 已发布快照
|
||||
*/
|
||||
@Column(typeHandler = FastjsonTypeHandler.class, comment = "已发布快照")
|
||||
private Map<String, Object> publishedSnapshotJson;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
@Column(comment = "发布时间")
|
||||
private Date publishedAt;
|
||||
|
||||
/**
|
||||
* 发布人
|
||||
*/
|
||||
@Column(comment = "发布人")
|
||||
private BigInteger publishedBy;
|
||||
|
||||
public BigInteger getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -366,4 +396,44 @@ public class DocumentCollectionBase extends DateEntity implements Serializable {
|
||||
this.visibilityScope = visibilityScope;
|
||||
}
|
||||
|
||||
public String getPublishStatus() {
|
||||
return publishStatus;
|
||||
}
|
||||
|
||||
public void setPublishStatus(String publishStatus) {
|
||||
this.publishStatus = publishStatus;
|
||||
}
|
||||
|
||||
public BigInteger getCurrentApprovalInstanceId() {
|
||||
return currentApprovalInstanceId;
|
||||
}
|
||||
|
||||
public void setCurrentApprovalInstanceId(BigInteger currentApprovalInstanceId) {
|
||||
this.currentApprovalInstanceId = currentApprovalInstanceId;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPublishedSnapshotJson() {
|
||||
return publishedSnapshotJson;
|
||||
}
|
||||
|
||||
public void setPublishedSnapshotJson(Map<String, Object> publishedSnapshotJson) {
|
||||
this.publishedSnapshotJson = publishedSnapshotJson;
|
||||
}
|
||||
|
||||
public Date getPublishedAt() {
|
||||
return publishedAt;
|
||||
}
|
||||
|
||||
public void setPublishedAt(Date publishedAt) {
|
||||
this.publishedAt = publishedAt;
|
||||
}
|
||||
|
||||
public BigInteger getPublishedBy() {
|
||||
return publishedBy;
|
||||
}
|
||||
|
||||
public void setPublishedBy(BigInteger publishedBy) {
|
||||
this.publishedBy = publishedBy;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ package tech.easyflow.ai.entity.base;
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.annotation.Id;
|
||||
import com.mybatisflex.annotation.KeyType;
|
||||
import com.mybatisflex.core.handler.FastjsonTypeHandler;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import tech.easyflow.common.entity.DateEntity;
|
||||
|
||||
|
||||
@@ -109,6 +111,36 @@ public class WorkflowBase extends DateEntity implements Serializable {
|
||||
@Column(comment = "可见范围")
|
||||
private String visibilityScope;
|
||||
|
||||
/**
|
||||
* 发布状态
|
||||
*/
|
||||
@Column(comment = "发布状态")
|
||||
private String publishStatus;
|
||||
|
||||
/**
|
||||
* 当前审批实例ID
|
||||
*/
|
||||
@Column(comment = "当前审批实例ID")
|
||||
private BigInteger currentApprovalInstanceId;
|
||||
|
||||
/**
|
||||
* 已发布快照
|
||||
*/
|
||||
@Column(typeHandler = FastjsonTypeHandler.class, comment = "已发布快照")
|
||||
private Map<String, Object> publishedSnapshotJson;
|
||||
|
||||
/**
|
||||
* 发布时间
|
||||
*/
|
||||
@Column(comment = "发布时间")
|
||||
private Date publishedAt;
|
||||
|
||||
/**
|
||||
* 发布人
|
||||
*/
|
||||
@Column(comment = "发布人")
|
||||
private BigInteger publishedBy;
|
||||
|
||||
public BigInteger getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -237,4 +269,44 @@ public class WorkflowBase extends DateEntity implements Serializable {
|
||||
this.visibilityScope = visibilityScope;
|
||||
}
|
||||
|
||||
public String getPublishStatus() {
|
||||
return publishStatus;
|
||||
}
|
||||
|
||||
public void setPublishStatus(String publishStatus) {
|
||||
this.publishStatus = publishStatus;
|
||||
}
|
||||
|
||||
public BigInteger getCurrentApprovalInstanceId() {
|
||||
return currentApprovalInstanceId;
|
||||
}
|
||||
|
||||
public void setCurrentApprovalInstanceId(BigInteger currentApprovalInstanceId) {
|
||||
this.currentApprovalInstanceId = currentApprovalInstanceId;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPublishedSnapshotJson() {
|
||||
return publishedSnapshotJson;
|
||||
}
|
||||
|
||||
public void setPublishedSnapshotJson(Map<String, Object> publishedSnapshotJson) {
|
||||
this.publishedSnapshotJson = publishedSnapshotJson;
|
||||
}
|
||||
|
||||
public Date getPublishedAt() {
|
||||
return publishedAt;
|
||||
}
|
||||
|
||||
public void setPublishedAt(Date publishedAt) {
|
||||
this.publishedAt = publishedAt;
|
||||
}
|
||||
|
||||
public BigInteger getPublishedBy() {
|
||||
return publishedBy;
|
||||
}
|
||||
|
||||
public void setPublishedBy(BigInteger publishedBy) {
|
||||
this.publishedBy = publishedBy;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package tech.easyflow.ai.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* AI 资源发布状态。
|
||||
*/
|
||||
public enum PublishStatus {
|
||||
|
||||
DRAFT("DRAFT"),
|
||||
PUBLISH_PENDING("PUBLISH_PENDING"),
|
||||
PUBLISHED("PUBLISHED"),
|
||||
DELETE_PENDING("DELETE_PENDING");
|
||||
|
||||
private final String code;
|
||||
|
||||
PublishStatus(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态编码。
|
||||
*
|
||||
* @return 状态编码
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否允许作为线上版本使用。
|
||||
*
|
||||
* @return 允许外部访问或线上运行时返回 true
|
||||
*/
|
||||
public boolean isExternallyVisible() {
|
||||
return this == PUBLISHED || this == DELETE_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析发布状态。
|
||||
*
|
||||
* @param code 状态编码
|
||||
* @return 发布状态
|
||||
*/
|
||||
public static PublishStatus from(String code) {
|
||||
if (code == null || code.isBlank()) {
|
||||
return DRAFT;
|
||||
}
|
||||
String normalized = code.trim().toUpperCase(Locale.ROOT);
|
||||
return Arrays.stream(values())
|
||||
.filter(item -> item.code.equals(normalized))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("不支持的发布状态: " + code));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
package tech.easyflow.ai.publish;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.ai.entity.Bot;
|
||||
import tech.easyflow.ai.entity.BotDocumentCollection;
|
||||
import tech.easyflow.ai.entity.BotCategory;
|
||||
import tech.easyflow.ai.entity.BotMcp;
|
||||
import tech.easyflow.ai.entity.BotPlugin;
|
||||
import tech.easyflow.ai.entity.BotWorkflow;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
import tech.easyflow.ai.entity.Mcp;
|
||||
import tech.easyflow.ai.entity.Model;
|
||||
import tech.easyflow.ai.entity.PluginItem;
|
||||
import tech.easyflow.ai.entity.Workflow;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
import tech.easyflow.ai.service.BotCategoryService;
|
||||
import tech.easyflow.ai.service.BotDocumentCollectionService;
|
||||
import tech.easyflow.ai.service.BotMcpService;
|
||||
import tech.easyflow.ai.service.BotPluginService;
|
||||
import tech.easyflow.ai.service.BotService;
|
||||
import tech.easyflow.ai.service.BotWorkflowService;
|
||||
import tech.easyflow.ai.service.DocumentCollectionService;
|
||||
import tech.easyflow.ai.service.McpService;
|
||||
import tech.easyflow.ai.service.ModelService;
|
||||
import tech.easyflow.ai.service.PluginItemService;
|
||||
import tech.easyflow.ai.service.WorkflowService;
|
||||
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalCallbackContext;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalSubmitCallbackContext;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
|
||||
import tech.easyflow.approval.enums.ApprovalActionType;
|
||||
import tech.easyflow.approval.enums.ApprovalResourceType;
|
||||
import tech.easyflow.approval.service.ApprovalInstanceService;
|
||||
import tech.easyflow.approval.service.ApprovalSubjectHandler;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
import tech.easyflow.system.entity.SysDept;
|
||||
import tech.easyflow.system.service.SysDeptService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 聊天助手审批处理器。
|
||||
*/
|
||||
@Component
|
||||
public class BotApprovalSubjectHandler implements ApprovalSubjectHandler {
|
||||
|
||||
private static final String SNAPSHOT_KEY = "resourceSnapshot";
|
||||
|
||||
private final BotService botService;
|
||||
private final BotWorkflowService botWorkflowService;
|
||||
private final BotDocumentCollectionService botDocumentCollectionService;
|
||||
private final BotPluginService botPluginService;
|
||||
private final BotMcpService botMcpService;
|
||||
private final WorkflowService workflowService;
|
||||
private final DocumentCollectionService documentCollectionService;
|
||||
private final ApprovalInstanceService approvalInstanceService;
|
||||
private final CategoryPermissionService categoryPermissionService;
|
||||
private final ModelService modelService;
|
||||
private final BotCategoryService botCategoryService;
|
||||
private final SysDeptService sysDeptService;
|
||||
private final PluginItemService pluginItemService;
|
||||
private final McpService mcpService;
|
||||
|
||||
public BotApprovalSubjectHandler(BotService botService,
|
||||
BotWorkflowService botWorkflowService,
|
||||
BotDocumentCollectionService botDocumentCollectionService,
|
||||
BotPluginService botPluginService,
|
||||
BotMcpService botMcpService,
|
||||
WorkflowService workflowService,
|
||||
DocumentCollectionService documentCollectionService,
|
||||
ApprovalInstanceService approvalInstanceService,
|
||||
CategoryPermissionService categoryPermissionService,
|
||||
ModelService modelService,
|
||||
BotCategoryService botCategoryService,
|
||||
SysDeptService sysDeptService,
|
||||
PluginItemService pluginItemService,
|
||||
McpService mcpService) {
|
||||
this.botService = botService;
|
||||
this.botWorkflowService = botWorkflowService;
|
||||
this.botDocumentCollectionService = botDocumentCollectionService;
|
||||
this.botPluginService = botPluginService;
|
||||
this.botMcpService = botMcpService;
|
||||
this.workflowService = workflowService;
|
||||
this.documentCollectionService = documentCollectionService;
|
||||
this.approvalInstanceService = approvalInstanceService;
|
||||
this.categoryPermissionService = categoryPermissionService;
|
||||
this.modelService = modelService;
|
||||
this.botCategoryService = botCategoryService;
|
||||
this.sysDeptService = sysDeptService;
|
||||
this.pluginItemService = pluginItemService;
|
||||
this.mcpService = mcpService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resourceType() {
|
||||
return ApprovalResourceType.BOT.getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApprovalSubmitRequest buildSubmitRequest(BigInteger resourceId, String actionType, BigInteger operatorId) {
|
||||
Bot bot = requireBot(resourceId);
|
||||
assertManagePermission(bot);
|
||||
if (approvalInstanceService.existsActiveInstance(resourceType(), resourceId)) {
|
||||
throw new BusinessException("当前聊天助手存在未结束审批,请先处理完成");
|
||||
}
|
||||
ApprovalActionType approvalActionType = ApprovalActionType.from(actionType);
|
||||
Map<String, Object> resourceSnapshot = buildResourceSnapshot(bot);
|
||||
if (approvalActionType == ApprovalActionType.PUBLISH
|
||||
&& JSON.toJSONString(resourceSnapshot).equals(JSON.toJSONString(bot.getPublishedSnapshotJson()))) {
|
||||
throw new BusinessException("当前聊天助手没有变更,无需重复发布");
|
||||
}
|
||||
|
||||
ApprovalSubmitRequest request = new ApprovalSubmitRequest();
|
||||
request.setResourceType(resourceType());
|
||||
request.setResourceId(resourceId);
|
||||
request.setActionType(approvalActionType.getCode());
|
||||
request.setApplicantId(operatorId);
|
||||
request.setCategoryId(bot.getCategoryId());
|
||||
request.setDeptId(bot.getDeptId());
|
||||
request.setSummary((approvalActionType == ApprovalActionType.PUBLISH ? "发布" : "删除") + "聊天助手:" + bot.getTitle());
|
||||
request.setSnapshotJson(Map.of(SNAPSHOT_KEY, resourceSnapshot));
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubmitted(ApprovalSubmitCallbackContext context) {
|
||||
Bot update = new Bot();
|
||||
update.setId(context.getResourceId());
|
||||
update.setCurrentApprovalInstanceId(context.getInstanceId());
|
||||
update.setPublishStatus(resolvePendingStatus(context.getActionType()).getCode());
|
||||
botService.updateById(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApproved(ApprovalCallbackContext context) {
|
||||
ApprovalInstance instance = context.getInstance();
|
||||
if (ApprovalActionType.PUBLISH.getCode().equals(instance.getActionType())) {
|
||||
Bot update = new Bot();
|
||||
update.setId(instance.getResourceId());
|
||||
update.setPublishStatus(PublishStatus.PUBLISHED.getCode());
|
||||
update.setCurrentApprovalInstanceId(null);
|
||||
update.setPublishedSnapshotJson(readResourceSnapshot(instance));
|
||||
update.setPublishedAt(new Date());
|
||||
update.setPublishedBy(context.getOperatorId());
|
||||
botService.updateById(update);
|
||||
return;
|
||||
}
|
||||
removeBotRelations(instance.getResourceId());
|
||||
botService.removeById(instance.getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRejected(ApprovalCallbackContext context) {
|
||||
clearPendingStatus(context.getInstance().getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRevoked(ApprovalCallbackContext context) {
|
||||
clearPendingStatus(context.getInstance().getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertPublishedAccess(Object identifier, String denyMessage) {
|
||||
Bot bot = botService.getDetail(String.valueOf(identifier));
|
||||
if (bot == null || !PublishStatus.from(bot.getPublishStatus()).isExternallyVisible()
|
||||
|| bot.getPublishedSnapshotJson() == null || bot.getPublishedSnapshotJson().isEmpty()) {
|
||||
throw new BusinessException(denyMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private Bot requireBot(BigInteger id) {
|
||||
Bot bot = botService.getById(id);
|
||||
if (bot == null) {
|
||||
throw new BusinessException("聊天助手不存在");
|
||||
}
|
||||
return bot;
|
||||
}
|
||||
|
||||
private void assertManagePermission(Bot bot) {
|
||||
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||
boolean superAdmin = categoryPermissionService.isCurrentSuperAdmin();
|
||||
boolean creator = account != null && account.getId() != null && account.getId().equals(bot.getCreatedBy());
|
||||
if (!superAdmin && !creator) {
|
||||
throw new BusinessException("仅创建者或超级管理员可管理聊天助手");
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> buildResourceSnapshot(Bot bot) {
|
||||
Model model = resolveModel(bot.getModelId());
|
||||
BotCategory category = resolveCategory(bot.getCategoryId());
|
||||
SysDept dept = resolveDept(bot.getDeptId());
|
||||
Map<String, Object> snapshot = new LinkedHashMap<>();
|
||||
snapshot.put("id", bot.getId());
|
||||
snapshot.put("alias", bot.getAlias());
|
||||
snapshot.put("deptId", bot.getDeptId());
|
||||
snapshot.put("deptName", dept == null ? null : dept.getDeptName());
|
||||
snapshot.put("categoryId", bot.getCategoryId());
|
||||
snapshot.put("categoryName", category == null ? null : category.getCategoryName());
|
||||
snapshot.put("title", bot.getTitle());
|
||||
snapshot.put("description", bot.getDescription());
|
||||
snapshot.put("icon", bot.getIcon());
|
||||
snapshot.put("modelId", bot.getModelId());
|
||||
snapshot.put("modelName", resolveModelName(model));
|
||||
snapshot.put("modelOptions", bot.getModelOptions());
|
||||
snapshot.put("systemPrompt", resolveSystemPrompt(bot));
|
||||
snapshot.put("temperature", readNumberOption(bot.getModelOptions(), "temperature"));
|
||||
snapshot.put("topP", readNumberOption(bot.getModelOptions(), "topP"));
|
||||
snapshot.put("topK", readNumberOption(bot.getModelOptions(), "topK"));
|
||||
snapshot.put("maxReplyLength", readNumberOption(bot.getModelOptions(), "maxReplyLength"));
|
||||
snapshot.put("maxMessageCount", readNumberOption(bot.getModelOptions(), Bot.KEY_MAX_MESSAGE_COUNT));
|
||||
snapshot.put("status", bot.getStatus());
|
||||
snapshot.put("options", bot.getOptions());
|
||||
snapshot.put("anonymousEnabled", bot.isAnonymousEnabled());
|
||||
snapshot.put("workflowBindings", buildWorkflowBindings(bot.getId()));
|
||||
snapshot.put("knowledgeBindings", buildKnowledgeBindings(bot.getId()));
|
||||
snapshot.put("pluginBindings", buildPluginBindings(bot.getId()));
|
||||
snapshot.put("mcpBindings", buildMcpBindings(bot.getId()));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildWorkflowBindings(BigInteger botId) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create().eq(BotWorkflow::getBotId, botId);
|
||||
List<BotWorkflow> relations = botWorkflowService.getMapper().selectListWithRelationsByQuery(queryWrapper);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (BotWorkflow relation : relations) {
|
||||
Workflow workflow = relation.getWorkflow();
|
||||
if (workflow == null || !PublishStatus.from(workflow.getPublishStatus()).isExternallyVisible()) {
|
||||
throw new BusinessException("聊天助手绑定的工作流未发布,无法发布聊天助手");
|
||||
}
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("workflowId", relation.getWorkflowId());
|
||||
item.put("workflowName", workflow.getTitle());
|
||||
result.add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildKnowledgeBindings(BigInteger botId) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create().eq(BotDocumentCollection::getBotId, botId);
|
||||
List<BotDocumentCollection> relations = botDocumentCollectionService.getMapper().selectListWithRelationsByQuery(queryWrapper);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (BotDocumentCollection relation : relations) {
|
||||
DocumentCollection knowledge = relation.getKnowledge();
|
||||
if (knowledge == null || !PublishStatus.from(knowledge.getPublishStatus()).isExternallyVisible()) {
|
||||
throw new BusinessException("聊天助手绑定的知识库未发布,无法发布聊天助手");
|
||||
}
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("knowledgeId", relation.getDocumentCollectionId());
|
||||
item.put("knowledgeName", knowledge.getTitle());
|
||||
item.put("retrievalMode", relation.getRetrievalMode() == null ? null : relation.getRetrievalMode().name());
|
||||
result.add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildPluginBindings(BigInteger botId) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create().eq(BotPlugin::getBotId, botId);
|
||||
List<BotPlugin> relations = botPluginService.list(queryWrapper);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (BotPlugin relation : relations) {
|
||||
PluginItem pluginItem = pluginItemService.getById(relation.getPluginItemId());
|
||||
if (pluginItem == null) {
|
||||
throw new BusinessException("聊天助手绑定的插件工具不存在,无法发布聊天助手");
|
||||
}
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("pluginItemId", relation.getPluginItemId());
|
||||
item.put("pluginItemName", resolvePluginName(pluginItem));
|
||||
result.add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildMcpBindings(BigInteger botId) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create().eq(BotMcp::getBotId, botId);
|
||||
List<BotMcp> relations = botMcpService.list(queryWrapper);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (BotMcp relation : relations) {
|
||||
Mcp mcp = mcpService.getById(relation.getMcpId());
|
||||
if (mcp == null) {
|
||||
throw new BusinessException("聊天助手绑定的MCP不存在,无法发布聊天助手");
|
||||
}
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("mcpId", relation.getMcpId());
|
||||
item.put("mcpName", mcp.getTitle());
|
||||
item.put("mcpToolName", relation.getMcpToolName());
|
||||
item.put("mcpToolDescription", relation.getMcpToolDescription());
|
||||
result.add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析聊天助手分类。
|
||||
*
|
||||
* @param categoryId 分类 ID
|
||||
* @return 分类实体,不存在时返回 {@code null}
|
||||
*/
|
||||
private BotCategory resolveCategory(BigInteger categoryId) {
|
||||
if (categoryId == null) {
|
||||
return null;
|
||||
}
|
||||
return botCategoryService.getById(categoryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析部门信息。
|
||||
*
|
||||
* @param deptId 部门 ID
|
||||
* @return 部门实体,不存在时返回 {@code null}
|
||||
*/
|
||||
private SysDept resolveDept(BigInteger deptId) {
|
||||
if (deptId == null) {
|
||||
return null;
|
||||
}
|
||||
return sysDeptService.getById(deptId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析聊天模型。
|
||||
*
|
||||
* @param modelId 模型 ID
|
||||
* @return 模型实体
|
||||
*/
|
||||
private Model resolveModel(BigInteger modelId) {
|
||||
if (modelId == null) {
|
||||
throw new BusinessException("聊天助手未配置模型,无法提交审批");
|
||||
}
|
||||
Model model = modelService.getById(modelId);
|
||||
if (model == null) {
|
||||
throw new BusinessException("聊天助手绑定的模型不存在,无法提交审批");
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模型展示名称。
|
||||
*
|
||||
* @param model 模型实体
|
||||
* @return 模型名称
|
||||
*/
|
||||
private String resolveModelName(Model model) {
|
||||
if (model.getTitle() != null && !model.getTitle().isBlank()) {
|
||||
return model.getTitle();
|
||||
}
|
||||
return model.getModelName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取系统提示词。
|
||||
*
|
||||
* @param bot 聊天助手
|
||||
* @return 系统提示词
|
||||
*/
|
||||
private String resolveSystemPrompt(Bot bot) {
|
||||
if (bot.getModelOptions() == null) {
|
||||
return null;
|
||||
}
|
||||
Object prompt = bot.getModelOptions().get(Bot.KEY_SYSTEM_PROMPT);
|
||||
return prompt == null ? null : String.valueOf(prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取数值配置项。
|
||||
*
|
||||
* @param options 配置 map
|
||||
* @param key 配置键
|
||||
* @return 数值配置,不存在时返回 {@code null}
|
||||
*/
|
||||
private Number readNumberOption(Map<String, Object> options, String key) {
|
||||
if (options == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = options.get(key);
|
||||
if (value instanceof Number number) {
|
||||
return number;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成插件工具展示名称。
|
||||
*
|
||||
* @param pluginItem 插件工具实体
|
||||
* @return 展示名称
|
||||
*/
|
||||
private String resolvePluginName(PluginItem pluginItem) {
|
||||
if (pluginItem.getName() != null && !pluginItem.getName().isBlank()) {
|
||||
return pluginItem.getName();
|
||||
}
|
||||
return pluginItem.getEnglishName();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> readResourceSnapshot(ApprovalInstance instance) {
|
||||
Object snapshot = instance.getSnapshotJson() == null ? null : instance.getSnapshotJson().get(SNAPSHOT_KEY);
|
||||
if (!(snapshot instanceof Map<?, ?> map)) {
|
||||
throw new BusinessException("审批快照缺少聊天助手发布内容");
|
||||
}
|
||||
return (Map<String, Object>) map;
|
||||
}
|
||||
|
||||
private PublishStatus resolvePendingStatus(String actionType) {
|
||||
return ApprovalActionType.DELETE.getCode().equals(actionType)
|
||||
? PublishStatus.DELETE_PENDING
|
||||
: PublishStatus.PUBLISH_PENDING;
|
||||
}
|
||||
|
||||
private void clearPendingStatus(BigInteger botId) {
|
||||
Bot bot = botService.getById(botId);
|
||||
if (bot == null) {
|
||||
return;
|
||||
}
|
||||
Bot update = new Bot();
|
||||
update.setId(botId);
|
||||
update.setCurrentApprovalInstanceId(null);
|
||||
update.setPublishStatus(bot.getPublishedSnapshotJson() == null || bot.getPublishedSnapshotJson().isEmpty()
|
||||
? PublishStatus.DRAFT.getCode()
|
||||
: PublishStatus.PUBLISHED.getCode());
|
||||
botService.updateById(update);
|
||||
}
|
||||
|
||||
private void removeBotRelations(BigInteger botId) {
|
||||
botDocumentCollectionService.remove(QueryWrapper.create().eq(BotDocumentCollection::getBotId, botId));
|
||||
botWorkflowService.remove(QueryWrapper.create().eq(BotWorkflow::getBotId, botId));
|
||||
botPluginService.remove(QueryWrapper.create().eq(BotPlugin::getBotId, botId));
|
||||
botMcpService.remove(QueryWrapper.create().eq(BotMcp::getBotId, botId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package tech.easyflow.ai.publish;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.approval.annotation.ApprovalAction;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 聊天助手发布生命周期应用服务。
|
||||
*/
|
||||
@Service
|
||||
public class BotPublishAppService {
|
||||
|
||||
/**
|
||||
* 提交聊天助手发布审批。
|
||||
*
|
||||
* @param id 助手 ID
|
||||
* @return 助手 ID
|
||||
*/
|
||||
@ApprovalAction(
|
||||
resourceType = "BOT",
|
||||
actionType = "PUBLISH",
|
||||
idExpr = "#id"
|
||||
)
|
||||
public BigInteger submitPublishApproval(BigInteger id) {
|
||||
assertId(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交聊天助手删除审批。
|
||||
*
|
||||
* @param id 助手 ID
|
||||
* @return 助手 ID
|
||||
*/
|
||||
@ApprovalAction(
|
||||
resourceType = "BOT",
|
||||
actionType = "DELETE",
|
||||
idExpr = "#id"
|
||||
)
|
||||
public BigInteger submitDeleteApproval(BigInteger id) {
|
||||
assertId(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
private void assertId(BigInteger id) {
|
||||
if (id == null) {
|
||||
throw new BusinessException("聊天助手审批时资源ID不能为空");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
package tech.easyflow.ai.publish;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.ai.entity.BotDocumentCollection;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
import tech.easyflow.ai.entity.DocumentCollectionCategory;
|
||||
import tech.easyflow.ai.entity.Model;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
import tech.easyflow.ai.service.BotDocumentCollectionService;
|
||||
import tech.easyflow.ai.service.DocumentCollectionCategoryService;
|
||||
import tech.easyflow.ai.service.DocumentCollectionService;
|
||||
import tech.easyflow.ai.service.ModelService;
|
||||
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalCallbackContext;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalSubmitCallbackContext;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
|
||||
import tech.easyflow.approval.enums.ApprovalActionType;
|
||||
import tech.easyflow.approval.enums.ApprovalResourceType;
|
||||
import tech.easyflow.approval.service.ApprovalInstanceService;
|
||||
import tech.easyflow.approval.service.ApprovalSubjectHandler;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.SysDept;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
import tech.easyflow.system.enums.VisibilityScope;
|
||||
import tech.easyflow.system.service.SysDeptService;
|
||||
import tech.easyflow.system.service.ResourceAccessService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 知识库审批处理器。
|
||||
*/
|
||||
@Component
|
||||
public class KnowledgeApprovalSubjectHandler implements ApprovalSubjectHandler {
|
||||
|
||||
private static final String SNAPSHOT_KEY = "resourceSnapshot";
|
||||
|
||||
private final DocumentCollectionService documentCollectionService;
|
||||
private final ResourceAccessService resourceAccessService;
|
||||
private final ApprovalInstanceService approvalInstanceService;
|
||||
private final BotDocumentCollectionService botDocumentCollectionService;
|
||||
private final ModelService modelService;
|
||||
private final DocumentCollectionCategoryService documentCollectionCategoryService;
|
||||
private final SysDeptService sysDeptService;
|
||||
|
||||
public KnowledgeApprovalSubjectHandler(DocumentCollectionService documentCollectionService,
|
||||
ResourceAccessService resourceAccessService,
|
||||
ApprovalInstanceService approvalInstanceService,
|
||||
BotDocumentCollectionService botDocumentCollectionService,
|
||||
ModelService modelService,
|
||||
DocumentCollectionCategoryService documentCollectionCategoryService,
|
||||
SysDeptService sysDeptService) {
|
||||
this.documentCollectionService = documentCollectionService;
|
||||
this.resourceAccessService = resourceAccessService;
|
||||
this.approvalInstanceService = approvalInstanceService;
|
||||
this.botDocumentCollectionService = botDocumentCollectionService;
|
||||
this.modelService = modelService;
|
||||
this.documentCollectionCategoryService = documentCollectionCategoryService;
|
||||
this.sysDeptService = sysDeptService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resourceType() {
|
||||
return ApprovalResourceType.KNOWLEDGE.getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApprovalSubmitRequest buildSubmitRequest(BigInteger resourceId, String actionType, BigInteger operatorId) {
|
||||
DocumentCollection knowledge = requireKnowledge(resourceId);
|
||||
resourceAccessService.assertAccess(CategoryResourceType.KNOWLEDGE, knowledge, ResourceAction.MANAGE, "无权限管理知识库");
|
||||
if (approvalInstanceService.existsActiveInstance(resourceType(), resourceId)) {
|
||||
throw new BusinessException("当前知识库存在未结束审批,请先处理完成");
|
||||
}
|
||||
|
||||
ApprovalActionType approvalActionType = ApprovalActionType.from(actionType);
|
||||
Map<String, Object> resourceSnapshot = buildResourceSnapshot(knowledge);
|
||||
if (approvalActionType == ApprovalActionType.PUBLISH
|
||||
&& JSON.toJSONString(resourceSnapshot).equals(JSON.toJSONString(knowledge.getPublishedSnapshotJson()))) {
|
||||
throw new BusinessException("当前知识库没有变更,无需重复发布");
|
||||
}
|
||||
if (approvalActionType == ApprovalActionType.DELETE && hasBotBinding(resourceId)) {
|
||||
throw new BusinessException("此知识库还关联着bot,请先取消关联!");
|
||||
}
|
||||
|
||||
ApprovalSubmitRequest request = new ApprovalSubmitRequest();
|
||||
request.setResourceType(resourceType());
|
||||
request.setResourceId(resourceId);
|
||||
request.setActionType(approvalActionType.getCode());
|
||||
request.setApplicantId(operatorId);
|
||||
request.setCategoryId(knowledge.getCategoryId());
|
||||
request.setDeptId(knowledge.getDeptId());
|
||||
request.setSummary((approvalActionType == ApprovalActionType.PUBLISH ? "发布" : "删除") + "知识库:" + knowledge.getTitle());
|
||||
request.setSnapshotJson(Map.of(SNAPSHOT_KEY, resourceSnapshot));
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubmitted(ApprovalSubmitCallbackContext context) {
|
||||
DocumentCollection update = new DocumentCollection();
|
||||
update.setId(context.getResourceId());
|
||||
update.setCurrentApprovalInstanceId(context.getInstanceId());
|
||||
update.setPublishStatus(resolvePendingStatus(context.getActionType()).getCode());
|
||||
documentCollectionService.updateById(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApproved(ApprovalCallbackContext context) {
|
||||
ApprovalInstance instance = context.getInstance();
|
||||
if (ApprovalActionType.PUBLISH.getCode().equals(instance.getActionType())) {
|
||||
DocumentCollection update = new DocumentCollection();
|
||||
update.setId(instance.getResourceId());
|
||||
update.setPublishStatus(PublishStatus.PUBLISHED.getCode());
|
||||
update.setCurrentApprovalInstanceId(null);
|
||||
update.setPublishedSnapshotJson(readResourceSnapshot(instance));
|
||||
update.setPublishedAt(new Date());
|
||||
update.setPublishedBy(context.getOperatorId());
|
||||
documentCollectionService.updateById(update);
|
||||
return;
|
||||
}
|
||||
documentCollectionService.removeById(instance.getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRejected(ApprovalCallbackContext context) {
|
||||
clearPendingStatus(context.getInstance().getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRevoked(ApprovalCallbackContext context) {
|
||||
clearPendingStatus(context.getInstance().getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertPublishedAccess(Object identifier, String denyMessage) {
|
||||
DocumentCollection collection = documentCollectionService.getDetail(String.valueOf(identifier));
|
||||
if (collection == null || !PublishStatus.from(collection.getPublishStatus()).isExternallyVisible()
|
||||
|| collection.getPublishedSnapshotJson() == null || collection.getPublishedSnapshotJson().isEmpty()) {
|
||||
throw new BusinessException(denyMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private DocumentCollection requireKnowledge(BigInteger id) {
|
||||
DocumentCollection knowledge = documentCollectionService.getById(id);
|
||||
if (knowledge == null) {
|
||||
throw new BusinessException("知识库不存在");
|
||||
}
|
||||
return knowledge;
|
||||
}
|
||||
|
||||
private boolean hasBotBinding(BigInteger knowledgeId) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create().eq(BotDocumentCollection::getDocumentCollectionId, knowledgeId);
|
||||
return botDocumentCollectionService.exists(queryWrapper);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildResourceSnapshot(DocumentCollection collection) {
|
||||
Model vectorModel = resolveModel(collection.getVectorEmbedModelId(), "知识库向量模型不存在,无法提交审批");
|
||||
Model rerankModel = resolveOptionalModel(collection.getRerankModelId(), "知识库重排模型不存在,无法提交审批");
|
||||
DocumentCollectionCategory category = resolveCategory(collection.getCategoryId());
|
||||
SysDept dept = resolveDept(collection.getDeptId());
|
||||
Map<String, Object> snapshot = new LinkedHashMap<>();
|
||||
snapshot.put("id", collection.getId());
|
||||
snapshot.put("collectionType", collection.getCollectionType());
|
||||
snapshot.put("collectionTypeLabel", resolveCollectionTypeLabel(collection.getCollectionType()));
|
||||
snapshot.put("alias", collection.getAlias());
|
||||
snapshot.put("deptId", collection.getDeptId());
|
||||
snapshot.put("deptName", dept == null ? null : dept.getDeptName());
|
||||
snapshot.put("icon", collection.getIcon());
|
||||
snapshot.put("title", collection.getTitle());
|
||||
snapshot.put("description", collection.getDescription());
|
||||
snapshot.put("slug", collection.getSlug());
|
||||
snapshot.put("vectorStoreEnable", collection.getVectorStoreEnable());
|
||||
snapshot.put("vectorStoreType", collection.getVectorStoreType());
|
||||
snapshot.put("vectorEmbedModelId", collection.getVectorEmbedModelId());
|
||||
snapshot.put("vectorEmbedModelName", resolveModelName(vectorModel));
|
||||
snapshot.put("dimensionOfVectorModel", collection.getDimensionOfVectorModel());
|
||||
Map<String, Object> options = collection.getOptions() == null
|
||||
? Collections.emptyMap()
|
||||
: new LinkedHashMap<>(collection.getOptions());
|
||||
snapshot.put("options", options);
|
||||
snapshot.put("canUpdateEmbeddingModel", collection.getOptionsByKey(DocumentCollection.KEY_CAN_UPDATE_EMBEDDING_MODEL));
|
||||
snapshot.put("rerankEnable", collection.getOptionsByKey(DocumentCollection.KEY_RERANK_ENABLE));
|
||||
snapshot.put("rerankModelId", collection.getRerankModelId());
|
||||
snapshot.put("rerankModelName", rerankModel == null ? null : resolveModelName(rerankModel));
|
||||
snapshot.put("searchEngineEnable", collection.getSearchEngineEnable());
|
||||
snapshot.put("englishName", collection.getEnglishName());
|
||||
snapshot.put("categoryId", collection.getCategoryId());
|
||||
snapshot.put("categoryName", category == null ? null : category.getCategoryName());
|
||||
snapshot.put("visibilityScope", collection.getVisibilityScope());
|
||||
snapshot.put("visibilityScopeLabel", resolveVisibilityScopeLabel(collection.getVisibilityScope()));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析知识库分类信息。
|
||||
*
|
||||
* @param categoryId 分类 ID
|
||||
* @return 分类实体,不存在时返回 {@code null}
|
||||
*/
|
||||
private DocumentCollectionCategory resolveCategory(BigInteger categoryId) {
|
||||
if (categoryId == null) {
|
||||
return null;
|
||||
}
|
||||
return documentCollectionCategoryService.getById(categoryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析部门信息。
|
||||
*
|
||||
* @param deptId 部门 ID
|
||||
* @return 部门实体,不存在时返回 {@code null}
|
||||
*/
|
||||
private SysDept resolveDept(BigInteger deptId) {
|
||||
if (deptId == null) {
|
||||
return null;
|
||||
}
|
||||
return sysDeptService.getById(deptId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析必填模型。
|
||||
*
|
||||
* @param modelId 模型 ID
|
||||
* @param errorMessage 模型不存在时抛出的提示
|
||||
* @return 模型实体
|
||||
*/
|
||||
private Model resolveModel(BigInteger modelId, String errorMessage) {
|
||||
if (modelId == null) {
|
||||
throw new BusinessException(errorMessage);
|
||||
}
|
||||
Model model = modelService.getById(modelId);
|
||||
if (model == null) {
|
||||
throw new BusinessException(errorMessage);
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析可选模型。
|
||||
*
|
||||
* @param modelId 模型 ID
|
||||
* @param errorMessage 模型不存在时抛出的提示
|
||||
* @return 模型实体,不存在时返回 {@code null}
|
||||
*/
|
||||
private Model resolveOptionalModel(BigInteger modelId, String errorMessage) {
|
||||
if (modelId == null) {
|
||||
return null;
|
||||
}
|
||||
Model model = modelService.getById(modelId);
|
||||
if (model == null) {
|
||||
throw new BusinessException(errorMessage);
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模型展示名称。
|
||||
*
|
||||
* @param model 模型实体
|
||||
* @return 模型名称
|
||||
*/
|
||||
private String resolveModelName(Model model) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
if (model.getTitle() != null && !model.getTitle().isBlank()) {
|
||||
return model.getTitle();
|
||||
}
|
||||
return model.getModelName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析知识库可见范围文案。
|
||||
*
|
||||
* @param visibilityScope 可见范围编码
|
||||
* @return 展示文案
|
||||
*/
|
||||
private String resolveVisibilityScopeLabel(String visibilityScope) {
|
||||
return switch (VisibilityScope.fromOrDefault(visibilityScope, VisibilityScope.PRIVATE)) {
|
||||
case DEPT -> "部门";
|
||||
case PUBLIC -> "公开";
|
||||
default -> "个人";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析知识库类型文案。
|
||||
*
|
||||
* @param collectionType 知识库类型
|
||||
* @return 展示文案
|
||||
*/
|
||||
private String resolveCollectionTypeLabel(String collectionType) {
|
||||
if (DocumentCollection.TYPE_FAQ.equalsIgnoreCase(collectionType)) {
|
||||
return "FAQ";
|
||||
}
|
||||
return "文档";
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> readResourceSnapshot(ApprovalInstance instance) {
|
||||
Object snapshot = instance.getSnapshotJson() == null ? null : instance.getSnapshotJson().get(SNAPSHOT_KEY);
|
||||
if (!(snapshot instanceof Map<?, ?> map)) {
|
||||
throw new BusinessException("审批快照缺少知识库发布内容");
|
||||
}
|
||||
return (Map<String, Object>) map;
|
||||
}
|
||||
|
||||
private PublishStatus resolvePendingStatus(String actionType) {
|
||||
return ApprovalActionType.DELETE.getCode().equals(actionType)
|
||||
? PublishStatus.DELETE_PENDING
|
||||
: PublishStatus.PUBLISH_PENDING;
|
||||
}
|
||||
|
||||
private void clearPendingStatus(BigInteger knowledgeId) {
|
||||
DocumentCollection collection = documentCollectionService.getById(knowledgeId);
|
||||
if (collection == null) {
|
||||
return;
|
||||
}
|
||||
DocumentCollection update = new DocumentCollection();
|
||||
update.setId(knowledgeId);
|
||||
update.setCurrentApprovalInstanceId(null);
|
||||
update.setPublishStatus(collection.getPublishedSnapshotJson() == null || collection.getPublishedSnapshotJson().isEmpty()
|
||||
? PublishStatus.DRAFT.getCode()
|
||||
: PublishStatus.PUBLISHED.getCode());
|
||||
documentCollectionService.updateById(update);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package tech.easyflow.ai.publish;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.approval.annotation.ApprovalAction;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 知识库发布生命周期应用服务。
|
||||
*/
|
||||
@Service
|
||||
public class KnowledgePublishAppService {
|
||||
|
||||
/**
|
||||
* 提交知识库发布审批。
|
||||
*
|
||||
* @param id 知识库 ID
|
||||
* @return 知识库 ID
|
||||
*/
|
||||
@ApprovalAction(
|
||||
resourceType = "KNOWLEDGE",
|
||||
actionType = "PUBLISH",
|
||||
idExpr = "#id"
|
||||
)
|
||||
public BigInteger submitPublishApproval(BigInteger id) {
|
||||
assertId(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交知识库删除审批。
|
||||
*
|
||||
* @param id 知识库 ID
|
||||
* @return 知识库 ID
|
||||
*/
|
||||
@ApprovalAction(
|
||||
resourceType = "KNOWLEDGE",
|
||||
actionType = "DELETE",
|
||||
idExpr = "#id"
|
||||
)
|
||||
public BigInteger submitDeleteApproval(BigInteger id) {
|
||||
assertId(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
private void assertId(BigInteger id) {
|
||||
if (id == null) {
|
||||
throw new BusinessException("知识库审批时资源ID不能为空");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package tech.easyflow.ai.publish;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.ai.entity.BotWorkflow;
|
||||
import tech.easyflow.ai.entity.Workflow;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
import tech.easyflow.ai.service.BotWorkflowService;
|
||||
import tech.easyflow.ai.service.WorkflowService;
|
||||
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalCallbackContext;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalSubmitCallbackContext;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
|
||||
import tech.easyflow.approval.enums.ApprovalActionType;
|
||||
import tech.easyflow.approval.enums.ApprovalResourceType;
|
||||
import tech.easyflow.approval.service.ApprovalInstanceService;
|
||||
import tech.easyflow.approval.service.ApprovalSubjectHandler;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
import tech.easyflow.system.service.ResourceAccessService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工作流审批处理器。
|
||||
*/
|
||||
@Component
|
||||
public class WorkflowApprovalSubjectHandler implements ApprovalSubjectHandler {
|
||||
|
||||
private static final String SNAPSHOT_KEY = "resourceSnapshot";
|
||||
|
||||
private final WorkflowService workflowService;
|
||||
private final ResourceAccessService resourceAccessService;
|
||||
private final ApprovalInstanceService approvalInstanceService;
|
||||
private final BotWorkflowService botWorkflowService;
|
||||
|
||||
public WorkflowApprovalSubjectHandler(WorkflowService workflowService,
|
||||
ResourceAccessService resourceAccessService,
|
||||
ApprovalInstanceService approvalInstanceService,
|
||||
BotWorkflowService botWorkflowService) {
|
||||
this.workflowService = workflowService;
|
||||
this.resourceAccessService = resourceAccessService;
|
||||
this.approvalInstanceService = approvalInstanceService;
|
||||
this.botWorkflowService = botWorkflowService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resourceType() {
|
||||
return ApprovalResourceType.WORKFLOW.getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApprovalSubmitRequest buildSubmitRequest(BigInteger resourceId, String actionType, BigInteger operatorId) {
|
||||
Workflow workflow = requireWorkflow(resourceId);
|
||||
resourceAccessService.assertAccess(CategoryResourceType.WORKFLOW, workflow, ResourceAction.MANAGE, "无权限管理工作流");
|
||||
if (approvalInstanceService.existsActiveInstance(resourceType(), resourceId)) {
|
||||
throw new BusinessException("当前工作流存在未结束审批,请先处理完成");
|
||||
}
|
||||
|
||||
ApprovalActionType approvalActionType = ApprovalActionType.from(actionType);
|
||||
Map<String, Object> resourceSnapshot = buildResourceSnapshot(workflow);
|
||||
if (approvalActionType == ApprovalActionType.PUBLISH
|
||||
&& JSON.toJSONString(resourceSnapshot).equals(JSON.toJSONString(workflow.getPublishedSnapshotJson()))) {
|
||||
throw new BusinessException("当前工作流没有变更,无需重复发布");
|
||||
}
|
||||
if (approvalActionType == ApprovalActionType.DELETE && hasBotBinding(resourceId)) {
|
||||
throw new BusinessException("此工作流还关联有bot,请先取消关联后再删除!");
|
||||
}
|
||||
|
||||
ApprovalSubmitRequest request = new ApprovalSubmitRequest();
|
||||
request.setResourceType(resourceType());
|
||||
request.setResourceId(resourceId);
|
||||
request.setActionType(approvalActionType.getCode());
|
||||
request.setApplicantId(operatorId);
|
||||
request.setCategoryId(workflow.getCategoryId());
|
||||
request.setDeptId(workflow.getDeptId());
|
||||
request.setSummary((approvalActionType == ApprovalActionType.PUBLISH ? "发布" : "删除") + "工作流:" + workflow.getTitle());
|
||||
request.setSnapshotJson(Map.of(SNAPSHOT_KEY, resourceSnapshot));
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubmitted(ApprovalSubmitCallbackContext context) {
|
||||
Workflow update = new Workflow();
|
||||
update.setId(context.getResourceId());
|
||||
update.setCurrentApprovalInstanceId(context.getInstanceId());
|
||||
update.setPublishStatus(resolvePendingStatus(context.getActionType()).getCode());
|
||||
workflowService.updateById(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApproved(ApprovalCallbackContext context) {
|
||||
ApprovalInstance instance = context.getInstance();
|
||||
if (ApprovalActionType.PUBLISH.getCode().equals(instance.getActionType())) {
|
||||
Workflow update = new Workflow();
|
||||
update.setId(instance.getResourceId());
|
||||
update.setPublishStatus(PublishStatus.PUBLISHED.getCode());
|
||||
update.setCurrentApprovalInstanceId(null);
|
||||
update.setPublishedSnapshotJson(readResourceSnapshot(instance));
|
||||
update.setPublishedAt(new Date());
|
||||
update.setPublishedBy(context.getOperatorId());
|
||||
workflowService.updateById(update);
|
||||
return;
|
||||
}
|
||||
workflowService.removeById(instance.getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRejected(ApprovalCallbackContext context) {
|
||||
clearPendingStatus(context.getInstance().getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRevoked(ApprovalCallbackContext context) {
|
||||
clearPendingStatus(context.getInstance().getResourceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertPublishedAccess(Object identifier, String denyMessage) {
|
||||
Workflow workflow = workflowService.getDetail(String.valueOf(identifier));
|
||||
if (workflow == null || !PublishStatus.from(workflow.getPublishStatus()).isExternallyVisible()
|
||||
|| workflow.getPublishedSnapshotJson() == null || workflow.getPublishedSnapshotJson().isEmpty()) {
|
||||
throw new BusinessException(denyMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private Workflow requireWorkflow(BigInteger id) {
|
||||
Workflow workflow = workflowService.getById(id);
|
||||
if (workflow == null) {
|
||||
throw new BusinessException("工作流不存在");
|
||||
}
|
||||
return workflow;
|
||||
}
|
||||
|
||||
private boolean hasBotBinding(BigInteger workflowId) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create().eq(BotWorkflow::getWorkflowId, workflowId);
|
||||
return botWorkflowService.exists(queryWrapper);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildResourceSnapshot(Workflow workflow) {
|
||||
Map<String, Object> snapshot = new LinkedHashMap<>();
|
||||
snapshot.put("id", workflow.getId());
|
||||
snapshot.put("alias", workflow.getAlias());
|
||||
snapshot.put("deptId", workflow.getDeptId());
|
||||
snapshot.put("tenantId", workflow.getTenantId());
|
||||
snapshot.put("title", workflow.getTitle());
|
||||
snapshot.put("description", workflow.getDescription());
|
||||
snapshot.put("icon", workflow.getIcon());
|
||||
snapshot.put("content", workflow.getContent());
|
||||
snapshot.put("englishName", workflow.getEnglishName());
|
||||
snapshot.put("status", workflow.getStatus());
|
||||
snapshot.put("categoryId", workflow.getCategoryId());
|
||||
snapshot.put("visibilityScope", workflow.getVisibilityScope());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> readResourceSnapshot(ApprovalInstance instance) {
|
||||
Object snapshot = instance.getSnapshotJson() == null ? null : instance.getSnapshotJson().get(SNAPSHOT_KEY);
|
||||
if (!(snapshot instanceof Map<?, ?> map)) {
|
||||
throw new BusinessException("审批快照缺少工作流发布内容");
|
||||
}
|
||||
return (Map<String, Object>) map;
|
||||
}
|
||||
|
||||
private PublishStatus resolvePendingStatus(String actionType) {
|
||||
return ApprovalActionType.DELETE.getCode().equals(actionType)
|
||||
? PublishStatus.DELETE_PENDING
|
||||
: PublishStatus.PUBLISH_PENDING;
|
||||
}
|
||||
|
||||
private void clearPendingStatus(BigInteger workflowId) {
|
||||
Workflow workflow = workflowService.getById(workflowId);
|
||||
if (workflow == null) {
|
||||
return;
|
||||
}
|
||||
Workflow update = new Workflow();
|
||||
update.setId(workflowId);
|
||||
update.setCurrentApprovalInstanceId(null);
|
||||
update.setPublishStatus(workflow.getPublishedSnapshotJson() == null || workflow.getPublishedSnapshotJson().isEmpty()
|
||||
? PublishStatus.DRAFT.getCode()
|
||||
: PublishStatus.PUBLISHED.getCode());
|
||||
workflowService.updateById(update);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package tech.easyflow.ai.publish;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.approval.annotation.ApprovalAction;
|
||||
import tech.easyflow.approval.enums.ApprovalActionType;
|
||||
import tech.easyflow.approval.enums.ApprovalResourceType;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 工作流发布生命周期应用服务。
|
||||
*/
|
||||
@Service
|
||||
public class WorkflowPublishAppService {
|
||||
|
||||
/**
|
||||
* 提交工作流发布审批。
|
||||
*
|
||||
* @param id 工作流 ID
|
||||
* @return 工作流 ID
|
||||
*/
|
||||
@ApprovalAction(
|
||||
resourceType = "WORKFLOW",
|
||||
actionType = "PUBLISH",
|
||||
idExpr = "#id"
|
||||
)
|
||||
public BigInteger submitPublishApproval(BigInteger id) {
|
||||
assertId(id, ApprovalResourceType.WORKFLOW.getCode(), ApprovalActionType.PUBLISH.getCode());
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交工作流删除审批。
|
||||
*
|
||||
* @param id 工作流 ID
|
||||
* @return 工作流 ID
|
||||
*/
|
||||
@ApprovalAction(
|
||||
resourceType = "WORKFLOW",
|
||||
actionType = "DELETE",
|
||||
idExpr = "#id"
|
||||
)
|
||||
public BigInteger submitDeleteApproval(BigInteger id) {
|
||||
assertId(id, ApprovalResourceType.WORKFLOW.getCode(), ApprovalActionType.DELETE.getCode());
|
||||
return id;
|
||||
}
|
||||
|
||||
private void assertId(BigInteger id, String resourceType, String actionType) {
|
||||
if (id == null) {
|
||||
throw new BusinessException(resourceType + " " + actionType + " 审批时资源ID不能为空");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,30 @@ public interface BotService extends IService<Bot> {
|
||||
|
||||
Bot getByAlias(String alias);
|
||||
|
||||
/**
|
||||
* 获取已发布视图。
|
||||
*
|
||||
* @param idOrAlias ID 或别名
|
||||
* @return 已发布视图
|
||||
*/
|
||||
Bot getPublishedDetail(String idOrAlias);
|
||||
|
||||
/**
|
||||
* 根据 ID 获取已发布视图。
|
||||
*
|
||||
* @param id 机器人 ID
|
||||
* @return 已发布视图
|
||||
*/
|
||||
Bot getPublishedById(BigInteger id);
|
||||
|
||||
/**
|
||||
* 把当前资源映射为已发布视图。
|
||||
*
|
||||
* @param bot 当前资源
|
||||
* @return 已发布视图
|
||||
*/
|
||||
Bot toPublishedView(Bot bot);
|
||||
|
||||
SseEmitter checkChatBeforeStart(BigInteger botId, String prompt, String conversationId, BotServiceImpl.ChatCheckResult chatCheckResult);
|
||||
|
||||
SseEmitter startChat(BigInteger botId, String prompt, BigInteger conversationId, List<Map<String, String>> messages,
|
||||
|
||||
@@ -23,4 +23,28 @@ public interface DocumentCollectionService extends IService<DocumentCollection>
|
||||
DocumentCollection getDetail(String idOrAlias);
|
||||
|
||||
DocumentCollection getByAlias(String idOrAlias);
|
||||
|
||||
/**
|
||||
* 获取已发布视图。
|
||||
*
|
||||
* @param idOrAlias ID 或别名
|
||||
* @return 已发布视图
|
||||
*/
|
||||
DocumentCollection getPublishedDetail(String idOrAlias);
|
||||
|
||||
/**
|
||||
* 根据 ID 获取已发布视图。
|
||||
*
|
||||
* @param id 知识库 ID
|
||||
* @return 已发布视图
|
||||
*/
|
||||
DocumentCollection getPublishedById(BigInteger id);
|
||||
|
||||
/**
|
||||
* 把当前资源映射为已发布视图。
|
||||
*
|
||||
* @param collection 当前资源
|
||||
* @return 已发布视图
|
||||
*/
|
||||
DocumentCollection toPublishedView(DocumentCollection collection);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package tech.easyflow.ai.service;
|
||||
import tech.easyflow.ai.entity.Workflow;
|
||||
import com.mybatisflex.core.service.IService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 服务层。
|
||||
*
|
||||
@@ -18,4 +20,28 @@ public interface WorkflowService extends IService<Workflow> {
|
||||
|
||||
|
||||
Workflow getByAlias(String alias);
|
||||
|
||||
/**
|
||||
* 获取已发布视图。
|
||||
*
|
||||
* @param idOrAlias ID 或别名
|
||||
* @return 已发布视图
|
||||
*/
|
||||
Workflow getPublishedDetail(String idOrAlias);
|
||||
|
||||
/**
|
||||
* 根据 ID 获取已发布视图。
|
||||
*
|
||||
* @param id 工作流 ID
|
||||
* @return 已发布视图
|
||||
*/
|
||||
Workflow getPublishedById(BigInteger id);
|
||||
|
||||
/**
|
||||
* 把当前资源映射为已发布视图。
|
||||
*
|
||||
* @param workflow 当前资源
|
||||
* @return 已发布视图
|
||||
*/
|
||||
Workflow toPublishedView(Workflow workflow);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package tech.easyflow.ai.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.easyagents.core.file2text.File2TextService;
|
||||
import com.easyagents.core.file2text.source.HttpDocumentSource;
|
||||
@@ -27,7 +28,10 @@ import tech.easyflow.ai.easyagents.listener.ChatStreamListener;
|
||||
import tech.easyflow.ai.easyagents.memory.DefaultBotMessageMemory;
|
||||
import tech.easyflow.ai.easyagents.memory.PublicBotMessageMemory;
|
||||
import tech.easyflow.ai.easyagents.memory.RuntimeChatMemory;
|
||||
import tech.easyflow.ai.easyagents.tool.WorkflowTool;
|
||||
import tech.easyflow.ai.easyagentsflow.support.PublishedWorkflowDefinitionIds;
|
||||
import tech.easyflow.ai.entity.*;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
import tech.easyflow.ai.mapper.BotMapper;
|
||||
import tech.easyflow.ai.service.*;
|
||||
import tech.easyflow.ai.utils.CustomBeanUtils;
|
||||
@@ -75,6 +79,7 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
private Map<String, Object> modelOptions;
|
||||
private ChatModel chatModel;
|
||||
private String conversationIdStr;
|
||||
private boolean publishedAccess;
|
||||
|
||||
public Bot getAiBot() {return aiBot;}
|
||||
|
||||
@@ -91,6 +96,10 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
public String getConversationIdStr() {return conversationIdStr;}
|
||||
|
||||
public void setConversationIdStr(String conversationIdStr) {this.conversationIdStr = conversationIdStr;}
|
||||
|
||||
public boolean isPublishedAccess() {return publishedAccess;}
|
||||
|
||||
public void setPublishedAccess(boolean publishedAccess) {this.publishedAccess = publishedAccess;}
|
||||
}
|
||||
|
||||
@Resource(name = "sseThreadPool")
|
||||
@@ -100,8 +109,12 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
@Resource
|
||||
private BotWorkflowService botWorkflowService;
|
||||
@Resource
|
||||
private WorkflowService workflowService;
|
||||
@Resource
|
||||
private BotDocumentCollectionService botDocumentCollectionService;
|
||||
@Resource
|
||||
private DocumentCollectionService documentCollectionService;
|
||||
@Resource
|
||||
private BotPluginService botPluginService;
|
||||
@Resource
|
||||
private PluginItemService pluginItemService;
|
||||
@@ -141,6 +154,57 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
return getOne(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Bot getPublishedDetail(String id) {
|
||||
return toPublishedView(getDetail(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Bot getPublishedById(BigInteger id) {
|
||||
return toPublishedView(getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Bot toPublishedView(Bot bot) {
|
||||
if (bot == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> snapshot = bot.getPublishedSnapshotJson();
|
||||
if (snapshot == null || snapshot.isEmpty()) {
|
||||
return bot;
|
||||
}
|
||||
Bot published = JSON.parseObject(JSON.toJSONString(snapshot), Bot.class);
|
||||
if (published == null) {
|
||||
return bot;
|
||||
}
|
||||
published.setId(bot.getId());
|
||||
published.setTenantId(bot.getTenantId());
|
||||
published.setCategoryId(bot.getCategoryId());
|
||||
published.setDeptId(bot.getDeptId());
|
||||
published.setCreated(bot.getCreated());
|
||||
published.setCreatedBy(bot.getCreatedBy());
|
||||
published.setModified(bot.getModified());
|
||||
published.setModifiedBy(bot.getModifiedBy());
|
||||
published.setPublishStatus(bot.getPublishStatus());
|
||||
published.setCurrentApprovalInstanceId(bot.getCurrentApprovalInstanceId());
|
||||
published.setPublishedSnapshotJson(bot.getPublishedSnapshotJson());
|
||||
published.setPublishedAt(bot.getPublishedAt());
|
||||
published.setPublishedBy(bot.getPublishedBy());
|
||||
if (published.getPublishStatus() == null) {
|
||||
published.setPublishStatus(PublishStatus.DRAFT.getCode());
|
||||
}
|
||||
return published;
|
||||
}
|
||||
|
||||
public SseEmitter checkChatBeforeStart(BigInteger botId, String prompt, String conversationId, ChatCheckResult chatCheckResult) {
|
||||
if (!StringUtils.hasLength(prompt)) {
|
||||
return ChatSseUtil.sendSystemError(conversationId, "提示词不能为空");
|
||||
@@ -175,6 +239,16 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
if ((!login || anonymousAccount) && !aiBot.isAnonymousEnabled()) {
|
||||
return ChatSseUtil.sendSystemError(conversationId, "此聊天助手不支持匿名访问");
|
||||
}
|
||||
if (!login || anonymousAccount) {
|
||||
Bot publishedBot = toPublishedView(aiBot);
|
||||
if (!PublishStatus.from(aiBot.getPublishStatus()).isExternallyVisible()) {
|
||||
return ChatSseUtil.sendSystemError(conversationId, "聊天助手尚未发布");
|
||||
}
|
||||
aiBot = publishedBot;
|
||||
chatCheckResult.setPublishedAccess(true);
|
||||
} else {
|
||||
chatCheckResult.setPublishedAccess(false);
|
||||
}
|
||||
Map<String, Object> modelOptions = aiBot.getModelOptions();
|
||||
Model model = modelService.getModelInstance(aiBot.getModelId());
|
||||
if (model == null) {
|
||||
@@ -213,7 +287,10 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
prompt = "【用户问题】:\n" + prompt + "\n\n请基于用户上传的附件内容回答用户问题: \n" + "【用户上传的附件内容】:\n" + attachmentsToString ;
|
||||
}
|
||||
UserMessage userMessage = new UserMessage(prompt);
|
||||
userMessage.addTools(buildFunctionList(Maps.of("botId", botId).set("needEnglishName", false)));
|
||||
userMessage.addTools(buildFunctionList(Maps.of("botId", botId)
|
||||
.set("needEnglishName", false)
|
||||
.set("bot", chatCheckResult.getAiBot())
|
||||
.set("publishedOnly", chatCheckResult.isPublishedAccess())));
|
||||
ChatOptions chatOptions = getChatOptions(modelOptions);
|
||||
Boolean enableDeepThinking = MapUtil.getBoolean(modelOptions, Bot.KEY_ENABLE_DEEP_THINKING, false);
|
||||
chatOptions.setThinkingEnabled(enableDeepThinking);
|
||||
@@ -270,6 +347,8 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
userMessage.addTools(buildFunctionList(Maps.of("botId", botId)
|
||||
.set("needEnglishName", false)
|
||||
.set("needAccountId", false)
|
||||
.set("bot", chatCheckResult.getAiBot())
|
||||
.set("publishedOnly", chatCheckResult.isPublishedAccess())
|
||||
));
|
||||
ChatSseEmitter chatSseEmitter = new ChatSseEmitter();
|
||||
SseEmitter emitter = chatSseEmitter.getEmitter();
|
||||
@@ -380,30 +459,39 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
if (needEnglishName == null) {
|
||||
needEnglishName = false;
|
||||
}
|
||||
Bot runtimeBot = (Bot) buildParams.get("bot");
|
||||
boolean usePublishedSnapshot = Boolean.TRUE.equals(buildParams.get("publishedOnly"))
|
||||
&& runtimeBot != null
|
||||
&& runtimeBot.getPublishedSnapshotJson() != null
|
||||
&& PublishStatus.from(runtimeBot.getPublishStatus()).isExternallyVisible();
|
||||
|
||||
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||
|
||||
// 工作流 function 集合
|
||||
queryWrapper.eq(BotWorkflow::getBotId, botId);
|
||||
List<BotWorkflow> botWorkflows = botWorkflowService.getMapper()
|
||||
.selectListWithRelationsByQuery(queryWrapper);
|
||||
if (botWorkflows != null && !botWorkflows.isEmpty()) {
|
||||
for (BotWorkflow botWorkflow : botWorkflows) {
|
||||
Tool function = botWorkflow.getWorkflow().toFunction(needEnglishName);
|
||||
functionList.add(function);
|
||||
if (usePublishedSnapshot) {
|
||||
appendPublishedWorkflowTools(functionList, runtimeBot, needEnglishName);
|
||||
appendPublishedKnowledgeTools(functionList, runtimeBot, needEnglishName);
|
||||
} else {
|
||||
// 工作流 function 集合
|
||||
queryWrapper.eq(BotWorkflow::getBotId, botId);
|
||||
List<BotWorkflow> botWorkflows = botWorkflowService.getMapper()
|
||||
.selectListWithRelationsByQuery(queryWrapper);
|
||||
if (botWorkflows != null && !botWorkflows.isEmpty()) {
|
||||
for (BotWorkflow botWorkflow : botWorkflows) {
|
||||
Tool function = botWorkflow.getWorkflow().toFunction(needEnglishName);
|
||||
functionList.add(function);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 知识库 function 集合
|
||||
queryWrapper = QueryWrapper.create();
|
||||
queryWrapper.eq(BotDocumentCollection::getBotId, botId);
|
||||
List<BotDocumentCollection> botDocumentCollections = botDocumentCollectionService.getMapper()
|
||||
.selectListWithRelationsByQuery(queryWrapper);
|
||||
if (botDocumentCollections != null && !botDocumentCollections.isEmpty()) {
|
||||
for (BotDocumentCollection botDocumentCollection : botDocumentCollections) {
|
||||
Tool function = botDocumentCollection.getKnowledge()
|
||||
.toFunction(needEnglishName, botDocumentCollection.getRetrievalMode().name());
|
||||
functionList.add(function);
|
||||
// 知识库 function 集合
|
||||
queryWrapper = QueryWrapper.create();
|
||||
queryWrapper.eq(BotDocumentCollection::getBotId, botId);
|
||||
List<BotDocumentCollection> botDocumentCollections = botDocumentCollectionService.getMapper()
|
||||
.selectListWithRelationsByQuery(queryWrapper);
|
||||
if (botDocumentCollections != null && !botDocumentCollections.isEmpty()) {
|
||||
for (BotDocumentCollection botDocumentCollection : botDocumentCollections) {
|
||||
Tool function = botDocumentCollection.getKnowledge()
|
||||
.toFunction(needEnglishName, botDocumentCollection.getRetrievalMode().name());
|
||||
functionList.add(function);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,6 +525,59 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
return functionList;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void appendPublishedWorkflowTools(List<Tool> functionList, Bot runtimeBot, boolean needEnglishName) {
|
||||
Object workflows = runtimeBot.getPublishedSnapshotJson().get("workflowBindings");
|
||||
if (!(workflows instanceof List<?> workflowBindings)) {
|
||||
return;
|
||||
}
|
||||
for (Object item : workflowBindings) {
|
||||
if (!(item instanceof Map<?, ?> workflowMap)) {
|
||||
continue;
|
||||
}
|
||||
Object workflowId = workflowMap.get("workflowId");
|
||||
if (workflowId == null) {
|
||||
continue;
|
||||
}
|
||||
Workflow workflow = workflowService.getPublishedById(new BigInteger(String.valueOf(workflowId)));
|
||||
if (workflow == null) {
|
||||
continue;
|
||||
}
|
||||
WorkflowTool tool = new WorkflowTool(
|
||||
workflow,
|
||||
needEnglishName,
|
||||
PublishedWorkflowDefinitionIds.published(String.valueOf(workflow.getId()))
|
||||
);
|
||||
functionList.add(tool);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void appendPublishedKnowledgeTools(List<Tool> functionList, Bot runtimeBot, boolean needEnglishName) {
|
||||
Object knowledges = runtimeBot.getPublishedSnapshotJson().get("knowledgeBindings");
|
||||
if (!(knowledges instanceof List<?> knowledgeBindings)) {
|
||||
return;
|
||||
}
|
||||
for (Object item : knowledgeBindings) {
|
||||
if (!(item instanceof Map<?, ?> bindingMap)) {
|
||||
continue;
|
||||
}
|
||||
Object knowledgeId = bindingMap.get("knowledgeId");
|
||||
if (knowledgeId == null) {
|
||||
continue;
|
||||
}
|
||||
DocumentCollection knowledge = documentCollectionService.getPublishedById(new BigInteger(String.valueOf(knowledgeId)));
|
||||
if (knowledge == null) {
|
||||
continue;
|
||||
}
|
||||
Object retrievalMode = bindingMap.get("retrievalMode");
|
||||
functionList.add(knowledge.toFunction(
|
||||
needEnglishName,
|
||||
retrievalMode == null ? null : String.valueOf(retrievalMode)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public String attachmentsToString(List<String> fileList) {
|
||||
StringBuilder messageBuilder = new StringBuilder();
|
||||
if (fileList != null && !fileList.isEmpty()) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package tech.easyflow.ai.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.easyagents.core.document.Document;
|
||||
import com.easyagents.core.model.rerank.RerankException;
|
||||
import com.easyagents.core.model.rerank.RerankModel;
|
||||
@@ -29,6 +30,7 @@ import tech.easyflow.ai.entity.DocumentChunk;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
import tech.easyflow.ai.entity.FaqItem;
|
||||
import tech.easyflow.ai.entity.Model;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
import tech.easyflow.ai.mapper.DocumentChunkMapper;
|
||||
import tech.easyflow.ai.mapper.DocumentCollectionMapper;
|
||||
import tech.easyflow.ai.mapper.FaqItemMapper;
|
||||
@@ -148,6 +150,57 @@ public class DocumentCollectionServiceImpl extends ServiceImpl<DocumentCollectio
|
||||
return formatDocuments(searchDocuments, shouldApplyMinSimilarityFilter(retrievalMode, reranked), minSimilarity, docRecallMaxNum);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public DocumentCollection getPublishedDetail(String idOrAlias) {
|
||||
return toPublishedView(getDetail(idOrAlias));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public DocumentCollection getPublishedById(BigInteger id) {
|
||||
return toPublishedView(getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public DocumentCollection toPublishedView(DocumentCollection collection) {
|
||||
if (collection == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> snapshot = collection.getPublishedSnapshotJson();
|
||||
if (snapshot == null || snapshot.isEmpty()) {
|
||||
return collection;
|
||||
}
|
||||
DocumentCollection published = JSON.parseObject(JSON.toJSONString(snapshot), DocumentCollection.class);
|
||||
if (published == null) {
|
||||
return collection;
|
||||
}
|
||||
published.setId(collection.getId());
|
||||
published.setTenantId(collection.getTenantId());
|
||||
published.setCategoryId(collection.getCategoryId());
|
||||
published.setDeptId(collection.getDeptId());
|
||||
published.setCreated(collection.getCreated());
|
||||
published.setCreatedBy(collection.getCreatedBy());
|
||||
published.setModified(collection.getModified());
|
||||
published.setModifiedBy(collection.getModifiedBy());
|
||||
published.setPublishStatus(collection.getPublishStatus());
|
||||
published.setCurrentApprovalInstanceId(collection.getCurrentApprovalInstanceId());
|
||||
published.setPublishedSnapshotJson(collection.getPublishedSnapshotJson());
|
||||
published.setPublishedAt(collection.getPublishedAt());
|
||||
published.setPublishedBy(collection.getPublishedBy());
|
||||
if (published.getPublishStatus() == null) {
|
||||
published.setPublishStatus(PublishStatus.DRAFT.getCode());
|
||||
}
|
||||
return published;
|
||||
}
|
||||
|
||||
private VectorRetriever buildVectorRetriever(DocumentCollection documentCollection,
|
||||
int docRecallMaxNum,
|
||||
Float minSimilarity) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
|
||||
package tech.easyflow.ai.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import tech.easyflow.ai.entity.Workflow;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
import tech.easyflow.ai.mapper.WorkflowMapper;
|
||||
import tech.easyflow.ai.service.WorkflowService;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
@@ -11,6 +12,9 @@ import com.mybatisflex.core.query.QueryWrapper;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.ai.utils.CustomBeanUtils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 服务层实现。
|
||||
*
|
||||
@@ -52,6 +56,57 @@ public class WorkflowServiceImpl extends ServiceImpl<WorkflowMapper, Workflow> i
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Workflow getPublishedDetail(String idOrAlias) {
|
||||
return toPublishedView(getDetail(idOrAlias));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Workflow getPublishedById(BigInteger id) {
|
||||
return toPublishedView(getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Workflow toPublishedView(Workflow workflow) {
|
||||
if (workflow == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> snapshot = workflow.getPublishedSnapshotJson();
|
||||
if (snapshot == null || snapshot.isEmpty()) {
|
||||
return workflow;
|
||||
}
|
||||
Workflow published = JSON.parseObject(JSON.toJSONString(snapshot), Workflow.class);
|
||||
if (published == null) {
|
||||
return workflow;
|
||||
}
|
||||
published.setId(workflow.getId());
|
||||
published.setTenantId(workflow.getTenantId());
|
||||
published.setCategoryId(workflow.getCategoryId());
|
||||
published.setDeptId(workflow.getDeptId());
|
||||
published.setCreated(workflow.getCreated());
|
||||
published.setCreatedBy(workflow.getCreatedBy());
|
||||
published.setModified(workflow.getModified());
|
||||
published.setModifiedBy(workflow.getModifiedBy());
|
||||
published.setPublishStatus(workflow.getPublishStatus());
|
||||
published.setCurrentApprovalInstanceId(workflow.getCurrentApprovalInstanceId());
|
||||
published.setPublishedSnapshotJson(workflow.getPublishedSnapshotJson());
|
||||
published.setPublishedAt(workflow.getPublishedAt());
|
||||
published.setPublishedBy(workflow.getPublishedBy());
|
||||
if (published.getPublishStatus() == null) {
|
||||
published.setPublishStatus(PublishStatus.DRAFT.getCode());
|
||||
}
|
||||
return published;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean updateById(Workflow entity, boolean ignoreNulls) {
|
||||
|
||||
Reference in New Issue
Block a user